首页 > 数据库    日期:2026-07-11 / 浏览

在高并发的分布式系统中,Redis 作为高性能的内存数据库,广泛用于缓存、会话存储、计数器等场景。然而,随着业务规模的增长,热 Key(Hot Key)问题逐渐成为影响系统稳定性和性能的重要隐患。

什么是热 Key?

热 Key 是指在 Redis 中被高频访问的某个 Key,其访问频率远超其他 Key,导致该 Key 所在的 Redis 实例或节点成为系统瓶颈。

典型特征:

  • 单个 Key 的 QPS(每秒查询数)极高,可能达到数万甚至更高。
  • 该 Key 集中在一个 Redis 节点上,导致该节点 CPU、内存、网络带宽负载过高。
  • 其他 Key 的访问正常,但该 Key 的访问延迟明显升高,甚至引发超时或服务不可用。

热 Key 的常见场景

  1. 秒杀/抢购商品信息
    • 某个热门商品详情被大量用户频繁查询。
  2. 热点新闻或文章
    • 突发新闻的阅读量、点赞数等 Key 被高频访问。
  3. 全局配置或公共数据
    • 如系统开关、活动配置等,所有服务都依赖同一个 Key。
  4. 大 V 用户数据
    • 某个明星用户的粉丝列表、动态被大量访问。
  5. 缓存穿透/击穿后的集中重建
    • 大量请求同时重建同一个缓存 Key。

热 Key 的危害

问题影响
单点瓶颈热 Key 集中在一个 Redis 节点,导致该节点负载过高,影响其他 Key 的访问。
网络带宽耗尽高频读写导致网络 IO 达到瓶颈,响应变慢。
CPU 过载Redis 单线程处理命令,热 Key 导致主线程阻塞,影响其他请求。
缓存雪崩风险若热 Key 失效,大量请求直接打到数据库,可能压垮 DB。
主从延迟写热 Key 时,主节点压力大,同步到从节点延迟增加。

如何发现热 Key?

方法一:使用redis-cli --hotkeys(Redis 自带工具)

这是最简单直接的方式,适用于 Redis 4.0+ 版本。

原理

Redis 内置了基于 LFU(Least Frequently Used)算法 的热点 Key 发现机制。通过采样命令访问频率,自动识别出访问最频繁的 Key。

使用方式

# 连接 Redis 并启动热 Key 检测
redis-cli --hotkeys
 
# 可指定 host 和 port
redis-cli -h 127.0.0.1 -p 6379 --hotkeys

输出示例

# Scanning the entire keyspace to find hot keys as well as
# average sizes per pattern of keys.
 
# 1 hottest key found at 'user:profile:10086' with 12532 hits
# 2 hottest key found at 'product:detail:hot' with 9823 hits

注意事项

  • 需要开启 maxmemory-policy 为 LFU 相关策略(如 allkeys-lfu 或 volatile-lfu),否则可能无效。
  • 仅能发现 读操作多的 Key,写操作不会计入 LFU 计数。

方法二:SLOWLOG分析慢查询

虽然不直接找“热 Key”,但可以间接发现性能瓶颈。

命令

# 查看最近 10 条慢查询
SLOWLOG GET 10
 
# 查看慢查询总数
SLOWLOG LEN
 
# 重置慢日志
SLOWLOG RESET

输出字段说明

  • id: 日志 ID
  • timestamp: 执行时间戳
  • duration: 执行耗时(微秒)
  • cmd: 完整命令(包含 Key 名)

如果某个 Key 多次出现在慢日志中,很可能是热 Key 或大 Key。

配置慢查询阈值

redis.conf 中设置:

slowlog-log-slower-than 10000  # 超过 10ms 的命令记录
slowlog-max-len 1024           # 最多保存 1024 条日志

热 Key 的解决方案

方案 1:本地缓存 + Redis(二级缓存)

思路:在应用层使用本地缓存(如 Caffeine、Guava Cache)缓存热 Key,减少对 Redis 的直接访问。

// 示例:使用 Caffeine 缓存热 Key
LoadingCache<String, String> cache = Caffeine.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(5, TimeUnit.MINUTES)
    .build(key -> redis.get(key));

String value = cache.get("hot:product:123");

优点:简单高效,显著降低 Redis 压力。
缺点:存在缓存不一致问题,需设置合理过期时间。

方案 2:Key 拆分(分片)

思路:将一个热 Key 拆分为多个子 Key,分散访问压力。

# 原始热 Key
SET hot:product:123 "details"
 
# 拆分为多个 Key
SET hot:product:123:1 "details_part1"
SET hot:product:123:2 "details_part2"

读取时:随机选择一个子 Key 读取,或合并多个。
适用场景:读多写少,且数据可拆分。

方案 3:读写分离 + 从库扩容

思路:将读请求打到 Redis 从库,写请求打到主库。

  • 增加从节点数量,分担读压力。
  • 使用 Redis Cluster 或主从架构,合理分配读请求。

注意:从库有延迟,对一致性要求高的场景慎用。

方案 4:使用 Redis 集群(Cluster)

思路:通过 Redis Cluster 将 Key 分布到多个节点,避免单点过热。

  • Redis Cluster 使用 CRC16(key) % 16384 计算槽位,自动分片。
  • 热 Key 仍可能集中在某个槽位,但可通过 手动迁移槽位 分散。

建议:为特别热的 Key 单独部署一个 Redis 实例。

方案 5:异步更新 + 预加载

思路:避免大量请求同时触发缓存重建。

  • 使用 定时任务 预先加载热 Key。
  • 缓存失效前,异步刷新,避免集中重建。
// 使用 ScheduledExecutorService 定时刷新
scheduler.scheduleAtFixedRate(this::refreshHotKey, 0, 4, TimeUnit.MINUTES);

方案 6:限流与降级

思路:在应用层对热 Key 访问进行限流,防止系统崩溃。

  • 使用 Sentinel、Hystrix 等熔断限流框架。
  • 当访问超过阈值时,返回默认值或降级数据。
@SentinelResource(value = "getHotKey", blockHandler = "handleHotKey")
public String getHotKey() {
    return redis.get("hot:key");
}
 
public String handleHotKey(BlockException ex) {
    return "default_value";

觉得上面的内容有用吗?快来点个赞吧!

点赞() 我要打赏

温馨提示 : 本站内容来自会员投稿以及互联网,所有源码及教程均为作者总结编辑,请大家在使用过程中提前做好备份,以免发生无法预知的错误,源码类教程请勿直接用于生产环境!

 可能感兴趣的文章