1. 什么是 bigkey ?
在 Redis 中,“大 key” 是指占用大量内存或处理时间较长的键,可能会对性能产生负面影响。这可能是由于列表、集合、有序集合或哈希等数据结构中的元素数量过多,或者字符串类型的值长度超过了一定阈值。
相应地,“大 value” 指的是存储在 Redis 中的数值类型数据结构或字符串类型数据结构,它们可能包含大量的数据内容。大值会占用大量的内存空间,可能导致性能下降或引起内存溢出问题。因此,管理和优化大键和大值是维护 Redis 数据库性能的重要方面。
2.bigkey 带来了什么危害?
Redis 阻塞:因为 Redis 单线程特性,如果操作某个 Bigkey 耗时比较久,则后面的请求会被阻塞。
内存空间不均匀:比如在 Redis cluster 或者 codis 中,会造成节点的内存使用不均匀。
过期时可能阻塞:如果 Bigkey 设置了过期时间,当过期后,这个 key 会被删除,假如没有使用 Redis 4.0 的过期异步删除,就会存在阻塞 Redis 的可能性,并且慢查询中查不到(因为这个删除是内部循环事件)。
导致倾斜:某个实例上正好保存了 bigkey。bigkey 的 value 值很大(String 类型),或者是 bigkey 保存了大量集合元素(集合类型),会导致这个实例的数据量增加,内存资源消耗也相应增加。实例的处理压力就会增大,速度变慢,甚至还可能会引起这个实例的内存资源耗尽,从而崩溃。
3.如何排查 Redis 的 bigkey?
Redis 可以在执行 redis-cli 命令时带上–bigkeys 选项,进而对整个数据库中的键值对大小情况进行统计分析,比如说,统计每种数据类型的键值对个数以及平均大小。此外,这个命令执行后,会输出每种数据类型中最大的 bigkey 的信息,对于 String 类型来说,会输出最大 bigkey 的字节长度,对于集合类型来说,会输出最大 bigkey 的元素个数。
redis-cli -h xxxxxx.redis.rds.aliyuncs.com -a shiliName:password --bigkeys
4.bigkey 怎么处理
- 首先可以考虑在应用层先对 Key 进行压缩,比如LZ4/Snappy/ZLIB 之类的,再配合 Redis 客户端的序列化配置,可以“无侵入”的完成这个 key 的压缩操作
- 对key重新设计使key越小越好。使用更贴切自己业务的缓存策略比如:LRU,LFU,FIFO,超时等;或者使用这几种策略的组合方式;为了达到最好效果也可以在应用层自己编写处理逻辑
- 把key放在不同的redis实例,分片中存储
4.1 单个K存储的V很大
-
该key需要每次都整存整取
尝试将对象分拆成几个K.V, 使用multiGet
获取值。拆分旨在降低单次操作的压力,将操作压力平摊到多个Redis实例,降低对单个redis的I/O影响。 -
该对象每次只需要存取部分数据
类似上一种方案,拆分成几个K.V,也可将这个大对象存储在一个hash,每个field代表一个具体属性。hget、hmget
获取部分value;hset,hmset
来更新部分属性。
4.2 一个集群存储了上亿key
如果key个数过多,会带来更多内存空间占用:
-
key本身的占用
每个key 都会有一个Category前缀 -
集群模式中,服务端需要建立一些slot2key的映射关系
这其中的指针占用在key多的情况下也是浪费巨大空间
这两方面在key个数上亿时消耗内存十分明显(Redis 3.2及以下版本均存在这个问题,4.0有优化)。所以减少K个数可以减少内存消耗,可以参考的方案是转Hash结构存储,即原先是直接使用Redis String 的结构存储,现在将多个key存储在一个Hash结构:
4.2.1 key本身具备强相关性
比如多个K代表一个对象,每个K是对象的一个属性,这种可直接按照特定对象的特征来设置一个新K——Hash结构, 原先的K则作为这个新Hash 的field。
4.2.2 key本身无相关性
预估总量,预分一个固定的桶数量:
比如现在预估K总计2亿,按一个hash存储 100个field算,需要 2亿 / 100 = 200W 个桶 (200W 个K占用的空间很少,2亿可能有近20G )。
现在按200W固定桶分,即先计算出桶的序号
hash(123456789) % 200W
最好保证该hash算法的值是个正数,否则需要调整模除的规则。
这样算出三个key 的桶分别是 1、2、2。 所以
存储时调用hset(key, field, value)
读取时使用hget(key, field)
注意hash取模对负数的处理,还有预分桶时, 一个hash 中存储的值最好不要超过 512,100 左右较为合适
5.删除时需要注意防止阻塞
异步删除操作是 Redis 4.0 以后才有的功能,如果你使用的是 4.0 之前的版本,当你遇到 bigkey 删除时,先使用集合类型提供的 SCAN 命令读取数据,然后再进行删除。因为用 SCAN 命令可以每次只读取一部分数据并进行删除,这样可以避免一次性删除大量 key 给主线程带来的阻塞。例如,对于 Hash 类型的 bigkey 删除,你可以使用 HSCAN 命令,每次从 Hash 集合中获取一部分键值对(例如 200 个),再使用 HDEL 删除这些键值对,这样就可以把删除压力分摊到多次操作中,那么,每次删除操作的耗时就不会太长,也就不会阻塞主线程了