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

监控清单

  1. 每周运行 redis-cli --bigkeys
  2. 设置集合大小超过阈值的告警。
  3. 对疑似大 Key 用 MEMORY USAGE 监控内存。
  4. SLOWLOG GET 追踪命令延迟。
  5. 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

练习检测和清理模式。在生产环境中,这些命令是你对抗性能退化的第一道防线。