热点数据与突发流量

系统平均负载只有 20%,某几个 key 却把单个缓存分片的 CPU 打满;或者平时平稳,一场秒杀让 QPS 涨了五十倍。热点和突发流量是两种不同的压力形态,但都会在“平均值看起来很正常”的时候把系统搞挂。

什么是热点

热点指访问量高度集中在少数 key、少数分片或少数接口上。典型来源:

  • 可预知的:秒杀商品、首页爆款、热搜词、大 V 发布内容。
  • 不可预知的:突发新闻、明星事件、被爬虫集中抓取。
  • 写热点:点赞、投票、计数器,同一个 key 被高频自增。

热点的破坏力在于“集中”:集群有 100 个分片,流量却全压在其中 1 个上,等于集群缩容了 99%。

热点怎么发现

手段做法局限
分片维度监控各分片 QPS/CPU/带宽对比,异常高说明有热点只能定位到分片,需再细化
客户端埋点统计在缓存客户端按 key 计数,定时上报 TOP N有性能开销,可采样
Redis 热点分析redis-cli --hotkeys 找出访问频率最高的 key(需配置 LFU 类淘汰策略)有一定开销,低频执行
慢查询日志单 key 数据大、命令重时会进慢日志只能看到慢,不一定看到热
代理层统计在自研代理/中间件里统计 TOP key依赖架构改造
业务预判运营动作、活动排期、热搜接入只能覆盖可预知热点
# 查看热点 key(需要淘汰策略为 LFU 系列,参数以官方文档为准)
redis-cli --hotkeys
# 输出:按访问频率排序的 key 列表及其频率统计

实践建议是“监控 + 预判”双管齐下:可预知的热点提前做准备,不可预知的靠分片监控和客户端埋点发现。

热点读的缓解手段

手段原理适用
多副本 key 打散同一份数据写成 N 个 key(hot:1~hot:N),读时随机选一个读多写少的强热点
本地缓存挡流进程内缓存该 key,直接挡掉绝大多数请求极小、极热、容忍秒级过期
多级缓存CDN → 本地 → Redis → DB 逐层拦截静态或半静态内容
读写分离读请求分散到多个从库数据来自数据库时
限流保护网关按 key/用户维度限流兜底,防止打垮依赖

多副本 + 本地缓存是应对强热点的组合拳:

// 以下片段需放进使用 Redis 与 Caffeine 的工程中运行
int REPLICA = 8;                            // 热点数据写 8 份副本

public String getHotGoods(long goodsId) {
    String local = localCache.getIfPresent("hot:goods:" + goodsId);
    if (local != null) {
        return local;                        // 输出:本地命中,完全不打 Redis
    }
    int idx = ThreadLocalRandom.current().nextInt(REPLICA) + 1;
    String key = "hot:goods:" + goodsId + ":" + idx;  // 随机挑一个副本读
    String v = redis.get(key);
    if (v != null) {
        localCache.put("hot:goods:" + goodsId, v);     // 回填本地,TTL 设 1~3 秒
    }
    return v;
}

// 写入时要写全部副本,且允许有短暂不一致,靠短 TTL 收敛
public void setHotGoods(long goodsId, String v) {
    for (int i = 1; i <= REPLICA; i++) {
        redis.setex("hot:goods:" + goodsId + ":" + i, 300, v);
    }
}

多副本的代价是写放大(写 N 次)和一致性变弱,所以只对“读极多、写极少”的数据使用。

热点写怎么办

写热点没法打散成多副本那么简单,因为写要合并回一个真实值。常见做法是分桶累加 + 异步汇总

点赞数 +1 的流程:
1. 把计数拆成 N 个桶:like:1001:1 ~ like:1001:8
2. 每次点赞随机选一个桶 INCR,压力被摊到 8 个 key 上
3. 后台任务每 1~5 秒把 N 个桶求和,写回 like:1001 的总数
4. 展示时读总数 key(若读也热,就再叠一层本地缓存)

其他手段:

  • 异步化:写请求只落入队列,由消费者批量合并后落库(如“同一商品 1 秒内的 1000 次点赞合并成一次 SQL”)。
  • 合并写:应用内先攒一小段时间的增量,一次性写库。
  • 业务上削峰:把实时计数改为可接受延迟的近似值,前端展示“10 万+”而不是精确值。

突发流量与预案

突发流量(秒杀、推送、开抢提醒)要求提前准备,而不是临时救火:

阶段动作
容量评估按预估峰值算 QPS、连接数、缓存/DB 承载,留 2~3 倍余量
预热活动开始前把商品、库存、配置加载进缓存,避免开抢瞬间集体回源
分层限流CDN/网关/应用/DB 各层都设阈值,前端排队或发令牌控制进入量
队列削峰写请求先进 MQ,按下游处理能力匀速消费
静态化活动页/详情页做成静态资源走 CDN,不经过应用
降级开关提前准备好关闭非核心功能、返回兜底数据的开关
压测演练按峰值压测并演练降级,验证阈值与预案可执行
监控告警大促期间加强监控频率,值班表与升级机制到位

热点与雪崩的区别

维度热点缓存雪崩 / 击穿
成因流量集中在少数 key 或分片大批 key 同时失效、缓存集群宕机,或单个极热 key 失效瞬间回源
表现单分片 CPU/带宽打满、响应变慢命中率骤降、数据库 QPS 暴涨
影响范围局部:与热点相关的请求受影响全局:几乎全部请求回源
主要对策打散(多副本)、本地缓存、限流TTL 加随机抖动、多级缓存、熔断降级、集群高可用、失效时互斥重建
关键区别容量问题:压力集中失效问题:缓存没挡住

两者也可能叠加:一个热点 key 恰好过期,同时上万个请求回源,就会从热点演变成击穿。

小结:热点的核心是“压力集中”,解法是打散——多副本 key、本地缓存、多级缓存、读写分离,再用限流兜底;写热点靠分桶累加与异步批量合并。突发流量则靠容量评估、预热、分层限流、队列削峰和降级预案提前准备。记住热点是容量问题、雪崩是失效问题,两者的排查方向和处置手段完全不同。

笔记加载中…