Redis 热 Key 与大 Key:排查方法、风险与解决方案
热 Key 和大 Key 是生产环境 Redis 最常见的两大性能杀手。它们导致负载不均、内存飙升,甚至可能让你的 Redis 实例崩溃。本指南教你如何发现和修复它们。
什么是热 Key?
热 Key 是接收了不成比例的高请求量的 Key。在 Redis Cluster 中,对一个 Key 的所有请求都发往同一个分片。如果一个 Key 的流量是其他 Key 的 100 倍,那个分片就成了瓶颈。
常见热 Key 场景
- 一个爆款商品页面缓存在 Redis 中。
- 一个全局配置 Key 被每个请求读取。
- 一个热门 API 端点的限流 Key。
- 一个机器人发起数千次请求的会话 Key。
什么是大 Key?
大 Key 是值异常大的 Key——要么是巨大的字符串,要么是包含数百万元素的集合(Hash、List、Set、ZSET)。
常见大 Key 场景
- 一个有 100 万字段的 Hash(如把所有用户存在一个 Hash 里)。
- 一个有 1000 万元素的 List(如无界日志)。
- 一个有 50 万成员的 Set(如某个大 V 的所有粉丝)。
- 一个 100MB 的 String 值(如序列化的报表)。
检测热 Key
方法一:redis-cli --hotkeys
redis-cli --hotkeys
这利用了 LFU(最不经常使用)淘汰策略的访问频率计数器。需要 maxmemory-policy 设置为 LFU 变体。
方法二:MONITOR 命令
MONITOR
实时捕获所有命令。解析输出找到频繁访问的 Key。
警告:
MONITOR有显著的性能开销。在生产环境中短暂使用。
方法三:OBJECT FREQ
如果启用了 LFU 策略:
SET popular:item "data"
OBJECT FREQ popular:item
返回 Key 的访问频率计数器。
方法四:应用层追踪
在应用中记录 Key 访问模式并聚合。这是识别热 Key 最可靠的方法。
检测大 Key
方法一:redis-cli --bigkeys
redis-cli --bigkeys
扫描整个键空间,报告每种类型最大的 Key。可以安全地在生产环境运行(内部使用 SCAN)。
方法二:MEMORY USAGE
SET small:key "hello"
MEMORY USAGE small:key
HSET big:hash f1 "v1" f2 "v2" f3 "v3" f4 "v4" f5 "v5"
MEMORY USAGE big:hash
返回精确的内存消耗(字节)。
方法三:DEBUG OBJECT
SET mykey "hello world"
DEBUG OBJECT mykey
显示编码、序列化长度和其他内部细节。
方法四:集合大小命令
HSET user:hash f1 "v1" f2 "v2" f3 "v3"
HLEN user:hash
RPUSH mylist "a" "b" "c" "d" "e"
LLEN mylist
SADD myset "a" "b" "c"
SCARD myset
ZADD myzset 1 "a" 2 "b" 3 "c"
ZCARD myzset
定期检查集合大小。超过阈值时告警。
风险分析
热 Key 风险
| 风险 | 影响 |
|---|---|
| 单分片过载 | 一个节点 CPU 100%,其他空闲 |
| 延迟增加 | 该 Key 的所有请求排队 |
| 集群不均衡 | 内存和 CPU 分布不均 |
| 级联故障 | 过载分片影响其上所有 Key |
大 Key 风险
| 风险 | 影响 |
|---|---|
| 操作缓慢 | 对 100 万字段的 Hash 执行 HGETALL 阻塞 Redis 数秒 |
| 内存飙升 | 突然分配大块内存 |
| DEL 缓慢 | 删除大 Key 阻塞 Redis(改用 UNLINK) |
| 复制延迟 | 大 Key 变更产生大量复制负载 |
| 备份问题 | RDB/AOF 操作变慢 |
热 Key 解决方案
方案一:本地缓存(L1)
在应用内存中缓存热 Key。大幅减少 Redis 负载:
SET config:global '{"feature_flags":{"dark_mode":true}}' EX 60
GET config:global
你的应用本地缓存 5 秒。每秒 1000 个请求变成每 5 秒 1 次 Redis 读取。
方案二:读副本
将读流量路由到副本。主节点只处理写入。
方案三:Key 拆分
将一个热 Key 拆分为 N 个子 Key。分散读取:
SET hotkey:product:2001:0 '{"name":"Widget","price":9.99}'
SET hotkey:product:2001:1 '{"name":"Widget","price":9.99}'
SET hotkey:product:2001:2 '{"name":"Widget","price":9.99}'
应用从 hotkey:product:2001:{hash(request_id) % 3} 读取。在集群中,这些子 Key 可能落在不同分片上。
大 Key 解决方案
方案一:拆分为更小的 Key
不用一个 100 万字段的 Hash,按范围拆分:
HSET user:bucket:0 "user:0" "data" "user:1" "data"
HSET user:bucket:1 "user:1000" "data" "user:1001" "data"
HLEN user:bucket:0
按 user_id / 1000 分桶。
方案二:用 UNLINK 代替 DEL
SET bigkey "some large value"
UNLINK bigkey
UNLINK 是非阻塞的——它立即从键空间移除 Key,在后台线程释放内存。DEL 会阻塞直到内存释放完毕。
方案三:用 SCAN 惰性删除
对于大集合,分批删除:
HSET bighash f1 "v1" f2 "v2" f3 "v3" f4 "v4" f5 "v5"
HSCAN bighash 0 COUNT 2
HDEL bighash f1 f2
HLEN bighash
分小批扫描和删除,避免阻塞。
方案四:设置限制
从源头防止大 Key 形成:
- 每次 push 后用
LTRIM限制 List 长度。 - 用
ZREMRANGEBYRANK限制 ZSET 大小。 - 用
MAXLEN限制 Stream 长度。
RPUSH bounded:list "a" "b" "c" "d" "e" "f" "g" "h"
LTRIM bounded:list -5 -1
LRANGE bounded:list 0 -1
监控清单
- 每周运行
redis-cli --bigkeys。 - 设置集合大小超过阈值的告警。
- 对疑似大 Key 用
MEMORY USAGE监控内存。 - 用
SLOWLOG GET追踪命令延迟。 - 用
INFO commandstats识别热点命令模式。
SLOWLOG GET 5
INFO commandstats
在线编辑器试一试
前往 Redis 在线编辑器 练习检测命令:
HSET user:profile f1 "v1" f2 "v2" f3 "v3" f4 "v4" f5 "v5"
HLEN user:profile
MEMORY USAGE user:profile
OBJECT ENCODING user:profile
RPUSH mylist "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
LLEN mylist
LTRIM mylist -5 -1
LRANGE mylist 0 -1
SET bigstring "hello world this is a test"
UNLINK bigstring
EXISTS bigstring
练习检测和清理模式。在生产环境中,这些命令是你对抗性能退化的第一道防线。