序列化与反序列化的坑
刚开始用Redis的时候,我直接往里面塞Python的字典对象。结果报错说无法序列化。Redis只认字符串、字节这些基础类型。你得手动转JSON。把字典用json.dumps转成字符串存进去,取出来再用json.loads还原。别偷懒用pickle,它不安全而且跨语言不兼容。团队里有人图省事,结果后期数据迁移到Java项目时全废了。
连接池不是自动创建的
很多人以为redis.Redis()会自动管理连接。实际情况是,每次调用都会新建一个TCP连接。高并发场景下,你的程序会因为端口耗尽或者连接数超限挂掉。正确的做法是用redis.ConnectionPool。初始化的时候设置max_connections,比如pool = redis.ConnectionPool(max_connections=10)。然后把池子传给Redis客户端。这样连接的创建和释放都交给池子管理,程序跑得稳。
键过期时间是个陷阱
设置键的过期时间,单位是秒。但有人会把毫秒当秒用。执行setex(key, 1, value),本来想设一天,结果一分钟后键就消失了。还有种情况是,你用expire方法设了过期时间,但后来对这个键执行了set操作,过期时间会被清掉。所以每次更新值后,记得重新设过期时间。数据莫名其妙消失,八成是这里出了问题。
管道不是事务
用管道批量提交命令,能省网络开销。有人以为管道里的操作是原子性的。错了。管道只是把多条命令打包发送,服务器按顺序挨个执行。如果中间某条命令失败,后面的还会继续。要保证原子性,得用redis.Redis.transaction和WATCH命令。我之前用管道做扣库存操作,高并发时数据错乱,查了半天才发现这个区别。
编码问题让人头疼
Python3里字符串是Unicode。默认连接Redis时,编码是utf-8。如果你存了gbk或者其他编码的数据,取出来就乱码。有个同事的爬虫存了中文字符,他用的服务器默认编码是ascii,结果全变成问号。解决办法是在创建客户端时指定decode_responses=True,这样输出的都是字符串,不再返回字节。或者统一在项目里规定编码标准。
大key操作要小心
一个键里存了几百万个元素的列表,或者几千KB的字符串。这种大key在执行del或者smembers的时候,会导致Redis阻塞。线上服务会卡几秒钟。系统管理员会找你谈话。处理办法是分批查询,比如用sscan代替smembers。删除大key前用unlink命令,它是异步删除,不阻塞主线程。
订阅发布模式要注意连接断开
用pubsub订阅频道后,连接长时间保持空闲。Redis服务器有超时设置,默认是0不超时。但如果有防火墙或者负载均衡设备,它们可能会断开空闲连接。订阅者这边不知道,一直在等消息。结果消息发不出去。解决方案是加个心跳机制,定时发送ping命令。Python的redis-py库没有自动保活功能,需要手动实现。
生产环境别用默认端口和密码
Redis默认端口6379,没密码的话,外网扫描工具几分钟就能找到。有人把Redis部署在公网服务器上,没配防火墙,结果数据库被勒索,所有键被删光。一定要设置requirepass密码,绑定bind 127.0.0.1或内网IP。还有把rename-command FLUSHALL改成其他名字,防止被人清库。