双层缓存的预热策略
双层缓存(L1 本地缓存 + L2 分布式缓存)是高并发系统的经典架构。然而,冷启动和缓存失效后重建瞬间的缓存缺失,会导致大量请求穿透到后端,引发性能尖峰甚至系统崩溃。预热策略就是为了解决这个问题。
一、预热的核心问题
1.1 冷启动的三个问题
| 问题 | 描述 | 影响 |
|---|---|---|
| 缓存全空 | 系统刚启动,两级缓存都为空 | 所有请求直击数据库 |
| 热点数据缺失 | 本地缓存有限,热点数据可能被淘汰 | 本地缓存命中率低 |
| 雪崩效应 | 大量缓存同时失效或重建 | 数据库瞬间过载 |
1.2 预热的目标
二、本地缓存(Caffeine)预热策略
2.1 启动时同步加载
最简单的预热方式:应用启动时,从数据库或 Redis 加载热点数据到本地缓存。
java
@Component public class LocalCacheWarmer { @Autowired private ProductMapper productMapper; @Autowired private RedisTemplate<String, Object> redisTemplate; private final Cache<String, Product> localCache = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(5, TimeUnit.MINUTES) .build(); @EventListener(ApplicationReadyEvent.class) public void warmUp() { log.info("开始预热本地缓存..."); long start = System.currentTimeMillis(); // 策略1: 加载数据库中的热点商品(按销量排序) List<Product> hotProducts = productMapper.selectTopHotProducts(5000); for (Product product : hotProducts) { localCache.put("product:" + product.getId(), product); } // 策略2: 从 Redis 加载预定义的热点 Key 列表 Set<String> hotKeys = redisTemplate.opsForSet().members("config:hot_keys"); for (String key : hotKeys) { Product product = (Product) redisTemplate.opsForValue().get(key); if (product != null) { localCache.put(key, product); } } long cost = System.currentTimeMillis() - start; log.info("本地缓存预热完成,加载 {} 条数据,耗时 {} ms", localCache.estimatedSize(), cost); } }2.2 使用 Caffeine 的 refreshAfterWrite 自动刷新
Caffeine 的 refreshAfterWrite 可以在缓存过期后异步刷新,避免缓存击穿。
java
@Configuration public class CaffeineConfig { @Bean public Cache<String, Product> productCache(ProductLoader productLoader) { return Caffeine.newBuilder() .maximumSize(10_000) // 写入后 30 秒刷新(异步) .refreshAfterWrite(30, TimeUnit.SECONDS) // 写入后 5 分钟过期 .expireAfterWrite(5, TimeUnit.MINUTES) .build(new CacheLoader<String, Product>() { @Override public @Nullable Product load(String key) throws Exception {