什么是预热?它有什么作用?
结论:预热(Warm-up)是在真实流量到来之前,把系统从「冷」拉到「热」:缓存加载热点数据、连接池提前建连、JIT 编译热点方法、限流器从低阈值爬升、CDN 回源分发。不做预热,冷启动瞬间的缓存穿透、建连与类加载会把 P99 拉高数倍,甚至引发雪崩。
一、预热的六个层次
| 层次 | 冷的后果 | 预热手段 |
|---|---|---|
| JVM | 解释执行、类加载慢 | 启动后回放流量、调 CompileThreshold、原生镜像 |
| 连接池 | 首个请求建连超时 | 启动时按 minimumIdle 建连并 SELECT 1 探活 |
| 本地缓存 | 命中率为 0,全部打 DB | 启动加载热点 key、refreshAfterWrite |
| 分布式缓存 | 缓存为空,穿透到 DB | 大促前批量灌数据、pipeline 导入 |
| 限流器 | 冷启动即满速,压垮下游 | 预热模式,阈值从 1/3 线性爬升 |
| CDN | 回源风暴 | 提前刷新与预推 |
二、冷启动曲线
QPS
│ ┌────────────── 稳态
│ ╱────┘
│ ╱────┘ ← 预热爬升:JIT + 缓存 + 连接
│ ╱────┘
│ ─────┘
│ ▲ 冷启动瞬间:RT 高、错误率高、DB 压力尖峰
└──────────────────────────────────────────► t
限流器的预热同理:Guava 的 SmoothWarmingUp 会在 warmupPeriod 内把放行速率从 stableInterval / coldFactor 线性提升到目标速率。
三、缓存预热代码
@Component
public class HotDataWarmer implements ApplicationRunner {
private final RedisTemplate<String, String> redis;
private final SkuMapper skuMapper;
@Override
public void run(ApplicationArguments args) {
List<Long> hotSkuIds = skuMapper.selectTopHotSkuIds(5_000);
redis.executePipelined((RedisCallback<Object>) conn -> {
for (Long id : hotSkuIds) {
String json = loadSkuJson(id);
if (json == null) {
continue;
}
// TTL 加随机抖动,避免预热数据在同一刻集体过期
long ttl = 3600 + ThreadLocalRandom.current().nextInt(600);
conn.stringCommands().setEx(
("sku:" + id).getBytes(StandardCharsets.UTF_8),
ttl,
json.getBytes(StandardCharsets.UTF_8));
}
return null;
});
log.info("热点 SKU 预热完成,共 {} 条", hotSkuIds.size());
}
private String loadSkuJson(Long id) {
Sku sku = skuMapper.selectById(id);
return sku == null ? null : JsonUtil.toJson(sku);
}
}
用 pipeline 把 5000 次 SET 合并成一次往返;TTL 抖动是防止预热完就在同一秒集体失效。
四、限流预热配置
# Sentinel 冷启动预热:阈值在 warmUpPeriodSec 内从 1/coldFactor 爬升到 count
flow:
- resource: orderCreate
grade: 1 # QPS 模式
count: 500
controlBehavior: 2 # WARM_UP
warmUpPeriodSec: 60
maxQueueingTimeMs: 0
五、常见追问
Q1:预热数据量太大怎么办? 只预热 Top N(按访问日志排序),其余靠懒加载加互斥重建;同时给缓存写空值占位,避免穿透。
Q2:怎么验证预热生效? 看三个指标:缓存命中率是否在启动后立刻达到稳态、P99 是否还有冷启动尖峰、DB QPS 是否被削平。
Q3:预热和缓存雪崩什么关系? 预热是提前填满,雪崩是同时失效。两者必须配合:预热时给 TTL 加随机抖动,否则预热完仍会在同一时刻集体过期。
Q4:预热的代价是什么? 启动变慢、启动瞬间对 DB 有一波集中读。要给预热加限速与开关,避免应用重启风暴把数据库打挂。
小结
- 预热覆盖 JVM、连接池、缓存、限流器、CDN 六个层次,缺一层就有一处尖峰。
- 缓存预热要配 pipeline 与 TTL 抖动,否则预热本身会成为新尖峰。
- 用命中率、P99、DB QPS 三个指标验证预热是否真的生效。