一、什么是大 key
两种判定标准:
- 数据体积大:字符串 value 超过 10kb;集合类 (list/hash/set/zset) 元素总量超 1 万、总大小超 100kb
- 操作耗时久:单次命令遍历大量元素,阻塞主线程
常见大 key 类型
- string:超大文本、序列化全量对象、整表缓存
- hash:单 hash 存储上万条业务数据(如用户全部标签塞一个 hash)
- list:消息队列堆积几十万未消费数据
- zset:排行榜一次性存储全量用户数据
二、大 key 带来的致命问题
redis 是单线程执行命令,所有操作串行,大 key 操作直接阻塞主线程:
- 命令阻塞,服务雪崩 删除大 hash/list、
hgetall、lrange 0 -1、zrange 0 -1会一次性遍历数万元素,cpu 打满,后续所有读写请求排队超时。 - 内存瞬间抖动,触发 oom 删除大 key 时,redis 会一次性回收大量内存,内存分配器出现卡顿;若开启持久化,rdb/aof 重写时拷贝大 key,内存占用翻倍,容器 / 服务器内存溢出宕机。
- 集群槽位迁移卡死 redis cluster 集群迁移 slot 时,会完整拷贝 key,大 key 迁移耗时数十秒,集群迁移超时失败,槽位不稳定。
- 网络流量突增 客户端一次性读取超大 value,网卡瞬间打满,挤压其他正常请求。
三、如何定位大 key
1. 线上在线扫描(不阻塞,推荐)
redis 4.0+ 内置命令,分批次扫描,不会阻塞主线程
# 扫描整个库,输出top大key redis-cli --bigkeys
输出会区分 string/list/hash/zset,给出每个类型最大 key、元素数量、占用空间。
2. 精准扫描指定库 / 筛选 key 前缀
# 选择db1,匹配user开头的key,分段扫描 redis-cli -n 1 --bigkeys --pattern "user*"
3. 离线 rdb 分析(超大集群避免线上扫描)
rdb-tools 工具解析 rdb 文件,导出全部 key 大小报表,适合生产集群。
4. 监控实时识别
监控指标:
- 慢查询日志(slowlog):大量耗时超过 100ms 的命令,大概率操作大 key
- redis 内存监控:内存毛刺式上涨下跌
- 集群迁移 slot 耗时指标
四、大 key 优化方案(分场景)
场景 1:string 大 key(超长字符串)
问题:缓存全量对象、大文本、完整列表数据
优化 1:分片拆分(推荐)
将单个大 key 拆分成多个小 key 例:存储 id=1000 用户详情
# 原大key(禁止)
user_info:1000 = {id:1000,name:xx,addr:xx,tag:[...]}
# 拆分多个小string
user_info:1000:name = 张三
user_info:1000:addr = 北京市xxx
# 列表标签单独拆分hash存储
user_tag:1000 hash优化 2:压缩序列化
使用snappy/gzip压缩 value,大幅降低体积;避免 java 原生序列化(体积极大),改用 protostuff/json 压缩。
优化 3:分页存储,不缓存全量
列表数据不要一次性存入,按分页 key 存储:
goods_list:page1 goods_list:page2
场景 2:hash 大 key(最常见业务坑)
问题:单 hash 存上万条 field,hgetall 直接阻塞
方案 1:hash 分片(hash 拆分)
原 key:product:info 存储十万商品信息 分片拆分 n 个 hash:
product:info:0 product:info:1 # 分片规则:商品id % 10
每个 hash 仅几千条 field,hgetall 无压力。
方案 2:禁止 hgetall,使用 hscan 迭代遍历
业务代码绝对不要全量读取 hash,使用游标分批拉取,不会阻塞 redis:
# 游标0开始,每次取100条 hscan product:info 0 count 100
方案 3:冷热分离
高频访问字段单独拆分小 hash,低频大字段存入独立 key。
场景 3:list 大 key(消息队列堆积)
问题:生产者速度远大于消费者,list 堆积几十万数据,lrange 0 -1、批量删除阻塞
优化 1:分片队列
拆分多个 list,生产者轮询写入,多消费者并行消费,分散数据量
msg_queue:0 msg_queue:1 msg_queue:2
优化 2:限制队列长度,设置丢弃策略
业务允许的前提下,队列超过阈值时丢弃旧数据,避免无限堆积。
优化 3:改用专业队列(redis stream)
stream 支持消费组、ack 确认,不会出现 list 堆积后大量删除阻塞问题,替代 list 做消息队列。
场景 4:zset/set 大 key(排行榜、标签集合)
- 排行榜分片:按区间拆分多个 zset(0-1000、1000-2000)
- 禁止
zrange 0 -1全量拉取,分页zrange start end - 超大标签集合拆分多 set,求交集时客户端合并结果
五、大 key 安全删除方案(重中之重)
直接 del big_key 会阻塞主线程,分两种安全删除方式:
1. 集合类大 key(hash/list/set/zset):分段删除
循环分批删除少量元素,每次操作耗时极短,不阻塞
# hash分批删除field hscan big_hash 0 count 100 hdel big_hash field1 field2 ... # list从尾部批量弹出 lpop big_list 50
2. string 超大 key:异步非阻塞删除(redis 6+)
使用unlink替代del:
del:同步删除,立即释放内存,阻塞主线程unlink:异步删除,主线程仅标记 key,后台子线程回收内存,无阻塞
unlink big_string_key
六、线上预防规范(开发约束)
- 编码规范:
- 禁止单 hash/list 存储超过 1000 条元素
- 禁止使用
hgetall/lrange 0 -1/zrange 0 -1全量读取 - 列表、排行榜强制分页存储、分页查询
- 监控告警:
- 定时执行
--bigkeys脚本,超过阈值触发告警 - 慢查询日志持续监控,捕获大 key 耗时命令
- 定时执行
- 集群规范: redis cluster 环境严格规避大 key,迁移 slot 极易集群故障;分片是集群最优解。
- 过期策略: 大 key 不要设置统一过期时间,打散过期时间,避免大批量同时过期产生内存雪崩。
七、补充:大 key 与热 key 区别
很多人混淆两者:
- 大 key:数据体积大,问题是阻塞、内存、迁移卡顿
- 热 key:访问 qps 极高(每秒几万次请求),问题是 cpu、缓存击穿、集群流量倾斜 优化方案完全独立,分开处理。
以上就是redis大key问题的完整解决方案的详细内容,更多关于redis大key问题解决的资料请关注代码网其它相关文章!
发表评论