得物二面:Redis 中某个 Key 访问量特别大怎么办?我:Redis 能顶得住...生瓜蛋子生瓜蛋子
热点 Key vs 大 Key别搞混了热点 Key 是指被大量客户端频繁访问的 KeyQPS远高于其他 Key导致单个 Redis 节点成为瓶颈。维度热点 Key大 Key问题本质访问频率过高数据体积过大核心危害单节点 CPU/网络被打满主线程阻塞、网络拥塞典型场景秒杀商品、热搜话题10MB 的 JSON、百万级集合解决思路分散访问压力拆分数据体积一句话结论热点 Key 的核心问题是访问集中在一个节点上解决方案是本地缓存减少请求 读写分离分散读压力 Key 分片分散到多个节点。热点 Key 的三种死法单节点过载热点 Key 集中在某个节点该节点 CPU 和网卡带宽被瞬间打满其他节点空闲。邻居效应同一节点上的其他 Key 响应变慢一个热点 Key 拖垮一整片业务。主从同步延迟热点 Key 涉及写操作时主节点压力大导致复制延迟从节点读到旧数据。典型场景突发热点明星官宣、热搜不可预测最危险预期内热点秒杀、限时抢购可以提前准备架构缺陷全局配置放单个 Key人为制造的热点改设计就能解决怎么发现热点 Key方法一redis-cli --hotkeysRedis 4.0# 需要先开启 LFU 淘汰策略 redis-cli --hotkeys # 输出示例 # [Hot Key] user:profile:10086 (access count: 583210) # [Hot Key] seckill:goods:1001 (access count: 421305)基于 Redis 的 LFU 计数器统计前提是淘汰策略必须设为volatile-lfu或allkeys-lfu。方法二MONITOR命令临时排查慎用redis-cli MONITOR | grep GET\|HGET | awk {print $NF} | sort | uniq -c | sort -nr | head -20严重警告MONITOR在高 QPS 下会导致 10%~30% 性能下降只能短时间使用。方法三业务层监控推荐长期方案public class HotKeyDetector { // Guava Cache 做 1 秒滑动窗口 privatefinal CacheString, AtomicLong counterCache CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.SECONDS) .build(); 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); } } }对 Redis 零侵入适合长期运行可结合 Prometheus 做可视化。四种解决方案方案一本地缓存最优先在应用服务器本地缓存热点数据请求直接从内存返回不经过 Redispublic class LocalCacheSolution { privatefinal CacheString, String localCache Caffeine.newBuilder() .maximumSize(10000) .expireAfterWrite(5, TimeUnit.SECONDS) // 5 秒过期容忍短暂不一致 .build(); public String getHotData(String key) { // 1. 先查本地缓存 String value localCache.getIfPresent(key); if (value ! null) return value; // 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; } }热点 Key 的 Redis QPS 可能从 30 万降到几千。适用于数据量小、更新不频繁的场景。方案二Key 分片将一个热点 Key 复制为 N 个副本分布在不同节点public class HotKeyShardingSolution { privatestaticfinalint SHARD_COUNT 16; // 写入时同时写 N 个分片 public void setHotKey(String key, String value) { for (int i 0; i SHARD_COUNT; i) { redis.set(key :shard: i, value, 30, TimeUnit.MINUTES); } } // 读取时随机选一个分片 public String getHotKey(String key) { int shard ThreadLocalRandom.current().nextInt(SHARD_COUNT); return redis.get(key :shard: shard); } }30 万 QPS 分 16 片每个节点只承受约 1.9 万。代价是写入开销和内存都翻 N 倍适合读远多于写的场景。方案三读写分离 多级缓存京东方案三级协同本地缓存挡 90% → Redis 从节点挡 9% → DB 熔断兜底 1%。30 万 QPS 的热点 Key 也能稳定应对。方案四限流降级兜底对热点 Key 的访问频率限流超阈值直接返回降级数据。实现简单但部分用户会看到降级内容。方案对比与生产推荐方案优点缺点适用场景本地缓存性能最好直接挡在应用层多实例数据不一致数据量小、更新不频繁Key 分片读写都能分散写入开销大维护成本高读远多于写读写分离对应用透明主从延迟有从节点资源限流降级兜底保命部分用户看到降级数据配合其他方案使用生产推荐组合本地缓存首选 读写分离分散读压力 限流降级兜底保命。面试高频追问追问一本地缓存和 Redis 的一致性怎么保证本地缓存过期时间设短3~5 秒容忍短暂不一致。要求高的场景用 Redis Pub/Sub 或 MQ 广播通知所有实例失效本地缓存。大多数热点数据商品详情、文章内容短暂不一致是可接受的。追问二热点 Key 和大 Key 同时存在怎么办先拆大 Key减小体积再分散热点减少单节点压力。两者解决思路完全不同但可能同时出现在同一个 Key 上。