Redis 集群出问题,往往不是整体 QPS 顶不住,而是某个分片先扛不住。监控大盘上整体 QPS 看起来一切正常,但某个分片的 CPU 跑满,或者某次操作延迟突然飙到几百毫秒。这类故障的根因八成是两类东西,热点 key 和大 key。它们不会在系统层面报错,却悄悄拖垮稳定性,排查时如果不往这个方向想,很容易误判成网络抖动或机器问题。
热点 key 是流量倾斜,单个 key 承载了远超平均的访问量,把它所在的分片打满。大 key 是体积倾斜,单个 key 存的数据太大,处理它时阻塞主线程。两者的共同点是,问题不在总量而在分布,整体资源够用,但分布极度不均。
热点 key,流量倾斜打挂单分片
图:热点 key 把流量集中到一个分片,大盘 QPS 看似正常但单分片 CPU 跑满,连带该分片所有 key 变慢
热点 key 的危害不是访问量大,而是倾斜。一个爆款商品的详情 key,一次大促可能扛整个集群三分之一的读流量。Redis 集群是分片的,这个 key 只存在于某一个分片上,所有访问都打到那一个分片。集群总容量可能还有富余,但那个分片的 CPU 已经跑满,单分片的延迟飙升,连带这个分片上所有其他 key 的访问都变慢。
典型的热点 key 有几类。大促商品或秒杀商品的详情缓存,热搜词条,排行榜的头部数据,某个明星的微博这类突发热点。它们的特点是访问量突然集中,平时正常,某个时间点爆发。
怎么发现热点 key。最直接的是看各分片的 QPS 分布,如果某个分片的 QPS 是其他分片的几倍,基本能确定有热点。Redis 4.0 以上可以用 redis-cli --hotkeys 命令,前提是开启了 LFU 淘汰策略,它能统计出访问频次最高的 key。更通用的做法是在代理层统计,像 Twemproxy、Codis 这类代理都能按 key 维度统计访问频次。业务侧也能预判,大促商品、热搜、排行榜头部天然就是热点,提前就能识别。
热点 key 的应对,分散是核心
图:热点 key 四种分散策略——本地缓存首选、多副本分散读、分桶分散写、读写分离兜底,按一致性强弱选用
对付热点 key,所有手段的思路都是"分散",把集中在一个 key 上的流量摊开。
本地缓存是最直接有效的。热点 key 缓存在应用进程内(Caffeine 或 Guava Cache),读的时候根本不访问 Redis。对爆款商品详情这种容忍几秒不一致的数据,本地缓存能把 Redis 的访问量降一个数量级。代价是本地缓存一致性弱,数据更新有延迟,所以只适合容忍短暂不一致的热点。我做过的人群底座服务,把热点人群数据从 Redis 前置到本地 Caffeine 缓存,命中率从 40% 提到 99%,服务 RT 从 45ms 降到 30ms,上游的 QPS 上限从 1300 提到 2800,这还是没动 Redis 的情况下。
多副本读是另一个思路。把热点 key 复制成多份,比如 product_001 复制成 product_001_1、product_001_2、product_001_3,读的时候随机选一个副本。这样流量被分散到不同的 key 上,配合分片能分散到不同物理节点。
分桶适合写热点。比如秒杀库存,把 1000 件库存拆成 10 个 key 各存 100 件,扣减时随机或轮询选一个桶,写流量也被分散。秒杀系统防超卖用的就是库存分桶这个思路。
读写分离是兜底手段,读流量走从节点,分摊主节点压力。但对热点 key 这种极端倾斜的场景,读写分离的效果有限,因为读副本本身也可能被这个 key 的流量打满。它更适合常规的读多写少场景。
大 key,阻塞主线程的隐形杀手
Redis 处理命令是单线程的(6.0 之后网络 IO 多线程,但命令执行还是单线程)。这意味着处理一个耗时的命令时,主线程被占住,期间所有其他请求都得排队等。大 key 就是那个耗时命令的来源。
大 key 有三类。第一类是大 String,单个 value 几 MB 甚至十几 MB,一次读取要传输和反序列化大量数据。第二类是大集合,Hash、List、Set、ZSet 的成员数达到几万甚至几十万,比如一个存了全网用户头像的 Set。第三类是大 ZSet,排行榜里塞了十几万成员。
大 key 的危害是多方面的。读取慢,一次读几 MB 数据,网络和反序列化耗时长,期间主线程被占。删除阻塞,DEL 一个十万成员的 key,Redis 要逐个释放成员,主线程可能阻塞几秒,这几秒里整个分片不可用,这是实打实的生产事故。内存倾斜,大 key 集中在一个分片,导致这个分片内存占用远超其他分片。网络打满,大 key 的高频读取会吃掉大量带宽。
发现大 key 有现成工具。redis-cli --bigkeys 会对各类数据结构采样统计最大的 key。redis-cli --memkeys(6.0 以上)统计内存占用。更精确的是离线分析 RDB 文件,用 rdb-tools 解析 dump.rdb,能列出每个 key 的精确大小和成员数。监控上,给单个 key 的内存占用和成员数设阈值,String 超过 10KB、集合超过 1 万成员就告警。
大 key 的治理,拆分加异步删除
图:大 key 治理流程——发现采样、按类型拆分存量、用 UNLINK 或分批 HDEL 安全删除、预防靠规范和审查
大 key 的治理分两步,存量怎么处理,增量怎么预防。
存量大 key 能拆就拆。大集合按维度拆成多个 key,用户基础信息一个 key,用户订单列表另一个 key,别把所有数据塞进一个 Hash。大 String 能压缩就压缩,或者只存摘要,原始数据迁到对象存储。完全不适合放 Redis 的数据,比如历史明细,迁到 DB 或 ES。
删除大 key 有安全姿势,绝不能直接 DEL。Redis 4.0 以上用 UNLINK 命令,它把实际的内存释放放到后台线程异步执行,主线程立即返回,不阻塞。老版本没有 UNLINK,就用游标分批删除,HSCAN 取一批成员,HDEL 删掉这批,循环直到清空,每批控制在一两百个成员,避免单次阻塞。
# 大 key 发现redis-cli --bigkeysredis-cli --memkeys# 离线分析 RDBrdb-tools --command memory dump.rdb --bytes 10240 -f bigkey.csv大 key 还会引发连锁故障。缓存击穿,大 key 过期瞬间,重建时 DB 压力骤增。集群故障转移失败,大 key 所在分片故障时,迁移大 key 耗时过长,故障转移超时。主从同步延迟,大 key 的全量同步耗时长,主从不一致窗口拉长。所以大 key 不只是单点慢,它会放大各种边界场景的故障。
预防胜过治理
热点 key 和大 key 最经济的治理是预防,在设计阶段就规避。把单 key 的大小上限写进团队规范,String 不超过 10KB,集合成员数不超过 1 万,写入超限直接报错。代码审查时卡住无限制往集合塞数据的写法,禁止把序列化的大 JSON 整体存成一个 String。监控告警实时发现大 key 和热点 key,定期巡检扫描存量大 key 治理。
这几条里,设计规范和代码审查是源头治理,成本低收益高。等到线上跑出一堆大 key 再治理,迁移和删除都是高风险操作,费时费力还容易出事。我见过最离谱的案例是一个排行榜 ZSet 塞了 200 万成员,每次 ZREVRANGE 都让分片卡顿,治理时只能半夜低峰期分批迁,折腾了一周。把"单 key 上限"写进规范,从一开始就拦住,省心百倍。
直接 DEL 一个十万成员的大集合,主线程会阻塞几秒,这几秒内整个分片的所有请求超时,上层业务大面积报错。这种事故每年都在发生。Redis 4.0 以上必须用 UNLINK 异步删除,老版本用 HSCAN 加 HDEL 分批删。把"禁止 DEL 大 key"写进运维红线。
对付热点 key,本地缓存(Caffeine)效果最直接,根本不碰 Redis,热点自然消失。代价是一致性弱,只适合容忍几秒不一致的热点数据,比如商品信息、配置、字典。强一致的热点(如实时库存)不能放本地缓存,要用多副本或分桶分散。先判断数据的一致性要求,再选分散手段。








