大key的标准
一般的业务场景下,对并发和容量要求都不大:
单个string的value超过1MB
容器数据结构元素数量超过10000
在高并发且低延迟的场景里:
单个string的value超过10KB
容器数据结构元素数量超过5k或是整体value超过10MB
阿里云的云Redis:
Key本身的数据量过大:一个String类型的Key,它的值为5 MB。
Key中的成员数过多:一个ZSET类型的Key,它的成员数量为10,000个。
Key中成员的数据量过大:一个Hash类型的Key,它的成员数量虽然只有2,000个但这些成员的Value(值)总大小为100 MB。
大Key的影响
- 读取成本高
时延高,因为执行命令时间更长
消耗更多的带宽,导致自身服务变慢,从而影响相关服务
- 大Key相关操作容易阻塞,从而导致无法正常响应
慢查询变多
删除的时候容易导致主库长时间的阻塞,进而可能引发同步中断或主从切换
占用更多的存储空间,内存达到maxmemory参数定义的上限引发操作阻塞或重要的key被逐出,甚至是OOM
集群架构下,某个数据分片的内存使用率远超其他数据分片,导致数据分片的内存资源分配不均衡
大key产生的原因
业务设计不合理:最常见的原因,不经过合理的拆分,就把大json塞在一个key里,甚至塞二进制文件数据。没有对key中的成员进行合理的拆分,造成个别key中的成员数量过多。
没有处理好value的动态增长问题。如果一直添加value数据的话,没有定期的删除机制、合理的过期机制,或是没有设定合适的阈值,早晚会变成大key。例如:微博明星的粉丝列表、热门评论、直播弹幕
程序bug:某些异常情况导致某些key的生命周期超出预期,或是value数量异常增长。使用LIST类型key的业务消费侧发生代码故障,造成对应的key的成员只增不减。
如何找到大key
云Redis提供的服务
云服务提供的实时Top Key统计服务或是离线全量Key分析服务
通过redis-cli的bigkeys命令
Redis提供了bigkeys参数能够使redis-cli以遍历的方式分析Redis实例中的所有key,并返回key的整体统计信息与每个数据类型中Top1的大key,bigkeys只能分析并输入六种数据类型(STRING,LIST,HASH,SET,ZSET,STREAM),命令示例
redis-cli -h r-***************.redis.rds.aliyuncs.com -a <password> --bigkeys
注意:若只需要分析STRING类型的大key或是找出成员数量超过10个的HASH Key,则bigkeys参数无法直接实现该类需求。
Redis RDB Tools工具
支持定制化分析的开源工具 GitHub - sripathikrishnan/redis-rdb-tools: Parse Redis dump.rdb files, Analyze Memory, and Export Data to JSON 通过分析RDB文件,扫描出Redis的大key
可以根据自己的精细化需求,全面地分析所有Key的内存占用情况,而且对线上服务无影响。但是因为RDB文件较大时耗时较长。
Redis内置命令
通过Redis内置命令对目标Key进行分析,可以使用风险较低的命令来查询目前Key的字节数、列表长度或成员数量
STRING类型:执行STRLEN命令,返回对应Key的value的字节数。
LIST类型:执行LLEN命令,返回对应Key的列表长度。
HASH类型:执行HLEN命令,返回对应Key的成员数量。
SET类型:执行SCARD命令,返回对应Key的成员数量。
ZSET类型:执行ZCARD命令,返回对应Key的成员数量。
STREAM类型:执行XLEN命令,返回对应Key的成员数量。
注意!!!
DEBUG OBJECT
与MEMORY USAGE
命令在执行时需占用较多资源,且时间复杂度为O(N)
,有阻塞Redis实例的风险,不建议使用。
大Key优化
业务侧 – 根本
业务侧优先考虑避免大key设计,不要什么都往里放,仅仅缓存必要的数据字段
拆分 分片
例如将含有数万成员的一个HASH Key拆分为多个HASH Key,并保证每个Key的成员数量在合理范围。在Redis集群架构中,拆分大Key能对数据分片间的内存平衡起到显著作用
需要考虑下面几个问题:
定量拆分还是动态拆分?定量拆分需要考虑value增长的问题;动态分片就是先按原key读第一个分片,第一个分片的value记录分片的总数,然后再按照shard_num取。
存在部分写问题,比如写入10个分片的数据,只有5个分片写入成功,就会造成数据不一致问题。解决方案有下面几个
引入版本机制
value最前面带上版本号
一个分片不对就算错误,重新加载,容易陷入死循环
- 开发、维护、组装的成本
更换存储方案
换别的没有大key问题的存储
- 持久型kv存储
软件:LSM-Tree
硬件:固态存储
本地缓存,和redis结合实现多级缓存
CDN
数据压缩
golang自带的zip和unzip可以对string类型的数据进行压缩
string类型
考虑使用容器型数据结构替换 string,提高内存利用效率,也能一定程度节省键名空间
合理的清理机制
对过期数据进行定期清理
堆积大量过期数据会造成大Key的产生,例如在HASH数据类型中以增量的形式不断写入大量数据而忽略了数据的时效性。可以通过定时任务的方式对失效数据进行清理。
在清理HASH数据时,建议通过HSCAN命令配合HDEL命令对失效数据进行清理,避免清理大量数据造成Redis阻塞。
对大Key进行清理
直接删除的话容易阻塞,因为Redis是单线程,从而影响到其他线上请求,超时增加的话会导致Redis连接打满,从而引发各种异常问题
解决方案:
选择业务流量低峰期的时候删除
分批定时定量删除:一次删除一部分,防止一次性删除大量数据导致阻塞
hash:hsacn扫描
set:srandmember 随机取数删除
zset:zremrangebyrank 移除指定排名(rank)区间内的所有元素
list:直接 pop 删
- 异步删除:用unlink代码del删除,只是将键与键空间断开连接,实际的删除异步进行,非阻塞,逐步清理