本文大概记录了笔者在使用 Redis 过程中踩过的坑,以及相应的建议。
欢迎关注作者公众号:闲余说
笔者参与搭建、使用过百T的 Reids 集群以及数百T的磁盘存储集群,这些集群,有的支持异地容灾,有的支持业务(业务多写等方式)异地多活,有自身支持异地多活。但基本都是双副本、三地部署。而且承担了上百个上游业务,业务数据类型各种各样、数据量也有大有小,读写比也不尽相同。因此,在使用过程中遇到各种各样的问题,但总结下来,无非是 大value、大key、集群配置等使用问题。
上面章节说明了,大value的危害,那么怎么避免或者怎么解决呢?下面我通过几个例子做些说明。
list
value数目过多,笔者遇到过用list
做模拟消息队列的业务,其中有个业务在模拟消息队列时,消费者(消费完删除对应的value)是一个离线脚本,某天跑脚本的机器挂了,脚本也就停了,导致该list
膨胀,最终触发线上容量报警。
建议:此类情况,建议使用kafka等常规消息队列服务,毕竟Redis存储容量有限。
hash
中field
过多,业务需要存储用户是否领取过礼物,同时,又想很快获取哪些用户领取了礼物,因此将这个礼物领取功能使用一个hash
存储,key为gift
,field
为user_id
,最终该hash
大小可想而知。某天,该RD,手动删除了该hash
导致整体可用性抖动持续数秒。
建议:1.此case中,线上用string
,统计通过日志处理即可;2. hash
中field
最好不要上万,如果必须是用hash
建议拆成多个hash
,比如gift_1, ..., gitf_n
业务侧通过hash算法将对应的user_id
hash到不同的hash
key中,避免了大value;3. 最好不要执行hgetall
等命令,建议使用scan + hmget
组合操作;4. 不用直接del
一个大value,建议用scan + hdel
组合操作;4. 大value数据误删恢复,建议将RDB加载到例行Redis上然后获取全量数据,在写回线上。
遇到过把请求URL当缓存key的,key长度达500B以上,甚至比value还长。
建议:key的信息熵尽可能的高,去掉一些共性的,没有意义上的部分,建议做好逻辑规划,比如参考MySQL等关系型数据库,规划出来database、table的概念避免冲突,一个key可以是database:table:id
这种结构,进一步可以精简database
为db
,table
为t
,最终key为db:t:id
。
业务将一份公共资源存储在一个set
中,所以用户请求都会访问该公共资源,判断自己是否在其中,也是每次都通过smembers
将整个set
数据拉到本地最判断,导致存储该set
的Redis CPU使用率100%,Redis实例hang死,进而影响该Redis实例上的其他业务。
建议:1. 使用sismember
命令,避免拉取整个set
数据;2. 将数据分成n份,比分key_1, ..., key_n
这n份数据完全一样,业务通过hash函数确认每个请求该访问哪个key,该方法是典型的空间换时间的讨论,避免集中访问某一个key(该方案只对Redis集群方案有效),注意数据需要同步更新;3. Redis从实例承担部分读请求。强烈建议公共缓存,即大部分请求都会访问的数据复制多份保证热点key打散(对集群有效)。
线上Redis实例配置了stop-writes-on-bgsave-error
参数,某天凌晨该实例部署机器磁盘故障,导致生成rdb
失败,由于配置了该参数是的该机器上所有实例成为只读实例,写入失败。
建议:如果非必须,不建议设置该参数为on
.
线上实例混布,部分机器上实例部署密集并且部分实例每30min
执行一次bgsave
导致流量高峰期可用性抖动。
建议:部署时,需要预留一些CPU、内存等资源,不要过于密集。同时,非必要情况下不要频繁执行bgsave
.
业务将一次通过n条资源id去查询后端数据库并将结果缓存(key为资源id拼接的字符串,value为返回的资源详情列表),这样导致资源详情信息重复缓存(比如,很多请求返回结果中都包括相同的资源详情信息)进而导致存储数据膨胀,而且后续只有资源id列表相同(id、顺序、个数等完全相同)才能命中缓存。
建议:按资源维度缓存,每个资源id为key,资源详情信息为value,一次拿n个资源的详情信息可以通过mget
获取。这样每个资源的详情信息只存储一份,同时请求的命中率也大大提高。
几个业务都使用一份相同资源(比如用户的信息),为了方面可能每个业务都缓存了一份公共信息,导致资源浪费
建议:结合业务抽取出公共数据,比如资源数据,可以抽取出来,所有用户共享
业务缓存如{"app_id":"xxx", "app_key":"yyy", "app_secret":"zzz"}
此类信息json
结构固定的数据,key
反复存储导致浪费。
建议:可以考虑去掉json
中的key
,直接存储json
中的value
如"xxx,yyy,zzz"
。另外,高并发写入场景中,在条件允许的情况下,建议字符串长度控制在39字节(Redis 5.0 时可以设置为44,也可以通过适当改动源码调整该值,但可能会导致内存碎片)以内,减少创建redisObject内存分配次数,从而提高性能。
Redis官网
Redis开发与运维
How Twitter Uses Redis To Scale - 105TB RAM, 39MM QPS, 10,000+ Instances
Latency Numbers Every Programmer Should Know