Skip to content

Redis 大 Key 与热 Key 全过程治理

Redis 线上事故里,大 Key 和热 Key 非常高频。只会背“大 Key 要拆,热 Key 要本地缓存”不够。真正要懂的是:大 Key 为什么会让 Redis 慢,热 Key 为什么 Cluster 加机器也不一定解决,怎么发现、怎么治理、怎么避免治理时引入数据不一致或业务丢数据

这一页解决这些问题

问题要掌握到什么程度
大 Key 是什么知道不是 key 名字长,而是 value 太大或元素太多
热 Key 是什么知道是访问频率远高于其他 key,单点流量集中
为什么危险能讲清网络、单线程、内存、删除、复制、Cluster 热点
怎么发现会用 --bigkeysMEMORY USAGESLOWLOGINFO、应用侧统计
怎么治理会拆 Key、分页、限长、异步删除、本地缓存、请求合并、限流
Cluster 下怎么办知道单 key 只能落一个 slot,加机器不一定解决热 Key
项目怎么落地能设计商品详情、字典配置、资产字段、排行榜的缓存模型

大 Key 和热 Key 的区别

对比大 Key热 Key
核心问题单次操作太重单位时间访问太高
典型例子一个 String 存 5MB JSON,一个 Hash 有几十万 field秒杀商品、热门商品详情、全站配置
主要危害网络慢、命令慢、删除阻塞、复制和迁移慢单节点 CPU/网络高、请求排队、缓存击穿风险
Cluster 是否能解决不能根治,单个大 Key 仍在一个 slot不能根治,单个热 Key 仍在一个 slot
常见治理拆 Key、分页、限长、UNLINK本地缓存、多级缓存、请求合并、拆热点、限流

一个 Key 也可能既大又热,比如热门商品详情里塞了大量评论、规格、推荐列表,还被首页高频访问。这种最危险:单次请求重,访问频率还高。

大 Key 是什么

大 Key 不是 key 名字很长,而是 value 太大。

类型大 Key 示例危险命令
Stringproduct:detail:1001 存几 MB JSONGETSET
Hashtenant:fields:1001 有几十万字段HGETALLHKEYS
Listuser:messages:1001 有几十万元素LRANGE 0 -1
Setactivity:users:1001 有大量用户 IDSMEMBERS
ZSetrank:global 有百万成员大范围 ZRANGEZREVRANGE

大 Key 没有全行业统一阈值,要结合机器配置、网络、序列化、业务 SLA 判断。常见经验:

  1. String 超过几十 KB 就要关注,超过几百 KB 要谨慎,MB 级通常要治理。
  2. Hash、Set、List、ZSet 元素数超过几千到几万要关注。
  3. 单次返回结果越大,风险越高。
  4. 删除、迁移、复制时的风险往往比读取时更隐蔽。

大 Key 为什么会拖慢 Redis

Redis 核心命令执行通常是单线程顺序处理。一个大 Key 命令执行时间长,后面的命令就要排队。

mermaid
flowchart TD
    A["客户端请求 HGETALL big:hash"] --> B["Redis 主线程执行命令"]
    B --> C["遍历大量 field"]
    C --> D["组装大响应"]
    D --> E["网络传输大量数据"]
    E --> F["后续命令排队等待"]

大 Key 会在多个环节放大成本:

环节为什么变慢
命令执行需要遍历大量元素或处理大对象
网络传输响应体大,输出缓冲区压力大
客户端反序列化Java 对象解析、JSON 解析变慢
删除释放DEL 同步释放大对象可能阻塞主线程
AOF/RDB持久化文件变大,rewrite 成本变高
主从复制大对象写入复制慢,复制缓冲区压力大
Cluster 迁移slot 迁移时大 Key 搬迁慢

大 Key 怎么发现

bash
redis-cli -h 127.0.0.1 -p 6379 --bigkeys
MEMORY USAGE product:detail:1001
SLOWLOG GET 20
INFO commandstats
INFO memory

不要在生产执行:

bash
KEYS *

应该使用渐进扫描:

bash
SCAN 0 MATCH product:* COUNT 1000

注意:SCAN 也不是完全无成本,可能返回重复 key,需要应用侧容忍或去重。生产扫描要低峰、限速,最好走从库或离线分析。

大 Key 怎么治理

核心原则:不要让一个 key 承担无限增长的数据,也不要一次取回远超页面需要的数据。

场景错误设计改进设计
商品详情一个 Key 塞基础信息、评论、推荐、库存基础信息、评论页、推荐列表拆开
机构字段一个 Hash 存某机构所有表所有字段按机构、表、分页拆 Key
消息列表一个 List 无限追加只缓存最近 N 条,历史走数据库
排行榜一个 ZSet 存永久全量按日、周、月拆分,只保留 TopN
删除大对象DEL big:keyUNLINK big:key

异步删除:

bash
UNLINK tenant:fields:1001

UNLINK 会先把 key 从 keyspace 解除,再由后台线程释放内存,降低主线程阻塞风险。它不是不消耗资源,只是减少同步阻塞。

大 Key 拆分商业 Demo:资产字段缓存

错误设计:

text
asset:fields:tenant:1001 -> 一个 Hash 存该租户所有库表字段

问题:

  1. 租户数据越多,Hash 越大。
  2. HGETALL 一次返回太多。
  3. 更新单表字段时容易影响整个大 Key。
  4. Cluster 中该 key 只落一个 slot,内存不均。

更好的设计:

text
asset:field:summary:tenant:1001                 -> 统计信息
asset:field:table:tenant:1001:table:2001:page:1 -> 某表第 1 页字段 ID
asset:field:detail:field:3001                   -> 单字段详情

Java 示例:

java
public List<FieldDTO> listFields(Long tenantId, Long tableId, int pageNo) {
    String pageKey = "asset:field:table:tenant:" + tenantId
        + ":table:" + tableId + ":page:" + pageNo;

    List<Long> fieldIds = redis.getList(pageKey, Long.class);
    if (fieldIds == null) {
        fieldIds = fieldRepository.listFieldIds(tenantId, tableId, pageNo, 50);
        redis.set(pageKey, fieldIds, Duration.ofMinutes(20));
    }

    return fieldIds.stream()
        .map(this::getFieldDetail)
        .toList();
}

private FieldDTO getFieldDetail(Long fieldId) {
    String detailKey = "asset:field:detail:" + fieldId;
    FieldDTO cached = redis.get(detailKey, FieldDTO.class);
    if (cached != null) {
        return cached;
    }

    FieldDTO dbValue = fieldRepository.findField(fieldId);
    redis.set(detailKey, dbValue, Duration.ofMinutes(30));
    return dbValue;
}

这个设计牺牲了一点读取次数,但换来可控大小、可分页、可局部更新、可局部失效。

热 Key 是什么

热 Key 是访问频率远高于其他 key 的 key。

典型场景:

  1. 秒杀商品详情。
  2. 热门文章、热榜、首页配置。
  3. 全站字典配置。
  4. 热门资产详情、热门机构配置。
  5. 某个大客户或大租户的高频数据。

热 Key 的关键不是 value 多大,而是访问太集中。

mermaid
flowchart TD
    A["大量请求访问同一个 key"] --> B["同一个 Redis 节点"]
    B --> C["CPU 或网络打满"]
    C --> D["请求排队"]
    D --> E["应用 Redis 连接池等待"]
    E --> F["接口超时"]

为什么 Cluster 不一定解决热 Key

Redis Cluster 通过 hash slot 分散 key。一个 key 只会落到一个 slot,一个 slot 由一个 Master 负责。

mermaid
flowchart TD
    A["hot:product:1001"] --> B["计算 hash slot"]
    B --> C["slot 8888"]
    C --> D["Master-2"]
    E["大量请求"] --> D

所以加机器能扩整体容量,但不能把同一个热 Key 自动拆到多个节点上。这个问题和 Kafka 单分区热点很像:整体看有很多节点,局部热点仍然集中在一个处理单元。

热 Key 怎么发现

方法说明
应用侧统计在缓存访问封装层统计 key 模板和访问次数
网关/接口统计热接口通常对应热 Key
Redis --hotkeys需要 LFU 相关配置支持,适合辅助判断
代理层统计代理或网关统计 TopN
节点监控某个节点 CPU、网络远高于其他节点
慢日志不一定能发现热 Key,因为单次命令可能不慢

生产里最推荐应用侧做 key 模板统计。例如:

text
product:detail:{id} -> 120000 次/分钟
dict:global:all     -> 80000 次/分钟

不要把完整高基数 key 都打到日志里,否则日志量会爆。可以按 key 模板聚合,并对 TopN 具体 key 做采样。

热 Key 怎么治理

方案适用场景注意点
本地缓存字典、配置、商品详情等读多写少一致性只能做到短暂最终一致
多级缓存Caffeine + Redis + DB要有失效广播或短 TTL
请求合并热点过期瞬间大量回源同一 key 只允许一个线程重建
逻辑过期允许短时间旧值不适合账户余额、库存强一致判断
拆分 Key热点计数、库存分片读取时可能要聚合
限流降级秒杀、热点活动要有业务降级文案或兜底
预热活动前已知热点避免冷启动打 DB

热 Key 治理 Demo:本地缓存 + 短 TTL

适合字典、配置、热门详情这类允许短时间旧值的数据。

java
public class DictCacheService {
    private final Cache<String, DictDTO> localCache = Caffeine.newBuilder()
        .maximumSize(10_000)
        .expireAfterWrite(Duration.ofSeconds(30))
        .build();

    public DictDTO getDict(String dictCode) {
        String key = "dict:" + dictCode;

        DictDTO local = localCache.getIfPresent(key);
        if (local != null) {
            return local;
        }

        DictDTO redisValue = redis.get(key, DictDTO.class);
        if (redisValue != null) {
            localCache.put(key, redisValue);
            return redisValue;
        }

        DictDTO dbValue = dictRepository.findByCode(dictCode);
        redis.set(key, dbValue, Duration.ofMinutes(10));
        localCache.put(key, dbValue);
        return dbValue;
    }
}

这个方案为什么能抗热 Key:

  1. 大量请求先命中 JVM 本地缓存。
  2. Redis 只承受本地缓存失效后的少量请求。
  3. 本地缓存 TTL 很短,降低长期不一致风险。

不适合支付状态、账户余额、秒杀库存最终判断、权限强一致场景。这类场景要以数据库、状态机或专门库存服务为准。

请求合并防击穿

热 Key 过期时,如果每个线程都回源数据库,会形成击穿。请求合并的目标是:同一个 key 同一时刻只让一个线程重建缓存。

mermaid
flowchart TD
    A["热点 Key 未命中"] --> B["尝试获取重建锁"]
    B --> C{"是否拿到锁"}
    C -- "是" --> D["查询数据库并写缓存"]
    C -- "否" --> E["短暂等待或返回旧值"]
    D --> F["释放锁"]
    E --> G["再次读缓存"]

关键点:

  1. 锁要有过期时间,避免线程挂了锁不释放。
  2. 释放锁要校验唯一值,避免误删别人的锁。
  3. 等待线程不要无限等待,要有超时和降级。
  4. 高价值接口可以返回旧值,低价值接口可以限流。

治理决策表

线上遇到 Redis 抖动时,不要一看到慢就说“大 Key”,也不要一看到 CPU 高就说“热 Key”。要先分清是“单次操作重”,还是“访问频率高”,还是“缓存未命中导致数据库回源慢”。

mermaid
flowchart TD
    A["Redis 延迟升高"] --> B["看 slowlog"]
    B --> C{"是否有单条慢命令"}
    C -- "是" --> D["检查命令和 Key 大小"]
    D --> E{"value 或集合元素是否很大"}
    E -- "是" --> F["按大 Key 治理"]
    E -- "否" --> G["检查 Lua、复杂命令、网络返回"]
    C -- "否" --> H["看单节点 CPU/网络"]
    H --> I{"是否只有某节点特别高"}
    I -- "是" --> J["查热 Key 或 slot 热点"]
    I -- "否" --> K["看连接池、命中率、DB 回源"]
    J --> L["按热 Key 治理"]
    K --> M["排查穿透、击穿、雪崩或下游慢"]
现象更可能是什么第一优先级不要先做什么
SLOWLOGHGETALLSMEMBERS、大范围 ZRANGE大 Key 或全量命令拆 Key、分页、禁止全量读取盲目加 Redis 节点
单节点 CPU/网卡很高,其他节点正常热 Key 或 slot 热点应用侧 TopN、代理统计、本地缓存只扩容整个集群
Redis CPU 不高,但应用大量 timeout连接池等待、网络、DB 回源慢查连接池、命中率、DB QPS只看 Redis 服务端
删除某个 Key 后 Redis 卡顿大 Key 同步释放UNLINK,分批删除集合元素继续用 DEL 删大对象
缓存命中率突然下降,DB QPS 暴涨雪崩、穿透、击穿看 TTL 分布、空值缓存、布隆、热点锁直接把 DB 连接池调大
Cluster 某个 slot 迁移很慢大 Key 或大 slot找大 Key,拆分后再迁移高峰期强迁移

大 Key 治理优先级

大 Key 的治理目标不是“让 Redis 里没有任何稍大的 Key”,而是让单次命令、网络响应、删除释放、复制迁移都可控。

建议顺序:

  1. 禁止危险全量命令,例如 HGETALLSMEMBERSLRANGE 0 -1、超大范围 ZRANGE
  2. 把页面查询改成分页或按 ID 列表查询。
  3. 按业务维度拆 Key,例如按租户、表、日期、页码、分片编号拆。
  4. 限制集合长度,例如只缓存最近 N 条、TopN、最近 7 天。
  5. 删除大 Key 使用 UNLINK 或分批删除。
  6. 改数据模型,把详情、列表、统计拆成不同 Key。
  7. 对历史低频数据回数据库或归档系统,不强行塞 Redis。

示例:排行榜不要无限放一个 ZSet。

text
错误:rank:global -> 永久全量排行榜
改进:rank:daily:2026-07-05 -> 当日榜
改进:rank:weekly:2026-W27 -> 周榜
改进:rank:global:top1000 -> 只保留 TopN

热 Key 治理优先级

热 Key 的治理目标是把访问压力从单个 Redis 节点前移、分散或削峰。

建议顺序:

  1. 先在应用缓存封装层统计 Key 模板和 TopN 具体 Key。
  2. 对读多写少数据加本地缓存,例如 Caffeine,TTL 控制在秒级到几十秒。
  3. 对已知热点提前预热,避免活动开始时冷启动。
  4. 对热点过期使用请求合并或逻辑过期,避免击穿数据库。
  5. 对可拆分计数、库存桶、排行榜分段做 Key 分片。
  6. 对低价值热点接口限流或降级。
  7. 对强一致数据不要滥用本地缓存,读写仍以事实源为准。

热 Key 拆分要注意一致性

不是所有热 Key 都能随便拆。

数据是否适合拆 Key原因
商品详情展示适合按基础信息、库存、评价拆各部分更新频率不同
全站字典适合本地缓存和版本号读多写少
秒杀库存扣减可以分桶,但最终扣减要严谨要防超卖和重复扣减
账户余额不适合用 Redis 分片缓存做最终判断强一致要求高
权限结果可短 TTL 缓存,但关键操作要校验防止权限变更延迟风险

如果拆 Key 后需要读取多个分片再聚合,要评估聚合成本和一致性窗口。比如库存分桶可以提升并发,但最终下单仍要有数据库约束、库存服务状态机或补偿机制兜底。

生产排查流程

mermaid
flowchart TD
    A["Redis 超时或延迟升高"] --> B["看是否单节点异常"]
    B --> C{"单节点 CPU/网络很高吗"}
    C -- "是" --> D["怀疑热 Key 或 slot 热点"]
    C -- "否" --> E["看 slowlog 和 commandstats"]
    E --> F{"是否有慢命令或大返回"}
    F -- "是" --> G["怀疑大 Key 或全量命令"]
    F -- "否" --> H["看连接池、网络、持久化、DB 回源"]
    D --> I["查应用侧 key 访问 TopN"]
    G --> J["查 bigkeys、memory usage、scan"]

排查命令:

bash
SLOWLOG GET 20
INFO commandstats
INFO memory
INFO clients
redis-cli --bigkeys
MEMORY USAGE suspected:key

应用侧同时看:

  1. Redis 连接池 active、wait、timeout。
  2. 接口 P95/P99。
  3. 缓存命中率。
  4. 数据库 QPS 和慢 SQL。
  5. 单个 key 模板访问 TopN。

常见坑

后果正确做法
一个 Key 存全部数据大 Key 越来越大按业务维度、分页、时间拆分
HGETALL 当分页用一次返回全量,阻塞和网络慢HSCAN 或业务分页 Key
删除大 Key 用 DEL同步释放阻塞主线程大 Key 用 UNLINK
以为 Cluster 能解决热 Key单 key 仍落一个 slot本地缓存、拆 Key、限流
本地缓存不设 TTL数据长期不一致短 TTL、广播失效、版本号
热点缓存永不过期脏数据长期存在逻辑过期、主动失效、补偿刷新
治理只看 Redis可能根因在 DB 回源或连接池全链路看应用、Redis、DB

面试标准回答

text
大 Key 指一个 key 对应的 value 太大或元素太多,不是 key 名字长。比如一个 String 存几 MB JSON,一个 Hash 有几十万 field,一个 List/Set/ZSet 有大量元素。大 Key 会导致命令执行慢、网络传输慢、删除阻塞、AOF/RDB 和主从复制压力大,在 Cluster 中还会造成 slot 内存不均和迁移慢。发现可以用 redis-cli --bigkeys、MEMORY USAGE、SLOWLOG、INFO commandstats、SCAN 分批扫描。治理方式是拆 Key、分页取、限制集合长度、避免 HGETALL/SMEMBERS 这类全量命令,删除大 Key 用 UNLINK。

热 Key 指访问频率远高于其他 key 的 key,比如秒杀商品、热门商品详情、全站配置。Redis Cluster 只能把不同 key 分散到不同 slot,同一个热 Key 仍然落在一个 slot 和一个 Master 上,所以单纯加机器不一定解决。热 Key 要通过应用侧访问统计、节点 CPU/网络、代理层统计、--hotkeys 等方式发现,治理方式包括本地缓存、多级缓存、热点预热、请求合并、逻辑过期、拆分 Key、限流降级。

大 Key 是单次操作重,热 Key 是访问频率高。一个 key 可以既大又热,这种最危险。生产排查要结合 slowlog、bigkeys、memory usage、节点 CPU/网络、连接池、命中率和数据库回源一起看。

关联知识点

知识点说明
Redis 高并发缓存治理综合治理总览
Redis 命令执行与数据结构原理为什么慢命令、大 Key 会阻塞
过期删除与内存淘汰TTL、淘汰、大 Key 删除风险
缓存问题穿透、击穿、雪崩总览
主从哨兵 Cluster 全过程Cluster 下 slot、MOVED、热点限制