主题
大 Key 问题
一句话:大 Key 是单个 key 的内存或元素数远超正常量级;危害不在「存得多」,而在网络传输、主线程阻塞和集群内存倾斜。治本是拆分,删除用
UNLINK。
适用版本:Redis 7.x / 8.x。UNLINK 自 4.0 起提供。
为什么需要
Redis 命令在主线程执行,一个 key 的元素越多,遍历、序列化、释放占用的时间越长。单机看起来只是「这条命令慢一点」,在集群里还会变成「这个节点内存和带宽被打满,其他节点闲着」。大 Key 没有官方硬标准,但它是容量规划和故障排查里绕不开的一类问题。
核心机制
判定:经验阈值
| 类型 | 经验阈值 | 说明 |
|---|---|---|
| String | value > 10 KB 关注,> 1 MB 危险 | 网络传输和序列化成本随体积线性增长 |
| Hash / List / Set / ZSet | 元素数 > 5000 | 遍历、删除、迁移都变慢 |
| 通用 | MEMORY USAGE > 几 MB | 跨类型参考 |
这些是行业经验值,不是 Redis 的硬限制。一个 1 KB 的 key 如果是热点,也会造成问题,但那属于热点 Key,见 11-热点Key问题。
四个危害
- 内存倾斜:Cluster 按 slot 分片,一个大 key 全落在一个节点,该节点先 OOM/淘汰,其他节点还有余量。
- 网络阻塞:一次
HGETALL返回几 MB,占满出方向带宽,同节点其他请求排队。 - 慢查询:
HGETALL、LRANGE 0 -1、SMEMBERS是 O(N),遍历 + RESP 序列化 + 写 socket 都在主线程完成。 - 删除高危:
DEL同步释放内存,删几百万元素可能卡住主线程数秒;UNLINK只断开引用,把释放交给后台线程。
为什么 DEL 比 UNLINK 危险
DEL 在主线程遍历整个数据结构并逐个 free();元素越多,free() 次数越多,耗时越不可控。UNLINK 在主线程只做 O(1) 的引用摘除,真正的内存回收由 BIO 线程完成。4.0 之前没有 UNLINK,只能用 SCAN + 分批 DEL。
最小上手
bash
# 找出各类型最大的 key(内部用 SCAN,但仍会给实例带来压力)
redis-cli --bigkeys
# 按内存占用找 top key(4.0+,内部用 MEMORY USAGE)
redis-cli --memkeys
# 精确看单个 key
redis-cli MEMORY USAGE user:profile
redis-cli MEMORY USAGE user:profile SAMPLES 0 # 0 表示全量计算,大 key 上很慢
# 安全删除
redis-cli UNLINK user:profile批量巡检时用 SCAN,不要用 KEYS:
bash
redis-cli --scan --pattern 'user:*' | head -20 | while read k; do
echo "$k $(redis-cli MEMORY USAGE "$k")"
done生产动作
- 发现工具都有局限:
--bigkeys按元素数/字节数排序,String 报 bytes、集合报 items,不直接反映内存;--memkeys依赖MEMORY USAGE,对嵌套结构默认只采样 5 个元素估算。精确值用SAMPLES 0,但会遍历整个 key。 - 执行时机:
--bigkeys/--memkeys会全库SCAN,在低峰执行,或从从节点/离线副本上跑。 - 写入前拦截:应用层计算 value 大小或集合长度,超过阈值拒绝写入或告警,比事后清理便宜。
- 拆分:Hash 按 field 哈希分片,List 按时间/序号分段,Set 按成员取模分片,ZSet 按 score 范围分段;查询逻辑要跟着改。
- 压缩:大 String 用 Snappy/LZ4/Zstd 压缩,适合访问频率低的冷数据;热点数据压缩后解压开销可能超过收益。
- 安全删除:优先
UNLINK;开lazyfree-lazy-expire yes、lazyfree-lazy-eviction yes、lazyfree-lazy-server-del yes,但客户端显式DEL仍是同步的。 - 监控:
SLOWLOG GET、INFO commandstats、集群各节点内存/CPU 对比;迁移大 Key 时关注MIGRATE耗时。
边界
- 阈值是经验值:不同业务对 10 KB / 5000 元素的容忍度不同,关键是看延迟和内存曲线,不是背数字。
--bigkeys会漏:它只看元素数/字节数,不反映实际内存;一个含 1000 个 10 KB 字段的 Hash 可能元素数不超标,但内存很大。MEMORY USAGE默认是估算:SAMPLES默认 5,嵌套结构越多误差越大;SAMPLES 0精确但慢。- 拆分有代价:多 key 后跨分片查询、事务、聚合都要重写,可能引入一致性问题。
UNLINK不是免费:后台释放任务堆积会占用 CPU 和内存,需监控延迟释放队列。- 大 Key 迁移慢:
MIGRATE是DUMP + RESTORE,大 key 会阻塞源节点和目标节点,扩缩容要避开。
讲清楚
大 Key 的问题不是「占内存」这么简单,而是它把 O(N) 的操作放到了单线程的主线程上:一次
HGETALL要遍历并序列化几 MB,一次DEL要释放几十万个对象,整个实例都跟着停。集群里还会因为一个 slot 落在大 key 上,造成单节点内存和带宽倾斜。处理顺序是:先用
--bigkeys/--memkeys/MEMORY USAGE找到它,能拆就拆,不能拆就压缩或改类型;删除一定用UNLINK。工具的结果都是估算或按元素数排序,最终要结合延迟和内存曲线判断。