大 key 与热 key 是怎么产生的

Redis 单线程执行命令,任何一条慢命令都会堵住后面的所有请求。大 key 和热 key 正是慢命令的两大来源:大 key 指单个 key 的 value 过大,比如一个 list 塞了几百万条、一个 hash 存了几十万字段;热 key 指某个 key 被高频访问,比如秒杀商品、明星八卦,流量全打在一个节点上。

它们的产生往往来自设计欠账:图省事把整张表序列化进一个 key;排行榜、消息列表只增不减;缓存 key 不带随机后缀导致所有请求命中同一个 key。多数问题不是突发事故,而是长期积累,等到线上卡顿、节点漂移才被注意到。

本文核心知识点
  • 大 key单 key 数据量过大,阻塞命令。
  • 热 key单 key 访问过高,打爆节点。
  • 拆分按维度把大结构拆成小单元。
  • 本地缓存热 key 的兜底方案。
Redis 大 key 治理配图:蓝紫渐变背景左侧三层圆柱形数据库存储桶,顶部管线连接右侧网格状服务器机柜,机柜内立方体模块由浅蓝渐变到橙红,扁平科技插画
大块数据通过管线分流进小格子,正是大 key 拆分治理的意象。

大 key 的危害与排查

大 key 的危害是全面的:读取和删除都要遍历全部数据,单条命令耗时动辄几十毫秒,直接阻塞其他请求;大 key 占满节点内存后,集群会把整个 key 迁移到另一个节点,迁移期间服务不可用;持久化时写放大严重,主从复制也受影响。排查工具主要有三条路:slowlog 看慢命令、redis-cli --bigkeys 扫大 key、以及客户端统计 key 长度。

# 查看慢日志,找出耗时命令
redis-cli slowlog get 20

# 扫描大 key
redis-cli --bigkeys -i 0.1

更精细的做法是在客户端侧做抽样统计:记录每次读写命令涉及的 key 与数据量,定期汇总上报,这样能拿到真实业务视角下的大 key 名单,而不是靠一把梭的扫描。判定阈值按业务定,一般字符串超过 10KB、集合超过 5000 个元素就值得关注。

大 key 拆分实战

拆分的原则是按访问维度切,而不是机械地均分。同一个 hash 里字段按业务域拆成多个 key:用户信息、订单统计、行为标签各放各的;一个大 list 改成按时间分片,每个片一个 key;大字符串如果是整表快照,改为逐条小 key 加本地缓存。

// 原:一个 hash 存全部用户
HSET user:all 1001 "张三" 1002 "李四"

// 改:按区间拆分
HSET user:1001 name "张三" age 30
HSET user:1002 name "李四" age 25
拆分三种思路
  • 按业务域把混合数据按类型分 key。
  • 按时间流水数据按时分片,便于淘汰。
  • 按主键一对一大 key 改一对一细 key。

拆分要同步改读写代码:写端按新规则分散写入,读端按需聚合多个小 key。历史数据可以用后台任务逐步迁移,先写新 key 再删旧 key,避免一次性全量迁移把集群压垮。

大 key 删除的正确姿势

直接 DEL 一个大 key 是最危险的姿势:删除同样要遍历全量数据,阻塞时间和大 key 读写一样长。Redis 提供了两个非阻塞命令:UNLINK 把 key 从键空间摘除,真正的内存回收交给后台线程慢慢做;集合类数据还可以用 SSCAN 分批取出元素、逐步删除,适合在旧版本上做兼容处理。

# 非阻塞删除
redis-cli unlink big:key

# 分批清理集合(伪代码)
cursor = 0
loop {
  cursor, members = sscan big:set cursor
  srem big:set members
  if cursor == 0: break
}

还有一类特殊的大 key:过期时间很短、集中到期,瞬间同时过期也会造成阻塞抖动。处理方式是给过期时间加随机偏差,让淘汰分散到时间轴上。

热 key 发现与应对

热 key 的杀伤力不亚于大 key:单节点每秒几十万次访问,CPU 打满、网络打满,其他 key 全部陪跑。发现途径包括:客户端统计 key 访问频次、Redis 的 hotkeys 参数与 monitor 采样、以及代理层的 QPS 排行。应对策略有四层,从近到远:本地缓存扛掉大头,多级缓存分流,热点 key 加随机后缀分散到多节点,最后是容量上把热点数据放在更高性能的节点。

// 本地缓存:先查本地再查 Redis
Object v = localCache.get(key);
if (v == null) {
  v = redis.get(key);
  localCache.put(key, v, shortTtl);
}
热 key 应对梯度
  • 本地缓存最短路径,扛高频读。
  • 随机后缀让 key 分散到多个分片。
  • 读写分离读压力分流到从节点。
  • 限流兜底极端流量下保护节点。

注意本地缓存的一致性:写操作要失效或短 TTL 兜底,接受秒级延迟。随机后缀方案要区分读写,写仍写原 key,读随机挑副本 key,才能保证一致性。