Skip to content

第十三章:大 Key 问题与治理 ​

Q40:什么是大 Key、热 Key?它们会带来什么问题? ​

答:

「大 Key」和「热 Key」是两个不同的概念,但都可能导致 Redis 出现性能抖动,需要单独识别和治理。

一、什么是大 Key ​

大 Key 通常指单个 key 的 value 体积过大。

Redis 内部无论是 String 还是集合,本质都是「一个 key 对应一块数据」,这块数据越大,操作它时的成本就越高。

常见的大 Key 形式:

类型大 Key 表现典型来源
String单个 value 几十 KB 甚至几 MB整段 JSON、序列化的长文本、大对象快照
List / Set / ZSet单个 key 里塞了上万甚至几十万个元素消息堆积、加购物车未清理、粉丝列表
Hash单个 key 里成千上万个 field用户画像无限膨胀、把整个表塞进一个 Hash
Stream / Bitmap 等消息 ID 堆积、offset 稀疏导致底层字符串超大日志流堆积、不合理的 offset 设计

大 Key 本身不一定有「对错」,但会带来几个实实在在的问题:

  1. 阻塞主线程:Redis 是单线程执行命令,DEL 一个超大的 key、HGETALL 一个拥有百万 field 的 Hash、KEYS/SMEMBERS 遍历超大集合,都会长时间占用主线程,导致其他客户端命令排队等待。一旦单条命令耗时达到秒级,整个 Redis 就会出现「卡顿」。

  2. 网络抖动:读写大 Key 时,单次传输的数据量大,容易撑满网卡带宽,挤占其他命令的传输,造成整体延迟上升。

  3. 内存浪费与碎片:大 String 反复整体覆盖更新,容易产生内存碎片。

  4. 过期删除卡顿:一个超大 key 过期时,主线程要一次性回收它,同样可能卡一下。

  5. 迁移 / 持久化困难:在 Cluster 中,大 Key 会导致单节点负载不均;在 RDB/AOF 持久化、主从同步时也会拖慢整体节奏。

二、什么是热 Key ​

热 Key 指被极其频繁访问的 key,例如秒杀商品、明星微博、排行榜头部数据。访问量集中到极少数 key,会让它们所在的分片承受不成比例的负载,成为集群的「热点」。

大 Key 和热 Key 可能同时出现(一个超大 List 又被海量并发读写),这种情况最危险。

三、问题的直观表现 ​

text
大 Key                                     热 Key
value = 100 万行的哈希表                       每秒 10 万次 GET hot:goods:1001
   │                                             │
   ├─► DEL 耗时 3 秒,主线程被卡住                    ├─► 单节点 QPS 被打满
   ├─► HGETALL 返回 20MB,网卡被挤爆                    ├─► 集群负载严重倾斜
   └─► 过期回收卡顿                                  └─► 大量请求集中到同一分片

Q41:如何发现和定位大 Key、热 Key? ​

答:

治理的前提是「先看见」。发现大 Key、热 Key 的手段,从轻量到深入依次是:

一、用 Redis 自带命令做初步扫描 ​

SCAN + 类型特异性命令:不要用 KEYS(会阻塞主线程),用 SCAN 游标遍历,配合类型的长度/大小命令逐个判断。

bash
# 用 SCAN 遍历 key
redis-cli --scan --pattern '*'

# 针对不同类型查大小
redis-cli STRLEN user:1001:profile          # String 的字节长度
redis-cli LLEN   task:queue                 # List 元素个数
redis-cli HLEN   user:1001:tags             # Hash 的 field 个数
redis-cli SCARD  video:6:liked_users        # Set 元素个数
redis-cli ZCARD  rank:video:hot             # ZSet 元素个数

MEMORY USAGE:直接查某个 key 实际占用的内存字节数,比元素个数更准确。

bash
redis-cli MEMORY USAGE user:1001:profile
redis-cli MEMORY USAGE rank:video:hot

二、用 redis-cli 提供的分析工具 ​

Redis 自带两个常被忽略的分析工具:

bash
# 扫描并找出大 key
redis-cli --bigkeys

# 配合选项,指定扫描采样数量
redis-cli --bigkeys -i 0.1

--bigkeys 会按类型统计每种结构里元素最多、字节最大的 key,并给出汇总。优势是开箱即用;局限是它主要按「元素个数」判断,对单个大 String 的字节数判断有限,且默认会遍历全部 key,量大时本身也有开销。

新版 Redis 也提供了 MEMORY DOCTOR 做内存诊断,以及 redis-cli --memkeys 辅助分析。

三、热 Key 的发现 ​

热 Key 是「访问频率」问题,用静态扫描发现不了,需要动态观测:

手段说明
redis-cli --hotkeys基于内存采样估算热门 key(需要配置 maxmemory-policy 为 LFU)
MONITOR实时打印所有命令,可以观察高频 key,但会显著拖慢性能,生产慎用
慢日志 SLOWLOG发现耗时异常的命令,间接定位大 Key/热 Key
客户端埋点 / 指标系统在应用侧统计 key 的访问次数,最精确但需要额外开发
代理层统计(如 Codis 等)中间件层统计访问热点

实操建议:先跑 --bigkeys 快速摸清「大」的问题;热 Key 优先依靠应用侧埋点或 LFU 采样,避免在生产环境跑 MONITOR。

四、结合项目场景的排查清单 ​

可疑现象优先排查
某段时间整机延迟飙升、其他命令排队是否在 DEL / 遍历大 Key
网卡流量异常、传输缓慢是否在读写大 String / 大集合
集群单节点负载远高于其他节点是否有热 Key 集中在某个分片
内存缓慢上涨不回落是否有集合 key 只增不删、无限膨胀
主从同步、RDB 备份变慢是否存在超大 key 导致整体拖慢

Q42:如何治理和优化大 Key、热 Key? ​

答:

治理大 Key、热 Key 的核心思想只有四句话:能拆就拆,能删就删,删要渐进,热要分散。下面分别展开。

一、大 Key 的治理 ​

1. 拆分(能拆就拆) ​

把一个超大结构拆成多个小结构,是治本手段。

  • 大 String:不要把一个对象的完整 JSON 或长文本整体存进一个 key。拆成多个字段,用 Hash 或 RedisJSON 存,只按需取更新的部分。
  • 大 Hash:按维度分片,例如 user:1001:tags:0、user:1001:tags:1,或按业务字段拆成多个独立 Hash。
  • 大 List:按时间或按业务分队列,例如 task:queue:2026-08-27,而不是所有任务堆进一个 task:queue。
  • 大 ZSet:按时段、按业务分排名,或只保留 top N(配合 ZREMRANGEBYRANK 定期裁剪)。
text
拆之前                              拆之后
user:1001:profile (String, 2MB)    user:1001:basic   (Hash, 几 KB)
                                     user:1001:tags    (Hash, 几 KB)
                                     user:1001:history (Hash, 几 KB)

task:queue (List, 100 万条)          task:queue:00 (List, 少量)
                                     task:queue:01 (List, 少量)
                                     task:queue:02 (List, 少量)

直接 DEL 一个大 key 会阻塞主线程。正确的做法有两个:

用 UNLINK 替代 DEL:UNLINK 是异步回收,命令立即返回,真正的内存释放在后台线程完成,不阻塞主线程。

bash
redis-cli UNLINK user:1001:history

分批删除集合:对于超大 Hash/Set/ZSet/List,用 HSCAN/SSCAN/ZSCAN + 分批 HDEL/SREM/ZREM,或 LPOP 循环,每次只删一小批,中间 sleep 让出主线程。

python
# 分批删除大 Hash 的示例思路
cursor = 0
while True:
    cursor, fields = redis_client.hscan("user:1001:history", cursor, count=100)
    if fields:
        redis_client.hdel("user:1001:history", *fields.keys())
    if cursor == 0:
        break

对 List,可以直接用 LTRIM 只保留最新的 N 条来「裁剪」:

bash
redis-cli LTRIM task:queue 0 9999   # 只保留最新 1 万条
3. 从源头避免(能拆就拆的延伸) ​
  • 给集合设置上限或定期裁剪(如 LTRIM、ZREMRANGEBYRANK);
  • 给缓存设置合理的 TTL,避免「只写不删」无限膨胀;
  • 设计 key 时就评估单个 key 的规模上限。

二、热 Key 的治理 ​

热 Key 的本质是「访问量过于集中」,所以手段都是「分散」和「减压」。

1. 本地缓存(多级缓存) ​

在应用进程内加本地缓存,把热点数据挡在 Redis 之前,减少对 Redis 的访问压力:

text
请求 → 本地缓存 → Redis → 数据库
        命中即返回   未命中再查

热点数据(首页、热搜、秒杀商品)非常适合放入本地缓存,能显著降低 Redis 单 key 的压力,牺牲的是一致性(本地缓存刷新有延迟)。

2. 读写分离 / 副本扩容 ​

对读多写少的热 Key,通过加只读副本(replica)分担读压力,读请求打到副本而不是主节点。

3. 二级 key / 打散(分散热点) ​

把「一个热 key」拆成「多个冷 key」,例如 hot:goods:1001:0、hot:goods:1001:1,读的时候随机或 hash 到其中一个,把单一 key 的压力分摊到多个 key,进一步配合 Cluster 把流量分散到多个分片。

4. 限流与降级 ​

在应用侧对热点接口做限流、缓存预热、兜底降级,避免瞬时流量把热点 key 打穿。

三、治理手段速查表 ​

对象治理手段命令 / 示例要点
大 String拆分字段改为 Hash / RedisJSON按需取、按需更新,避免整体读写
大 Hash/Set/ZSet分片user:1001:tags:0 / rank:video:hot:0分片 key 控制规模
大 List分队列 + 裁剪LTRIM task:queue 0 9999控制长度,避免无限堆积
大 key 删除异步删除UNLINK key不阻塞主线程
大集合删除分批删除HSCAN / SSCAN + 分批 HDEL / SREM每次少量 + sleep 让出主线程
大 ZSet保留 top NZREMRANGEBYRANK rank:video:hot 0 -1001只留榜单头部
热 Key本地缓存应用内本地缓存热点数据减少 Redis 访问
热 Key读写分离 / 副本读请求走 replica副本分担读压力
热 Key打散为二级 keyhot:goods:1001:0 ~ hot:goods:1001:N注意力/压力分散到多 key
热 Key限流 / 降级 / 预热应用层限流兜底保护 Redis 和数据库

小结 ​

  • 大 Key 是「单个 key 太大」,主要危害是阻塞主线程、拖慢网络、造成内存碎片和迁移困难;治理靠拆分、异步删除、分批删除、源头设限。
  • 热 Key 是「单个 key 访问太频繁」,主要危害是负载倾斜、单分片被打满;治理靠本地缓存、副本、打散、限流降级。
  • 两者都要先发现、再治理:大 Key 用 --bigkeys、MEMORY USAGE、STRLEN/HLEN/LLEN 等扫描;热 Key 用 --hotkeys、LFU 采样、应用侧埋点等手段观测。
  • 核心口诀:能拆就拆,能删就删,删要渐进(UNLINK / 分批),热要分散(本地缓存 / 副本 / 打散)。