Redis大key“根治”指南:拆分策略与数据结构选型

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

凌晨三点,收到告警:Redis延迟飙升至500ms,大量请求超时。紧急排查后发现,罪魁祸首是一个200MB的大Key。你战战兢兢地想删掉它,结果DEL命令又把Redis卡了3秒。

不敢读、不敢写、不敢删------这就是Redis大Key的"死亡循环"。

上周讲了"Redis大key怎么排查",今天讲"排查出来之后怎么治"。大Key的本质危害不是"数据量大",而是Redis是单线程的,处理大Key就像让一个人一次性扛200斤重物,不仅慢,还会堵住后面所有人的路

大Key的优化本质 = 把单次大块内存操作拆成多次小块操作。总内存不变,但风险下降上百倍。

今天从三种大Key类型出发,拆解拆分策略和数据结构选型,一次性把大Key根治这件事讲清楚。

一、先搞清楚:大Key有三种类型

类型一:String大Value

单个String类型的Key存储的Value过大,比如存了一整个序列化对象、一个大JSON、一张图片的Base64。生产环境建议单个String不超过10KB。

类型二:集合类大Key

Hash、Set、ZSet、List中存储了过多的元素(以万为单位)。一个Hash有100万字段,一个Set有500万成员,一个List有几百万条消息。

类型三:Key数量爆炸

一个集群存储了上亿个Key,Key本身过多也带来了更多的空间占用。

核心认知:大Key真正可怕的不是占内存,而是单次操作太大。Redis是单线程模型,同一时间只能干一件事,大Key会让主线程长时间独占,其他命令全部排队等待。

二、拆分策略:不同类型怎么拆?

1. String类型

场景A:该对象需要整存整取

将一个String拆成多个Key-Value,使用MGET批量获取。分拆的意义在于将单次操作的压力分摊到多个Redis实例中,降低对单个Redis的I/O影响。

场景B:该对象只需要存取部分数据

可以像场景A一样分拆成几个Key-Value,也可以存在一个Hash中,每个Field代表一个具体属性。使用HGETHMGET获取部分Value,使用HSETHMSET更新部分属性。

参考阈值:String超过10MB必须拆分,建议512KB内切片。

2. Hash类型

核心思路:固定桶数量,本地计算路由

固定一个桶的数量(比如10000),每次存取时先在本地计算Field的Hash值,模除桶数量,确定该Field落在哪个Key上。

bash 复制代码
# 伪代码示例
BUCKET_COUNT = 10000
def get_hash_key(base_key, field):
    bucket = hash(field) % BUCKET_COUNT
    return f"{base_key}:{bucket}"

原先的hget(hashKey, field)变成hget(newHashKey, field)

参考阈值 :Hash字段数超过5000或总大小超过1MB时建议拆分。单次HGETALL操作耗时从几十ms降至亚ms级。

3. Set / ZSet / List类型

同理,将一个大集合按规则拆成多个小集合。

Set拆分:按业务维度做Sharding,将一个大Set按逻辑切分成多个小Set。

ZSet拆分:如果是为了排序场景,可以考虑改用ZSET替代。按时间或分数范围分桶。

List拆分 :需要保证LPOP的数据确实是最早Push进去的,需要在Key的拼接上做一些工作,比如按时间分拆。

三、渐进式拆分:如何在不影响业务的情况下完成拆分?

直接拆大Key风险极高------可能造成数据不一致、业务报错、甚至Redis阻塞。正确的做法是渐进式拆分

第一步:双写阶段

新的写入同时写到旧Key和新Key。应用层代码改造,写入时同时更新新旧两套结构。

第二步:双读阶段

读取时先读新Key,如果不存在再读旧Key。确保数据"读得到"。

第三步:数据迁移阶段

后台任务逐步将旧Key中的数据迁移到新Key。使用HSCANSSCANZSCAN分批扫描,每批处理少量数据。不要一次全量迁移。

第四步:切读阶段

确认新Key数据完整后,将读取逻辑完全切换到新Key。

第五步:下线阶段

确认新Key稳定运行一段时间后,逐步下线旧Key。使用UNLINK命令异步删除,不要用DEL

关键原则:全程利用Redis的非阻塞命令和异步特性,最大程度降低对现有业务的影响。

四、删除大Key的正确方式

禁止使用DEL直接删除大Key ------Redis是单线程的,DEL会一次性释放大块内存,主线程被卡住,可能造成Redis阻塞甚至主备倒换。

Redis 4.0及以上版本使用UNLINK命令,异步删除,不阻塞主线程。

bash 复制代码
# 正确删除方式
redis-cli UNLINK big_key_name

# 错误删除方式(会阻塞)
redis-cli DEL big_key_name

删除大Key的正确姿势

  1. 先用RENAME key tmp:key将Key重命名(让应用无法再访问到它)

  2. 再用UNLINK异步删除

  3. 或使用EXPIRE key <seconds>设置过期时间,让Redis自动清理

五、数据结构选型:从源头杜绝大Key

预防大Key比治理大Key更重要。从数据结构选型阶段就开始防患于未然。

数据结构 适用场景 大Key风险 预防建议
String 简单键值、缓存对象 序列化后体积大 压缩后存储,单个<10KB
Hash 对象属性存储 字段过多 按业务维度拆分,每Hash<5000字段
Set 去重集合 成员过多 按业务或时间分桶,每Set<1万成员
ZSet 排行榜、延迟队列 成员过多 按时间或分数范围分桶
List 消息队列、历史记录 元素过多 按时间分片,定期清理过期数据

选型原则

  • 不要把所有数据都塞进一个Key里

  • 集合类数据要控制大小,必要时按用户、时间、业务类型拆分

  • 读取时不要一次取完,能分页就分页,能分批就分批

  • 对已经存在的大Key,要先摸清楚来源,再安排低峰期处理,避免直接在线上粗暴删除

六、总结

大Key治理不是一次性的任务,而是需要纳入日常运维体系的工作。

根治大Key的四步法

步骤 手段 目标
发现 redis-cli --bigkeysMEMORY USAGE、慢查询、RDB分析 能看见
拆分 按业务维度、哈希取模、时间范围拆分 把大拆小
迁移 渐进式拆分、双写双读、分批迁移 平滑过渡
删除 使用UNLINK异步删除 安全下线

大Key优化的核心:把单次大块内存操作拆成多次小块操作。总内存不变,但风险下降上百倍。拆分后单次阻塞从几十毫秒降至亚毫秒级,内存操作更平滑,网络流量更平稳。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~

相关推荐
老郑聊AI业财智造1 小时前
Spring AI 技术架构与源码分析
java·人工智能·后端·spring·架构·软件工程
程序员与背包客_CoderZ1 小时前
高性能分布式KV存储引擎RocksDB入门与C/C++编码实战
c语言·开发语言·数据库·c++·分布式·分布式数据库·rocksdb
进击的_鹏1 小时前
从零开始的 Redis 学习
服务器·数据库·c++·redis·缓存
隔窗听雨眠1 小时前
当KES遇到多租户:金仓数据库多租户架构的隔离实践与部署指南
数据库·架构
JOker_Chu_1 小时前
知识图谱详解:从图结构、关系建模到查询与应用
数据库·知识图谱·database·neo4j
tryCbest2 小时前
搭建企业知识库之Milvus 向量数据库
数据库·milvus
czhc11400756632 小时前
2026-08-24 博客:几个让你少踩坑的概念
数据库
zzz_23682 小时前
【AI代码测评】OpenCodeReview 架构拆解:确定性工程与 Agent 如何分工
人工智能·架构·agent·agent测评·harnes
ACP广源盛139246256733 小时前
Qwen3.8‑2.4T 开源落地@ACP#国产 Serdes 长距离视频传输芯片 GSV5800 在私有化 AI 服务中的价值与应用场景
大数据·数据库·人工智能·嵌入式硬件·矩阵·开源·音视频