Big Key 有哪些危害?如何治理?
简化版
Big Key 是单个键对应的 value 过大或集合成员过多,不是固定字节阈值。它会造成单线程命令阻塞、网络突发、内存倾斜、复制与迁移变慢,还可能在删除时引发释放阻塞;治理要从发现、停止增长、拆分、渐进迁移和安全删除五步推进。
详细版
Big Key 的判断必须结合类型与访问方式:5MB 字符串、百万成员 Hash,或一次命令返回数十万成员都可能成为问题。危害不只在占内存,而在一次操作把大量 CPU、内存和网络工作集中到一个 Redis 主线程或一个分片上;主从全量/增量复制、AOF 重写、Cluster 槽迁移和故障恢复也会被拖慢。
发现手段包括低峰期运行 redis-cli --bigkeys 做全库渐进扫描、对候选键执行 MEMORY USAGE、STRLEN、HLEN/SCARD/ZCARD/LLEN,以及监控客户端响应大小、慢日志和分片内存倾斜。生产环境不要用 KEYS * 全量扫描,也不要对未知大集合直接执行会返回全部元素的命令。
治理时先限制写入继续膨胀,再按业务维度拆成多个键或将大对象移到对象存储/数据库;迁移采用双写、分批回填、校验和灰度切读。删除大键优先 UNLINK 异步回收,集合也可按批次 HSCAN/SSCAN/ZSCAN 后渐进删除,但仍需监控 lazyfree backlog 与内存峰值。
完整版教学
一、Big Key 的“大”没有统一阈值
Redis 不规定超过多少字节就叫 Big Key,因为风险取决于数据类型、命令复杂度、实例容量、网络和延迟目标。一个 2MB 字符串在离线缓存中可能可接受,在 P99 要求 5ms 的在线链路上就可能过大;一个 Hash 只有 20MB,但若含 100 万字段,遍历和删除仍很危险。
判断时至少看 value 字节数、集合元素数、单次命令处理/返回量和访问频率。Big Key 与 Hot Key 也不同:前者强调单键体积或成员规模,后者强调访问流量;同一个键可以又大又热,风险会叠加。
记忆钩子:Big Key 真正危险的是“把大量工作集中在一次操作和一个分片”,不只是占了多少内存。
二、为什么会阻塞请求和网络
Redis 大部分命令执行仍由主线程串行完成。读取 10MB 字符串需要查找、复制并写入客户端输出缓冲;对百万成员集合执行全量命令还要遍历和序列化。该命令执行期间,后续轻量请求也会排队。
假设接口 QPS 为 5000,某次大键命令阻塞 200ms,期间理论上约有 5000 × 0.2 = 1000 个请求等待。10MB 响应经 100Mbps 链路传输,忽略协议开销也约需 10×8/100 = 0.8 秒,客户端与代理的缓冲同样承压。
大键命令执行 -> Redis 主线程占用 -> 后续命令排队
-> 大响应进入输出缓冲 -> 网络发送变慢
-> 客户端超时重试 -> 压力进一步增加
三、它还会破坏集群均衡与恢复
Cluster 按 slot 分配键,一个键不能被自动拆到多个 slot。即使全局内存平均,某个 4GB 键也只落在一个主节点,形成内存和带宽倾斜。扩容迁移该 slot 时,这个键的数据搬迁会延长迁移窗口,甚至因超时反复失败。
复制也要传递大对象。主从断线重连、AOF rewrite 或 RDB fork 时,Big Key 会增加复制流量、写时复制内存和恢复时间。故障切换后,冷节点首次读取大对象还可能造成突发延迟。
| 环节 | Big Key 的影响 | 应观察的指标 |
|---|---|---|
| 在线命令 | 主线程与输出缓冲被占用 | command latency、slowlog |
| 分片 | 内存/QPS 倾斜 | max shard / avg shard |
| 复制 | 复制流量和 lag 增长 | replication lag、repl buffer |
| 持久化 | fork/COW 与重写压力 | fork time、RSS、rewrite time |
| 迁移恢复 | 单键搬迁时间过长 | slot migration time、失败数 |
四、怎样在生产中安全发现
redis-cli --bigkeys 基于 SCAN 渐进遍历当前键空间,并按数据类型比较长度或成员数;它报告的是扫描期间看到的候选大键,不是按真实内存占用做全局排序。扫描期间键仍可能增删,结果也不是某一时刻的完整快照。对候选键还要使用 MEMORY USAGE key SAMPLES n 估算内存,并用类型对应的长度命令确认成员数。
扫描要限速并避开业务高峰。SCAN 单次调用虽是增量的,完整遍历仍会消耗 CPU 和网络;在大集群里应按节点执行、设置较小 COUNT,并记录游标断点。更成熟的做法是在写入侧记录 value 大小分布,提前拒绝异常增长。
string -> STRLEN + MEMORY USAGE
hash -> HLEN + MEMORY USAGE
set -> SCARD + MEMORY USAGE
zset -> ZCARD + MEMORY USAGE
list -> LLEN + MEMORY USAGE
五、拆分方案必须匹配访问模式
大 Hash 可按业务 ID 哈希分桶,例如 user:profile:{bucket};时间序列可按天/月分键;排行榜可按地区或业务段拆分;大 JSON 应把高频字段独立缓存,低频大对象放对象存储。拆分目标是让常用读写只触达必要分片,而不是机械切成 100 份后每次仍全量读取。
假设百万成员集合拆成 100 桶,每桶约 1 万成员,单桶查询和删除成本显著下降。但若业务每次都要全量聚合 100 桶,读扩散和聚合成本会增加。因此要先统计查询维度,再选择分桶键。
迁移可采用新旧结构双写:先上线兼容代码,批量回填并比较数量/校验和,再按用户或租户灰度切读,最后停止旧写并延迟删除。双写失败要有重试和对账,不能靠一次脚本赌成功。
六、删除大键为什么也会出事故
DEL 不仅删除字典入口,还可能同步释放复杂对象及大量成员,回收工作会阻塞主线程。UNLINK 将实际内存回收交给后台 lazyfree 线程,可显著减少主线程停顿,但内存不会立即下降,后台队列过大时仍有压力。
对于巨大集合,可以先按 SCAN 分批删除成员,控制每批数量和间隔,再删除空键。执行前要确认业务已停止写入,否则边删边长会导致遗漏。删除期间监控内存、CPU、lazyfree pending objects、复制滞后和延迟。
冻结/切断旧键写入 -> 分批迁移与校验 -> 灰度切读
-> UNLINK 或分批删成员 -> 观察后台回收 -> 清理
七、如何建立长期治理
在客户端或缓存 SDK 设置单 value 大小和集合成员增长保护,例如字符串超过 512KB 告警、超过 2MB 拒绝(阈值按业务压测确定)。对批量读取限制元素数量和响应字节,禁止无界 HGETALL、SMEMBERS 进入在线接口。
监控应同时包含 Top Key 内存、Top Key 流量、分片倾斜、客户端输出缓冲、慢命令和迁移耗时。告警发现后先限制增长和大范围读取,再安排迁移,避免在高峰直接删除造成二次故障。
治理完成后用同等数据量压测拆分后的访问模式,并验证扩容、故障切换和清理路径。只验证普通 GET 延迟不足以证明风险消失。
八、常见误区与追问
- 误区:占用超过 10MB 才算 Big Key。 阈值取决于类型、命令、频率和 SLO,小于 10MB 也可能造成阻塞。
- 误区:Big Key 只浪费内存。 它还影响主线程、网络缓冲、复制、持久化、槽迁移和故障恢复。
- 误区:
SCAN完全没有性能影响。 单次增量不阻塞全库,但完整扫描仍消耗 CPU 和网络,必须限速。 - 误区:使用
UNLINK后内存会立刻释放。 它只是把回收移到后台,释放进度和后台队列仍需监控。 - 追问:为什么虚拟节点不能拆散一个 Big Key? 哈希路由以键为最小单位,同一个键始终落在一个 slot/节点。
- 追问:大 Hash 应按什么维度拆? 选择能匹配高频查询且分布均匀的业务字段或哈希桶,避免每次跨所有桶。
- 追问:怎样防止再次产生? 在写入 SDK 加大小与成员数阈值、增长告警和无界命令检查,并纳入代码评审。
九、加强记忆
用“量、堵、斜、迁、删”记 Big Key:单键数据量大,命令会堵主线程,分片出现倾斜,复制迁移变慢,删除回收也可能阻塞。治理顺序是先发现并停止增长,再按访问模式拆分、双写迁移和校验,最后使用 UNLINK 或分批删除安全清理,而不是在线高峰一把删掉。