Redis中的bigkey的介绍及影响
目录
1、定义分类
1.1、介绍
1.2、分类
1.3、应用场景
1.4、删除BigKey
2、作用影响
2.1、Redis阻塞
2.2、内存碎片化:
2.3、网络带宽阻塞:
2.4、持久化风险
2.5、集群运维困境
3、定位BigKey命令
3.1、redis-cli命令
3.2、SCAN命令
3.3、使用 RdbTools
4、优化方案
4.1、合理设计key
4.2、拆分大key
4.3、设置合理的过期时间
4.4、启用内存淘汰策略
4.5、使用数据压缩
4.6.、渐进式删除
4.7、数据迁移
4.8、优化数据结构
4.9、监控与预防
前言
Redis中的大key问题指的是某个key对应的value值所占的内存空间比较大,这会导致Redis的性能下降、内存不足、数据不均衡以及主从同步延迟等一系列问题。
大key的具体定义并不固定,通常认为字符串类型的key对应的value值占用空间大于1MB,或者集合类型的key元素数量超过1万个,就视为大key。
与热key不同的是:
把访问频率高的Key,称为热Key。
比如突然有几十万的请求去访问redis中某个特定的Key,那么这样会造成redis服务器短时间流量过于集中,很可能导致redis的服务器宕机。
1、定义分类
什么是BigKey?为什么它成为Redis的性能杀手?
1.1、介绍
BigKey指在Redis中key对应的value占用内存或元素数量超出业务合理阈值的键值对。
1.2、分类
分为字符串类型和集合类型。
1、String类型:
String 类型的值大于 10 KB(value最大值为512MB);
2、集合类型(Hash/List/Set/ZSet):
元素数量超过 5000个(阿里云规范建议值)或内存达百万级(最大存放2^32-1个元素)。
1.3、应用场景
1、redis数据结构使用不恰当
将Redis用在并不适合其能力的场景,造成Key的value过大,如使用String类型的Key存放大体积二进制文件型数据。
复合场景:如未分片的用户行为日志、商品详情页缓存(含图文描述+评价列表)。
案例:
某电商平台将单个商品的完整详情(包含20个字段的JSON数据)以String类型存储,导致每个Key大小超过2MB,最终引发查询延迟激增。
2、未及时清理垃圾数据
没有对无效数据进行定期清理,造成如HASH类型Key中的成员持续不断的增加。即一直往value塞数据,却没有删除机制,value只会越来越大。
3、对业务中key预估不准确
业务上线前规划设计考虑不足没有对Key中的成员进行合理的拆分,造成个别Key中的成员数量过多。
4、明星、网红的粉丝列表、某条热点新闻的评论列表
假设我们使用List数据结构保存某个明星/网红的粉丝,或者保存热点新闻的评论列表,因为粉丝数量巨大,热点新闻因为点击率、评论数会很多,这样List集合中存放的元素就会很多,可能导致value过大,进而产生Big Key问题。
1.4、删除BigKey
1、分批次删除
如果是集合类型,则遍历BigKey的元素,先逐个删除子元素,最后删除BigKey。
2、异步删除
从 Redis 4.0 版本开始,可以采用异步删除法,用 unlink 命令代替 del 来删除。这样 Redis 会将这个 key 放入到一个异步线程中进行删除,这样不会阻塞主线程。
2、作用影响
可能带来的网络阻塞、内存占用和性能影响。
如下图所示:
2.1、Redis阻塞
关于redis的线程模型,可参考:关于多线程的Redis模型_redis线程模型-CSDN博客https://blog.csdn.net/weixin_50055999/article/details/147977886?spm=1011.2415.3001.5331
单线程阻塞:执行HGETALL或DEL耗时超过10ms即会影响其他请求。
极端案例:某社交平台删除一个包含10万成员的ZSet时,主线程阻塞达2.3秒,触发服务熔断
内存与网络双重压力。
2.2、内存碎片化:
每个key在存放过程中,会先进行hash函数取模去判断分区,具体可参考:
深入了解redis的哈希槽的知识_redis 哈希槽-CSDN博客https://blog.csdn.net/weixin_50055999/article/details/148104826?spm=1011.2415.3001.5331
BigKey所在的Redis实例内存使用率远超其他实例,无法使数据分片的内存资源达到均衡。
2.3、网络带宽阻塞:
1MB的Key每秒访问1000次将产生1GB/s流量,千兆网卡直接打满。
2.4、持久化风险
关于redis的持久化,可参考:对Redis组件的深入探讨_redis 磁盘 内存-CSDN博客https://blog.csdn.net/weixin_50055999/article/details/147757520?spm=1011.2415.3001.5331
AOF追加延迟:Always策略下写入大Key导致fsync耗时激增,主线程卡顿
RDB生成失败:某游戏公司因一个50MB的排行榜Key导致bgsave超时,主从同步中断
1、对AOF日志的影响
Redis 提供了 3 种 AOF 日志写回硬盘的策略,分别是:
1、Always:
「总是」,所以它的意思是每次写操作命令执行完后,同步将 AOF 日志数据写回硬盘;
2、Everysec:
「每秒」,所以它的意思是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘;
3、No:
意味着不由 Redis 控制写回硬盘的时机,转交给操作系统控制写回的时机,也就是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。
总结:
Always 策略就是每次写入 AOF 文件数据后,就执行 fsync() 函数;
Everysec 策略就会创建一个异步任务来执行 fsync() 函数;
No 策略就是永不执行 fsync() 函数;
当 AOF 写回策略配置了 Always 策略,如果写入是一个大 Key,主线程在执行 fsync() 函数的时候,阻塞的时间会比较久,因为当写入的数据量很大的时候,数据同步到硬盘这个过程是很耗时的。
当使用 Everysec 策略的时候,由于是异步执行 fsync() 函数,所以大 Key 持久化的过程(数据同步磁盘)不会影响主线程。
当使用 No 策略的时候,由于永不执行 fsync() 函数,所以大 Key 持久化的过程不会影响主线程。
2、对AOF重写和RDB的影响
AOF 重写机制和 RDB 快照(bgsave 命令)的过程,都会分别通过 fork() 函数创建一个子进程来处理任务。会有两个阶段会导致阻塞父进程(主线程):
创建子进程的途中,由于要复制父进程的页表等数据结构,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长;
创建完子进程后,如果父进程修改了共享数据中的大 Key,就会发生写时复制,这期间会拷贝物理内存,由于大 Key 占用的物理内存会很大,那么在复制物理内存这一过程,就会比较耗时,所以有可能会阻塞父进程。
2.5、集群运维困境
关于redis的部署,可参考:谈谈Redis缓存和数据库一致性的处理方案_redis缓存如何与数据库保持一致-CSDN博客https://blog.csdn.net/weixin_50055999/article/details/147782383?spm=1011.2415.3001.5331
数据倾斜:某个分片存储3个10GB的Key,其他节点内存利用率不足20%
扩容失效:迁移BigKey时因超时触发slot迁移重试循环
3、定位BigKey命令
定位bigkey的方案如下:
3.1、redis-cli命令
示例:
redis-cli -h 127.0.0.1 -p6379 -a "password" -- bigkeys# 扫描耗时型操作,建议在从节点执行
redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 0.1# 输出示例
[00.00%] Biggest string found 'user:1024:info' has 12 bytes
[12.34%] Biggest hash found 'product:8888:spec' has 10086 fields
注意事项:
最好选择在从节点上执行该命令。因为主节点上执行时,会阻塞主节点;
如果没有从节点,那么可以选择在 Redis 实例业务压力的低峰阶段进行扫描查询,以免影响到实例的正常运行;或者可以使用 -i 参数控制扫描间隔,避免长时间扫描降低 Redis 实例的性能。
不足之处:
只能返回每种类型中最大的那个 bigkey,无法得到大小排在前 N 位的 bigkey;对于集合类型来说,只统计集合元素个数的多少,而不是实际占用的内存量。
但是,一个集合中的元素个数多,并不一定占用的内存就多。因为,有可能每个元素占用的内存很小,这样的话,即使元素个数有很多,总内存开销也不大;
3.2、SCAN命令
使用 SCAN 命令对数据库扫描,然后用 TYPE 命令获取返回的每一个 key 的类型。
对于 String 类型,可以直接使用 STRLEN 命令获取字符串的长度,也就是占用的内存空间字节数。
对于集合类型来说,有两种方法可以获得它占用的内存大小:
1、如果能够预先从业务层知道集合元素的平均大小,那么,可以使用下面的命令获取集合元素的个数,然后乘以集合元素的平均大小,这样就能获得集合占用的内存大小了。List 类型:LLEN 命令;Hash 类型:HLEN 命令;Set 类型:SCARD 命令;Sorted Set 类型:ZCARD 命令;
2、如果不能提前知道写入集合的元素大小,可以使用 MEMORY USAGE 命令(需要 Redis 4.0 及以上版本),查询一个键值对占用的内存空间。
示例如下:
public List<Map.Entry<String, Long>> findBigKeys(int threshold) {List<Map.Entry<String, Long>> bigKeys = new ArrayList<>();Cursor<byte[]> cursor = redisTemplate.execute((RedisCallback<Cursor<byte[]>>) connection -> connection.scan(ScanOptions.scanOptions().count(100).build()));while (cursor.hasNext()) {byte[] keyBytes = cursor.next();String key = new String(keyBytes);DataType type = redisTemplate.type(key);long size = 0;switch (type) {case STRING:size = redisTemplate.opsForValue().size(key);break;case HASH:size = redisTemplate.opsForHash().size(key);break;// 其他类型处理...}if (size > threshold) {bigKeys.add(new AbstractMap.SimpleEntry<>(key, size));}}return bigKeys;
}
3.3、使用 RdbTools
使用 RdbTools 第三方开源工具,可以用来解析 Redis 快照(RDB)文件,找到其中的大 key。
比如,下面这条命令,将大于 10 kb 的 key 输出到一个表格文件。
rdb dump.rdb -c memory --bytes 10240 -f redis.csv# 使用rdb-tools分析
rdb -c memory dump.rdb --bytes 10240 > bigkeys.csv# 输出示例
database,type,key,size_in_bytes,encoding,num_elements,len_largest_element
0,hash,user:1024:tags,1048576,hashtable,50000,128
4、优化方案
示例如下:
4.1、合理设计key
分析为何会产生大key,并根据业务场景考虑是否可以通过更好的设计来避免大key的产生。
4.2、拆分大key
如果大key是不可避免的,尝试将其拆分成更多的小key来分散数据。例如,对于列表、集合和有序集合,可以通过散列(hashing)某个属性,把它们分散到不同的小key中。
对于哈希表,可以使用一致性哈希等算法将大哈希表拆分成多个小哈希表。
4.3、设置合理的过期时间
为每个key设置过期时间,并设置合理的过期时间,以便在数据失效后自动清理,避免长时间累积的大key问题。
4.4、启用内存淘汰策略
启用Redis的内存淘汰策略,如LRU(Least Recently Used,最近最少使用)或者LFU,以便在内存不足时自动淘汰最近最少使用的数据,防止大key长时间占用内存。
4.5、使用数据压缩
对于String类型的大key,可以使用压缩算法(如LZF、QUICKLZ、GZIP等)减少value的大小。
4.6.、渐进式删除
如果要删除大key,为了避免一次性删除所带来的长时间阻塞,可以使用Redis的HSCAN、SSCAN、ZSCAN和SCAN命令,配合DEL命令对大key进行渐进式删除。
4.7、数据迁移
如果单个Redis实例无法处理大key问题,可以考虑将数据迁移到使用集群,以此来分散负载和存储。
4.8、优化数据结构
优化数据结构可能是处理大key最有效的方法。如果不必使用哈希表、列表、集合或有序集合的全部特性,可以考虑使用更简单的数据结构来替代。
4.9、监控与预防
定期监控Redis实例的内存使用情况和各种key的大小,能够帮助及时发现并处理大key问题。
在开发和部署Redis系统时充分考虑大key问题,并采取相应的措施来预防和避免这些问题的出现。
总结
通过全流程的预防、检测、处理体系建设,结合智能化的监控预警,可有效应对 BigKey 挑战,保障 Redis 高性能服务能力。
参考文章:
1、Redis大Key问题全解析:从原理到实战的深度解决方案_redis bigkey-CSDN博客https://blog.csdn.net/weixin_43674738/article/details/146159699?ops_request_misc=%257B%2522request%255Fid%2522%253A%252216eea5efdf0c25c283e92712b23989c5%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=16eea5efdf0c25c283e92712b23989c5&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-146159699-null-null.142^v102^pc_search_result_base1&utm_term=redis%20bigkey&spm=1018.2226.3001.4187
2、Redis中的BigKey_redis bigkey-CSDN博客https://blog.csdn.net/m0_74267125/article/details/137795444?ops_request_misc=%257B%2522request%255Fid%2522%253A%252216eea5efdf0c25c283e92712b23989c5%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=16eea5efdf0c25c283e92712b23989c5&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-137795444-null-null.142^v102^pc_search_result_base1&utm_term=redis%20bigkey&spm=1018.2226.3001.4187
3、Redis的热key以及Big(大)key是什么?如何解决Redis的热key以及Big(大)key问题?_redis大key和热key问题及处理-CSDN博客https://blog.csdn.net/Heyi3416/article/details/141223393?ops_request_misc=&request_id=&biz_id=102&utm_term=redis%20bigkey&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-2-141223393.142^v102^pc_search_result_base1&spm=1018.2226.3001.4187