1. 完整回答
Redis 热 Key 指的是某个 Key 在短时间内被大量请求集中访问,比如热门商品详情、热点新闻或者秒杀商品库存。
它的主要问题是访问压力集中在单个 Redis 分片上。因为 Redis Cluster 通过哈希槽分配数据,同一个 Key 通常由固定的主节点负责处理。当某个 Key 的请求量特别大时,就可能导致对应节点 CPU、网络带宽等资源达到瓶颈,出现响应变慢、请求超时,甚至影响同一节点上的其他业务。
解决热 Key 问题,一般先定位热点,再根据读写场景进行处理。
首先,通过 Redis 节点的 CPU、网络流量、请求延迟,以及客户端访问统计,找到访问量异常的 Key,并区分是读热点还是写热点。
对于读多写少的热 Key,通常优先使用本地缓存,比如 Caffeine,让请求先访问应用内存,未命中再访问 Redis,从而降低 Redis 的请求压力。如果流量依然很大,还可以把热 Key 复制成多个不同的 Key,分散到不同的 Redis 分片,或者通过读写分离,让从节点承担一部分读请求。
对于写热点,比如秒杀库存和高并发计数器,本地缓存通常无法直接解决写入压力,需要结合业务进行限流、请求合并或者分桶计数,同时保证关键操作的原子性。
最后,还需要考虑数据一致性问题。本地缓存可能出现短暂的数据延迟,读写分离存在主从复制延迟,多副本 Key 也会增加同步成本。
所以整体思路就是:先发现热点,再区分读写,通过缓存分层或流量分散降低单节点压力,并根据业务要求控制数据一致性。
2. 核心原理与关键概念
核心原理:
Redis Cluster 将数据划分为 16384 个哈希槽(Hash Slot),每个主节点负责其中一部分槽。同一个 Key 会映射到固定的槽,因此大量请求访问同一个 Key 时,压力会集中到对应节点。
热 Key 的形成过程:
业务热点出现 → 某个 Key 的访问量突然增加 → 请求集中到同一分片 → CPU 或网络资源达到瓶颈 → 请求排队、延迟升高。
解决过程:
发现热点 → 判断读写类型 → 减少请求或分散负载 → 控制数据一致性 → 监控优化效果。
具体有三种主要思路:
-
减少请求: 通过本地缓存、请求合并等方式,让部分请求在到达 Redis 前就得到处理。
-
分散读取: 通过读写分离或者复制多个缓存 Key,让读请求分布到不同节点。
-
分散写入: 对允许拆分的计数业务进行分桶,对无法拆分的写操作使用限流、排队等方式控制并发压力。
关键概念:
| 概念 | 含义 |
|---|---|
| QPS | 每秒请求量,用来衡量 Key 的访问热度 |
| 哈希槽 | Redis Cluster 分配数据的逻辑单位 |
| 本地缓存 | 将热点数据保存在应用进程内存中,减少 Redis 请求 |
| 读写分离 | 主节点处理写入,从节点分担读取 |
| 热 Key 多副本 | 将同一份热点数据保存为多个不同的缓存 Key,分散读取压力 |
3. 高频面试追问
3.1 为什么 Redis Cluster 扩容后,热 Key 问题可能依然存在?
因为扩容增加的是集群的整体处理能力,单个热 Key 仍然由固定分片负责。
Redis Cluster 根据 Key 计算哈希槽,再根据槽找到对应节点。
假设一个集群有 5 个主节点,其中一个 Key 每秒有 10 万次访问,这些访问仍然会集中到负责该 Key 的主节点。
即使扩容到 10 个主节点,也只是重新分配哈希槽,无法自动把这个 Key 的请求分摊到所有主节点。
因此,普通扩容更适合解决整体容量不足或者多个 Key 分布不均的问题。
如果瓶颈集中在单个热 Key,需要通过本地缓存、读取副本或者复制多个 Key 来减少单节点压力。
3.2 如何在线上发现和定位热 Key?
一般结合 Redis 节点监控、客户端访问统计和热点分析工具定位。
首先观察各个分片的 CPU 使用率、网络流量、QPS 和请求延迟。如果某个分片的负载明显高于其他分片,就可能存在热 Key。
接下来有几种定位方式:
第一种是客户端采样统计。在 Redis 客户端或代理层记录部分 Key 的访问情况,按时间窗口聚合,统计访问量最高的 Key。这种方式适合持续监控,也方便定位突发热点。
第二种是 redis-cli --hotkeys,它利用 LFU 访问频率信息寻找热点,需要 Redis 使用 LFU 内存淘汰策略,扫描过程也有一定开销。
Redis 8.6 及以上版本还支持 HOTKEYS 命令,可以根据 Key 消耗的 CPU 时间和网络流量进行热点分析。
第三种是云厂商提供的热点 Key 统计功能。
另外,MONITOR 也能观察请求,但它的性能开销较高,不适合长期在线上开启。
定位后还需要判断热点主要来自读取次数、写入次数,还是单次操作的数据量过大,再选择对应方案。
3.3 为什么本地缓存通常是解决读热 Key 的优先方案?
因为本地缓存可以直接减少 Redis 请求,而且访问延迟更低。
正常情况下,应用每次读取数据都需要进行一次 Redis 网络访问。
引入 Caffeine 这样的本地缓存后,请求流程变成:
请求 → 本地缓存 → Redis → 数据库。
其中只有本地缓存未命中时,才会继续访问 Redis。
例如,一条热门商品详情每秒被请求 5 万次,如果本地缓存能够命中其中 90% 的请求,那么访问 Redis 的请求就可以降低到每秒约 5000 次。
这种方式特别适合商品详情、热点新闻、配置信息等读多写少、允许短暂数据延迟的场景。
局限是每个应用实例都有独立缓存,增加了内存占用,也需要额外处理数据更新和失效问题。
3.4 本地缓存和 Redis 之间如何保证数据一致性?
通常采用过期时间配合主动失效,在业务允许的范围内控制数据延迟。
比较常见的做法是给本地缓存设置较短的 TTL。
当数据发生更新时,先更新权威数据源,再按缓存更新策略失效 Redis 和本地缓存,让后续请求重新加载最新数据。
如果希望减少缓存过期前的数据延迟,可以通过消息通知,让各个应用实例主动删除对应的本地缓存。
Redis 还提供了 CLIENT TRACKING 机制,可以在 Key 发生变化时通知相关客户端,使其清理本地缓存。
不过,通知可能因为连接异常等原因无法正常处理,所以仍然需要设置合理的兜底机制,例如 TTL、连接恢复时清空缓存等。
对于商品介绍、热点文章这种允许短暂延迟的数据,本地缓存比较合适。
如果是库存扣减、账户余额等强一致性要求较高的数据,关键读写应该通过权威数据和原子操作完成,不能直接依赖可能过期的本地缓存结果。
3.5 热 Key 拆分成多个 Key,具体怎么实现?
对于读多写少的热点数据,可以复制多个内容相同的 Key,让请求随机访问不同副本。
例如,原来只有一个 Key:
product:1001
可以复制成:
product:1001:0、product:1001:1、product:1001:2
这些 Key 保存相同的商品详情。
读取时,客户端根据随机数或者其他均衡策略选择其中一个 Key。
在 Redis Cluster 中,还需要确保这些 Key 实际分布在不同分片上,这样才能真正分散负载。
更新数据时,需要同步更新或者统一失效所有副本,避免不同请求读取到不同版本。
这种方案适合访问量非常大、更新频率较低的数据。
主要限制是多副本一致性和维护成本。 如果数据频繁变化,副本越多,同步成本越高。
继续追问:给 Key 添加不同后缀,就一定能分散到不同节点吗?
不一定。不同 Key 通常会计算出不同的哈希槽,但多个槽仍然可能归属同一个节点。
另外,如果多个 Key 使用了相同的 Hash Tag,例如 {product:1001}:0 和 {product:1001}:1,它们会被映射到同一个槽。
因此需要检查实际槽位与节点分布,确保请求真正落到了不同的 Redis 分片。
3.6 使用读写分离能彻底解决热 Key 吗?
读写分离可以缓解读热点,但对写热点帮助有限,而且存在数据延迟问题。
Redis 主从架构中,主节点负责写入,从节点通过复制获取数据。
如果大量请求都是读取,可以让一部分请求访问从节点,从而降低主节点的读取压力。
但这种方案存在两个限制。
第一,主从复制通常是异步的,从节点可能暂时落后于主节点,因此读取的数据可能不是最新版本。
第二,写操作仍然需要由主节点处理。如果某个库存 Key 每秒有大量写入,仅仅增加从节点无法分散这些写入。
因此,读写分离适合读多写少、能够接受一定复制延迟的业务。
实际使用时,还需要客户端支持从节点读取及相应的请求分配策略。
3.7 如果是写热点,比如秒杀库存,应该怎么处理?
写热点需要从业务层减少或分散写入压力,同时保证库存操作的正确性。
例如某个商品只剩 100 件库存,但短时间内有几万次抢购请求。
如果所有请求直接修改同一个 Redis 库存 Key,就会形成集中写入压力。
通常可以分层处理。
首先,通过限流、资格校验等方式过滤无效或超额请求,减少真正进入库存扣减环节的并发量。
其次,对于符合业务条件的场景,可以通过消息队列控制处理速率,防止瞬时写入超过 Redis 的处理能力。
库存扣减本身仍然要保证原子性,例如通过 Redis Lua 脚本完成库存检查和扣减,避免并发超卖。
如果是点赞数、浏览量等允许延迟汇总的计数场景,还可以把一个计数器拆成多个分桶,让不同请求更新不同 Key,读取时再汇总。
需要注意,分桶计数适合允许聚合的业务,不能直接套用到要求实时精确扣减的库存场景,否则容易引入库存分配和一致性问题。
3.8 热 Key 突然过期,导致大量请求访问数据库,应该怎么办?
这种情况属于热点 Key 的缓存击穿,核心是控制并发回源数量。
例如一个热门商品详情的 Redis 缓存突然过期,几万个请求同时发现缓存不存在,于是一起查询数据库,数据库就可能承受很大的瞬时压力。
常见做法是使用互斥重建。
请求发现 Redis 缓存未命中后,通过互斥机制只允许一个执行者查询数据库并重建缓存。
其他请求可以短暂等待,或者根据业务要求返回降级结果。
这样能够避免大量请求同时访问数据库。
另一种方式是逻辑过期,缓存数据中记录逻辑过期时间。数据过期后,允许短暂返回旧值,同时由后台任务刷新缓存。
这种方式适合允许短暂旧数据、但要求服务持续可用的场景。
继续追问:给缓存过期时间加随机值能解决吗?
随机过期时间主要用来避免大量 Key 同时失效,对缓存雪崩比较有效。
如果只有一个极热的 Key 过期,仍然可能出现大量请求同时回源,所以还需要互斥重建、主动预热或者逻辑过期等措施。
3.9 如果线上突然出现热 Key,应该如何应急处理?
一般先止损,再定位原因,最后进行长期治理。
首先通过监控确认是否出现单分片 CPU 飙升、网络带宽打满或者请求延迟明显增加。
如果已经影响业务,需要优先控制流量,例如接口限流、对非核心请求降级,防止热点继续扩大。
接下来根据访问统计找到具体 Key,判断是读热点还是写热点。
如果是热门商品详情这样的读热点,可以临时增加本地缓存,或者利用读副本分担压力。持续性热点可以进一步采用多副本 Key 分散请求。
如果是集中写入,就需要检查是否存在重复请求、异常流量,并结合业务进行排队、合并或分桶处理。
优化后重点观察热 Key 的请求量、Redis 各分片 CPU、网络吞吐量,以及接口 P99 延迟和错误率。
只有确认单节点压力下降、整体服务恢复正常,同时数据一致性满足业务要求,才能说明治理有效。
4. 易混淆概念对比
4.1 热 Key 与大 Key
| 对比项 | 热 Key | 大 Key |
|---|---|---|
| 核心特征 | 访问频率高 | Value 体积大或元素多 |
| 主要问题 | 请求集中、资源倾斜 | 单次操作开销大、占用内存多 |
| 典型场景 | 热门商品、秒杀库存 | 大型 Hash、超长 List |
| 主要方案 | 本地缓存、读取分流、请求限流 | 数据拆分、分页读取、渐进删除 |
一个 Key 可以同时是大 Key 和热 Key,这时需要同时处理访问频率和单次操作开销。
4.2 热 Key 与缓存击穿
| 对比项 | 热 Key | 缓存击穿 |
|---|---|---|
| 产生条件 | 某个 Key 被大量访问 | 热点缓存失效,大量请求同时回源 |
| 压力集中位置 | Redis 节点 | 数据库或后端数据源 |
| 主要解决方案 | 本地缓存、分散读取压力 | 互斥重建、逻辑过期、主动预热 |
两者可能连续发生:先出现热 Key,再因为缓存过期引发缓存击穿。
4.3 本地缓存、Redis 读副本与多副本 Key
| 方案 | 核心方式 | 优势 | 主要限制 |
|---|---|---|---|
| 本地缓存 | 应用进程内保存数据 | 减少网络请求,降低 Redis 压力 | 多实例数据一致性 |
| Redis 读副本 | 从节点分担读请求 | 利用现有复制架构扩展读取能力 | 主从复制延迟 |
| 多副本 Key | 将相同内容存为多个 Key | 将读取压力分散到不同分片 | 副本同步与更新成本 |
一般优先考虑本地缓存。对于持续存在的高强度读热点,再结合副本读取或多副本 Key 处理。
4.4 热 Key 复制与计数分桶
| 对比项 | 热 Key 复制 | 计数分桶 |
|---|---|---|
| 数据内容 | 多个 Key 保存相同值 | 每个 Key 保存部分计数 |
| 请求方式 | 随机选择一个副本读取 | 分散写入,读取时聚合 |
| 主要用途 | 分散读压力 | 分散写压力 |
| 一致性问题 | 多副本更新同步 | 聚合延迟、计数准确性 |
两者都使用多个 Key,但数据组织方式和适用场景不同。
5. 总结
Redis 热 Key 就是少量 Key 承受了非常高的访问量,导致请求集中到某个 Redis 分片,形成 CPU 或网络瓶颈。解决时首先通过监控和访问统计定位热点,再区分读热点和写热点。对于读热点,通常使用本地缓存、读写分离或者多副本 Key 分散压力;对于写热点,可以通过限流、请求合并或者业务分桶降低集中写入。需要特别注意,Redis Cluster 普通扩容不能自动分散单个热 Key 的请求,最终还需要根据业务对实时性和一致性的要求选择方案。