在使用 Redis 时,可能会出现请求响应慢、网络卡顿、数据丢失的情况。排查问题的时候,发现是 big keys 的问题。
在 Redis 中,一个字符串类型最大可以达到 512MB,其他非字符串类型的集合类型(list、set、hash、zset等)可以存储 40 亿个(2^32-1),但在实际业务场景中,并不需要这么大的内存。而且对于一个请求量大的互联网软件,对数据的大小要求更加的严格。如果达到如下标准,就可以认定是 big keys 了:
以上对 big keys 的判断标准并不是唯一,只是一个大题的标准。在实际业务开发中,对 big keys 的判断是需要根据具体的使用场景做不同的判断。比如操作某个 key 导致请求响应时间变慢,那么这个 key 就可以判定成 big keys。
一般来说,big keys 的产生都是由于程序的设计不当,或者对数据的规模没有一个大体的估算。比如:
以上几种类型都是在实际运维中遇到的。在开发中需要根据预估的数据大小来合理的设计缓存数据。
在系统中如果存在 big keys,会导致请求数据响应变慢、请求超时或者系统不稳定。
Redis 是单线程工作的,同一时间只能处理一个请求,操作 big keys 时比较耗时,请求响应也变慢。其他请求也处于阻塞状态,导致请求超时。除了查询 big keys 比较耗时,删除 big keys 也会导致一样的问题。
请求单个 big keys 产生的网络流量比较大,假设一个 big keys 为 1MB,客户端每秒访问量是 1000,那么每秒产生 1000MB 的流量,普通的千兆网卡承受不了这么大的流量。而且一般会在单机部署多个Redis实例,一个 big keys 可能也会影响其他实例。
Redis 集群模式中,key根据不同的hash嘈分配到不同的节点上,当大部分的 big keys 分布在同一个节点,导致内存倾斜在同一个节点上,内存分布不均。在水平扩容时,需要以最大容量的节为准,浪费内存。
Redis4.0 后提供了 --bigkeys
命令,比如:
./redis-cli --bigkeys
获取每个数据类型最大的 big keys,同时给出每个类型键的个数和平均大小。因为 Redis 是单线程工作的,为了减少对线上请求的影响,执行--bigkeys
命令需要注意一下几点:
--bigkeys
也是扫描数据,会造成其他线程阻塞。--i
参数,降低扫描的执行速度,比如 --i 0.1
表示 100 毫秒执行一次。找到 big keys 之后,首先需要删除对应的big keys,但是使用 del 命令删除 big keys 是比较耗时的。Redis4.0 后可以使用 unlink 删除,和 del 命令相比,unlink 是非阻塞的异步删除。
非字符串的 big keys,使用 hscan、sscan、zscan 方式渐进式删除,同时要注意防止big keys 过期时间自动删除问题(例如一个 200 万的 zset 设置1小时过期,会触发del操作,造成阻塞)。
字符串类型的数据是减少字符串的长度,将一个字符串拆成几个小的字符串。非字符串的是减少元素数量。这些都是讲一个 key 拆成多个 key,比如:
如果 big keys 无法避免,那获取数据尽量不要把所有的数据都取出来,就使用分段的方式取出数据。删除的方式也类似,分段删除数据。