连载中 8/20

预热:别让冷启动拖垮上线

2026-05-28 · 5422 阅读 · 0 评论 · 0 赞

冷启动是一道算术题

设想缓存命中率 95% 的详情接口:一万 QPS 里只有五百个会打到 DB,数据库气定神闲。现在发布新版本,缓存里是空的——一万 QPS 原封不动全量砸向 DB,二十倍的流量冲击,平时够用的容量瞬间不够。Redis 重启后、大促开闸前、新缓存架构切换时,都是同一道算术题。冷启动的缓存等于没有缓存,而穿透、击穿、雪崩三篇讲的所有防线,都要在这个空窗期里独木支撑。

预热的本质,是把这道算术题提前算掉:上线之前,把该有的数据先搬进缓存,让第一波真实流量进来时命中的是热缓存而不是空仓库。

预热什么:抓热点,别全量

第一反应是「把库里数据全灌进去」,劝住——全量预热既慢又浪费,绝大多数数据根本等不到被访问,就先在缓存里占着内存排队过气。预热的对象永远是热点数据:销量榜 Top N 的商品、首页与推荐位要展示的内容、核心配置与白名单、上一周期统计出的高频访问集合。热点数据的来源现成的:埋点统计、访问日志聚合、前一个版本缓存的访问频率,都指着同一批 key。

怎么预热:限速回源,打散 TTL

预热脚本的写法不难,难的是两个纪律。第一是控制回源速率:预热本身也是一次对 DB 的批量扫描,十万条热点数据一口气并发查库,预热脚本就成了压测工具——批量分页拉取、每批之间留间隔、QPS 压在 DB 闲时的安全水位以下(限流系列第 6 篇的 Redis+Lua 计数器可以直接拿来给预热脚本限速):

List<Long> hotIds = statService.topN("item_view", 50_000);
for (List<Long> batch : Lists.partition(hotIds, 200)) {
    Map<Long, Item> items = itemDao.loadBatch(batch); // 批量查,别一条条来
    items.forEach((id, it) ->
        redis.setex(keyOf(id), ttlWithJitter(it), toJson(it)));
    Thread.sleep(100);  // 限速:每批之间歇口气
}

第二是预热也要打散 TTL——雪崩篇的教训在这里原样生效:统一设 24 小时,明天凌晨同一秒集体失效,预热脚本亲手埋下下一场雪崩。批量加载用 MGET、pipeline、批量查询 走批,别让预热脚本自己变成击穿源。

什么时机:三个触发点

预热挂在三个时机上。发布前:新版本灰度前跑一遍预热,灰度实例从一开始就命中热缓存;大促前:活动数据、爆款商品在开闸前 T-1 就位,配合压测验证命中率;定时兜底:每天凌晨低峰跑一轮热点刷新,把过气 key 换成新的热点。三个时机共用同一套预热脚本,参数不同而已。

预热的反例也值得记:只预热了冷门数据浪费内存;预热脚本没限速把 DB 打挂在大促前一晚;预热完不验证命中率就上线,空窗期照样发生。预热不是仪式,是带验收的工程动作——上线前看一眼命中率报表,比十篇方案文档都踏实

到这里,「缓存空」的问题讲完了。但缓存还有一类更隐蔽的失衡:流量不是平均落在所有 key 上的——个别 key 扛下海量 QPS,把一个 Redis 分片打满。热的极限形态,下一篇讲热 Key。

503

10 年全栈工程师 · 503咖啡馆主理人

#缓存预热#冷启动#热点数据#灰度上线#流量控制

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 4 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞