Redis遇到热点key如何解决

一句话结论 :热点 Key 的核心问题是 访问集中在一个节点上 ,解决方案是 本地缓存(减少请求)+ 读写分离(分散读压力)+ Key 分片(分散到多个节点)

热点 Key和大key的区别

热点 Key 是指被大量客户端频繁访问的 Key,其 QPS 远高于其他 Key,导致单个 Redis 节点成为性能瓶颈。

维度 热点 Key 大 Key
问题本质 访问频率过高 数据体积过大
核心危害 单节点 CPU / 网络被打满 主线程阻塞、网络拥塞
典型场景 秒杀商品、热门文章、热搜话题 一个 10MB 的 JSON、百万级集合
解决思路 分散访问压力 拆分数据体积

深度解析

一、热点 Key 到底有什么危害?

img

上图展示了热点 Key 在 Redis Cluster 中的危害:

  • 单节点过载:热点 Key 集中在某个节点上,该节点的 CPU、网卡带宽被瞬间打满,而其他节点可能很空闲。

  • 影响其他业务:同一节点上的其他 Key 也会因为资源被占满而响应变慢,产生 "邻居效应"。

  • 主从同步延迟:如果热点 Key 涉及写操作,主节点压力大时,主从复制延迟增大,读从节点可能拿到旧数据。

二、热点 Key 的典型场景

img

上图列出了热点 Key 的三种典型场景:

  • 突发热点 :明星官宣、热搜话题、突发新闻等,短时间内大量用户访问同一页面,对应的 Redis Key 被疯狂读取。这类热点 不可预测,最危险。

  • 预期内热点 :秒杀商品、限时抢购等,在活动期间某些 Key 的 QPS 会飙到极高。这类热点 可以提前准备

  • 架构设计不当:把全局配置、系统参数放在单个 Key 中,每个请求都要读取,造成人为的热点。

三、如何发现热点 Key?

方法一:redis-cli --hotkeys(Redis 4.0+)

复制代码
# 需要先开启 maxmemory-policy 为 LFU 系列策略
redis-cli --hotkeys

# 输出示例:
# [Hot Key] user:profile:10086  (access count: 583210)
# [Hot Key] seckill:goods:1001  (access count: 421305)
# [Hot Key] hot:news:202        (access count: 389102)

关键点:

  • 基于 Redis 的 LFU 计数器统计,能直接列出访问频率最高的 Key。

  • 前提条件 :必须将淘汰策略设为 volatile-lfuallkeys-lfu,否则无法使用。

方法二:MONITOR 命令(临时排查)

复制代码
# 实时监控 Redis 执行的所有命令(危险!仅用于临时排查)
redis-cli MONITOR | grep "GET\|HGET" | awk '{print $NF}' | sort | uniq -c | sort -nr | head -20

# 输出示例:
# 583210 GET user:profile:10086
# 421305 GET seckill:goods:1001
# 389102 GET hot:news:202

关键点:

  • MONITOR 会输出 Redis 执行的每一条命令,可以用管道统计哪些 Key 被访问最多。

  • 严重警告MONITOR 本身会消耗 Redis 性能(高 QPS 下可能导致 10%~30% 性能下降),只能短时间使用,不能长期开启。

方法三:业务层面监控

复制代码
/**
 * 简单的热点 Key 检测器
 * 统计每个 Key 的访问频率,超过阈值告警
 */
publicclass HotKeyDetector {

    // Key 访问计数器(可用 Guava Cache 设置滑动窗口)
    privatefinal Cache<String, AtomicLong> counterCache = CacheBuilder.newBuilder()
            .expireAfterWrite(1, TimeUnit.SECONDS)  // 1 秒滑动窗口
            .build();

    // 热点阈值:1 秒内访问超过 1000 次
    privatestaticfinallong HOT_THRESHOLD = 1000;

    public void recordAccess(String key) {
        AtomicLong counter = counterCache.get(key, () -> new AtomicLong(0));
        long count = counter.incrementAndGet();

        if (count == HOT_THRESHOLD) {
            // 触发告警
            alertService.warn("检测到热点 Key:" + key + ",QPS:" + count);
        }
    }
}

关键点:

  • 在应用层统计每个 Key 的访问频率,超过阈值触发告警。

  • 这种方式对 Redis 零侵入,不影响 Redis 性能,适合长期运行。

  • 可以结合 Sentinel、Prometheus 等监控系统做可视化展示。

四、热点 Key 的解决方案

img

上图展示了热点 Key 的四种解决方案:

  • 本地缓存(最优先):在应用服务器本地缓存一份热点数据,请求直接从本地内存返回,不经过 Redis,大幅降低 Redis 访问压力。

  • 读写分离:增加从节点,读请求分散到多个从节点,热点 Key 的读压力被多个节点分担,适合读多写少的场景。

  • Key 分片:将一个热点 Key 复制为 N 个副本,分布在不同节点上,读请求随机选择一个副本读取。

  • 限流 + 熔断:对热点 Key 的访问频率进行限流,超过阈值的请求直接返回降级数据,保护 Redis 和 DB 不被压垮。

五、方案详解与代码示例

方案一:本地缓存(Caffeine)
复制代码
/**
 * 使用 Caffeine 本地缓存解决热点 Key
 * 适合:数据量小、更新不频繁、读多写少
 */
publicclass LocalCacheSolution {

    // Caffeine 本地缓存
    privatefinal Cache<String, String> localCache = Caffeine.newBuilder()
            .maximumSize(10000)               // 最多缓存 1 万个 Key
            .expireAfterWrite(5, TimeUnit.SECONDS)  // 5 秒过期(容忍短暂不一致)
            .recordStats()                    // 记录统计信息
            .build();

    public String getHotData(String key) {
        // 1. 先查本地缓存
        String value = localCache.getIfPresent(key);
        if (value != null) {
            return value;  // 本地缓存命中,直接返回,不访问 Redis
        }

        // 2. 本地缓存 miss,查 Redis
        value = redis.get(key);
        if (value != null) {
            localCache.put(key, value);  // 回写本地缓存
            return value;
        }

        // 3. Redis 也 miss,查 DB
        value = db.query(key);
        if (value != null) {
            redis.set(key, value, 30, TimeUnit.MINUTES);
            localCache.put(key, value);
        }
        return value;
    }
}

关键点:

  • 使用 Caffeine 作为本地缓存(高性能、支持过期淘汰),5 秒过期容忍短暂的数据不一致。

  • 热点 Key 的请求大部分会命中本地缓存,Redis 的 QPS 可能从 30 万降到几千。

  • 注意 :本地缓存适用于 数据量小、更新不频繁 的场景。如果数据更新频繁,要注意本地缓存和 Redis 的一致性问题。

方案二:Key 分片
复制代码
/**
 * 热点 Key 分片方案
 * 将一个热点 Key 复制为 N 个副本,分散到不同节点
 */
publicclass HotKeyShardingSolution {

    privatestaticfinalint SHARD_COUNT = 16;  // 分片数量

    // 写入时:同时写入所有分片
    public void setHotKey(String key, String value) {
        for (int i = 0; i < SHARD_COUNT; i++) {
            String shardKey = key + ":shard:" + i;
            redis.set(shardKey, value, 30, TimeUnit.MINUTES);
        }
    }

    // 读取时:随机选择一个分片读取
    public String getHotKey(String key) {
        int shard = ThreadLocalRandom.current().nextInt(SHARD_COUNT);
        String shardKey = key + ":shard:" + shard;
        return redis.get(shardKey);
    }
}

关键点:

  • 写入时同时写 N 个分片,读取时随机选一个分片,把 QPS 均摊到 N 个节点上。

  • 比如 30 万 QPS 的热点 Key,分 16 片后每个节点只承受约 1.9 万 QPS。

  • 代价:写入开销增大(写 N 次)、内存占用增大(存 N 份)、数据一致性维护成本高。

  • 适合 读远多于写 的场景(如秒杀商品详情)。

方案三:读写分离 + 热点探测(京东零售方案)

img

上图展示了京东零售的热点 Key 多级缓存方案:

  • 第一级:本地缓存(挡住 90% 请求):应用层统计 Key 访问频率,超过阈值自动标记为热点 Key,存入 Caffeine 本地缓存。

  • 第二级:Redis 读写分离(挡住 9% 请求):热点 Key 的读请求路由到从节点,分散读压力。

  • 第三级:DB 熔断兜底(1% 请求):Redis 全部 miss 时,查 DB 前先限流,DB 扛不住则返回降级数据。

  • 三级缓存协同工作,30 万 QPS 的热点 Key 也能稳定应对。

六、方案对比总结

方案 优点 缺点 适用场景
本地缓存 性能最好,直接挡在应用层 多实例数据不一致,内存有限 数据量小、更新不频繁
Key 分片 读写都能分散,通用性强 写入开销大,维护成本高 读远多于写
读写分离 对应用透明,运维层面解决 主从延迟,从节点成本 读多写少,有从节点资源
限流降级 兜底保命,实现简单 部分用户看到降级数据 兜底方案,配合其他方案使用

生产推荐组合:本地缓存(首选)+ 读写分离(分散读压力)+ 限流降级(兜底保命)。

面试高频追问

  1. 追问一:热点 Key 和大 Key 有什么区别?

    热点 Key 是 访问频率过高 ,问题在于单节点 CPU / 网络被打满;大 Key 是 数据体积过大,问题在于操作耗时长、阻塞主线程。两者可能同时存在(比如一个又大又被频繁访问的 Key),但解决思路完全不同:热点 Key 要分散访问,大 Key 要拆分数据。

  2. 追问二:本地缓存和 Redis 缓存的一致性怎么保证?

    本地缓存的过期时间设短一些(如 3~5 秒),容忍短暂的不一致。如果对一致性要求高,可以用 Redis Pub/SubMQ 广播 通知所有应用实例失效本地缓存。也可以接受最终一致性,大多数热点数据(商品详情、文章内容)短暂不一致是可接受的。

  3. 追问三:如何提前预知热点 Key?

    对于可预期的热点(如秒杀、大促),可以提前将热点数据加载到本地缓存中,活动开始前就预热好。对于突发热点,需要依赖热点探测机制实时发现并自动缓存。结合监控告警,当某个 Key 的 QPS 超过阈值时自动触发本地缓存。

常见面试变体

  • 变体一:"Redis 中某个 Key 访问量特别大怎么办?"

  • 变体二:"秒杀场景下 Redis 的热点 Key 如何处理?"

  • 变体三:"如何发现 Redis 中的热点 Key?"

  • 变体四:"本地缓存和 Redis 缓存怎么配合使用?"

记忆口诀

热点 Key:访问太多,单节点扛不住 ------ "流量太集中"。

解决方案优先级:本地缓存(挡最多)> Key 分片(分散节点)> 读写分离(分散读)> 限流降级(兜底)。

与大 Key 区别:热点是 "来的人太多",大 Key 是 "东西太大搬不动"。

生产最佳实践:热点探测 + 本地缓存 + 读写分离 + 限流降级,四板斧组合使用。

总结

Redis 热点 Key 是指被大量客户端频繁访问的 Key,会导致单个节点 CPU 和网络被打满。发现热点 Key 可以用 --hotkeysMONITOR、业务层监控等手段。解决方案的核心是 分散访问压力:优先使用本地缓存(Caffeine)挡住大部分请求,配合 Key 分片或读写分离分散 Redis 的读压力,最后用限流降级兜底保命。

复制代码
复制代码
复制代码
相关推荐
炜宏资料库2 小时前
产业集团总部大楼智能化系统项目规划方案精讲
运维·服务器·数据库
嵌入式×边缘AI:打怪升级日志2 小时前
从零开始写Linux字符设备驱动:一个不操作硬件的Hello驱动
linux·运维·数据库
搜佛说2 小时前
比SQLite更快,比InfluxDB更轻:sfsDb的降维打击
jvm·数据库·物联网·架构·sqlite·边缘计算·iot
LilySesy2 小时前
【与AI+】英语day4——数据库与性能优化
数据库·oracle·性能优化·sap·abap·自动翻译
前进的李工2 小时前
MySQL角色管理:权限控制全攻略
前端·javascript·数据库·mysql
爱丽_2 小时前
MySQL `EXPLAIN`:看懂执行计划、判断索引是否生效与排错套路
android·数据库·mysql
小红的布丁2 小时前
Redis 持久化详解:AOF、RDB 与混合持久化如何平衡性能和可靠性
数据库·redis·缓存
qqxhb2 小时前
23|工具生态全景:本地文件、网络、数据库、浏览器自动化
网络·数据库·自动化·ai编程·最小权限·人工确认