在高并发的分布式系统中,redis 作为高性能的内存数据库,广泛用于缓存、会话存储、计数器等场景。然而,随着业务规模的增长,热 key(hot key)问题逐渐成为影响系统稳定性和性能的重要隐患。
什么是热 key?
热 key 是指在 redis 中被高频访问的某个 key,其访问频率远超其他 key,导致该 key 所在的 redis 实例或节点成为系统瓶颈。
典型特征:
- 单个 key 的 qps(每秒查询数)极高,可能达到数万甚至更高。
- 该 key 集中在一个 redis 节点上,导致该节点 cpu、内存、网络带宽负载过高。
- 其他 key 的访问正常,但该 key 的访问延迟明显升高,甚至引发超时或服务不可用。
热 key 的常见场景
- 秒杀/抢购商品信息
- 某个热门商品详情被大量用户频繁查询。
- 热点新闻或文章
- 突发新闻的阅读量、点赞数等 key 被高频访问。
- 全局配置或公共数据
- 如系统开关、活动配置等,所有服务都依赖同一个 key。
- 大 v 用户数据
- 某个明星用户的粉丝列表、动态被大量访问。
- 缓存穿透/击穿后的集中重建
- 大量请求同时重建同一个缓存 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: 日志 idtimestamp: 执行时间戳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";到此这篇关于redis 热 key 问题的解决的文章就介绍到这了,更多相关redis 热 key 内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论