目录
前言
从缓存预热、动静分离到智能预加载,层层递进构建高性能缓存体系
如下所示:

在高并发系统中,缓存是提升性能、保护数据库的"第一道防线"。然而,许多团队虽然引入了 Redis,却依然面临:
- 服务重启后数据库被打爆;
- 大促期间缓存频频失效;
- 命中率长期徘徊在 50% 以下......
问题不在于"有没有缓存",而在于"会不会用缓存" 。本文将带你从真实痛点出发,通过 5 级缓存优化策略 ,系统性提升缓存命中率,最终实现 95%+ 命中率、毫秒级响应、数据库零压力 的理想状态。
1、缓存问题
典型场景:
冷启动雪崩:服务重启,缓存为空,10 万请求直击 DB;
缓存穿透:恶意查询user?id=-1,绕过缓存压垮 DB;
策略粗暴:所有数据统一 TTL=1 小时,导致脏读或频繁回源;
被动等待:用户点了才查,无法预判下一步行为。
缓存击穿:热点 key 过期瞬间,大量并发请求重建缓存;
命中率低:缓存策略不合理,大量请求未命中。
核心矛盾:缓存是"被动响应"的,但用户行为是"主动且有规律"的。
2、解决方案演进
5 级缓存优化策略,我们采用 渐进式优化路径,每一步都解决前一步的痛点:

Level 1:基础防护 ------ 空值缓存 + 简单 TTL
适用场景:小型项目、快速上线;
核心思想:有缓存总比没有强,至少防住无效查询。
代码如下所示:
java
public User getUser(Long id) {
String key = "user:" + id;
User user = redis.get(key);
if (user != null) return user;
user = db.query(id);
if (user != null) {
redis.setex(key, 1800, user); // 30分钟
} else {
redis.setex(key, 60, "NULL"); // 空值兜底
}
return user;
}
目标:防穿透、保底线;缺点:无法解决冷启动,命中率仅 40%~50%。
Level 2:缓存预热 ------ 解决冷启动问题
问题来了:
想象一下,你维护一个电商系统。某天凌晨 2 点,你升级服务,重启了 10 台应用服务器。
结果早上 8 点用户一上班,首页加载巨慢,监控报警:数据库连接池耗尽!
为什么?因为所有缓存都是空的!每个用户请求都要查数据库,10 万 QPS 全部压到 DB 上。
💡 这就是典型的 "冷启动问题"。
方案:在服务启动时,主动加载热点数据到缓存。
如下所示:

关键实践:
- 使用 @EventListener
(ApplicationReadyEvent.class)触发; - 异步执行,避免阻塞主线程;
- 分批加载,防止 OOM;
- 预热失败告警,但不影响服务可用性。
注意:
"启动完成后要做的事,交给ApplicationReadyEvent**;启动过程中要做的事,才用** @PostConstruct**。"**
代码如下所示:
java
@Component
@Slf4j
public class CacheWarmUpService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
@Qualifier("warmupExecutor")
private Executor warmupExecutor;
@EventListener(ApplicationReadyEvent.class)
public void startWarmUp() {
log.info(" 应用启动完成,提交缓存预热任务...");
warmupExecutor.execute(this::doWarmUp);
}
private void doWarmUp() {
try {
// 模拟加载热点数据
redisTemplate.opsForValue().set("hot:news", "Spring Boot 缓存预热成功!",
Duration.ofMinutes(30));
log.info(" 缓存预热完成");
} catch (Exception e) {
log.error(" 缓存预热异常", e);
}
}
}
@Configuration
public class ThreadPoolConfig {
@Bean("warmupExecutor")
public Executor warmupExecutor() {
return Executors.newFixedThreadPool(2,
r -> new Thread(r, "cache-warmup-thread"));
}
}
目标:服务启动即"热"。效果:命中率提升至 60%~70%,DB 压力骤降。
缺点:预热数据固定,无法适应实时变化。
Level 3:动静分离 + 分层缓存 ------ 精细化管理
预热之后,为什么命中率还是上不去?新问题浮现:
你做了预热,但发现:
- 用户搜索"iPhone",缓存没命中;
- 查看某个冷门商品详情,又要查 DB;
- 有些数据更新了,缓存还是旧的。
为什么?因为你预热的是"你认为的热点",但用户行为是动态变化的!
核心思想:不是所有数据都适合同一缓存策略!


查询链路:

代码如下所示:
java
// 静态数据:永久缓存
redis.set("dict:province", provinces);
// 半动态:带版本号,避免脏读
String version = db.getVersion("product");
redis.setex("product:" + id + ":v" + version, 7200, product);
// 动态数据:仅本地缓存(Caffeine)
LoadingCache<Long, Stock> localCache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.build(id -> db.getStock(id));
public Product getProductDetail(Long productId) {
// 1. 先查本地缓存(应对高频短时访问)
Product local = localCache.getIfPresent(productId);
if (local != null) return local;
// 2. 再查 Redis
String key = "product:detail:" + productId;
Product cached = redis.get(key);
if (cached != null) {
localCache.put(productId, cached); // 回填本地缓存
return cached;
}
// 3. 最后查 DB
Product db = productMapper.selectById(productId);
if (db != null) {
// 写入 Redis(TTL=1小时)+ 本地缓存(TTL=10秒)
redis.setex(key, 3600, db);
localCache.put(productId, db);
}
return db;
}
命中率 75%~85%,资源利用率最优。缓存更精准,避免"一刀切"导致的脏数据或频繁回源;
Level 4:智能预加载 ------ 主动预测用户行为
能否让缓存"主动出击",而不是被动等待?
场景思考:
用户打开商品列表页,看到第一页 20 个商品。他大概率会:
-
点击第一个商品 → 查看详情;
-
或点击"下一页" → 看第 21~40 个商品。
但当前系统是:用户点了,才去查缓存/DB。能不能在他"点之前",就把数据准备好?
💡 这就是 "智能预加载"(Predictive Prefetching) 的思想!
方案:根据历史访问模式,提前加载关联数据。
常见策略:
- 时间局部性:用户刚查了商品A,很可能马上看详情 → 预加载详情页数据;
- 空间局部性:用户浏览列表页第1页,可能翻到第2页 → 预加载下一页;
- 关联规则:买了手机的人常买耳机 → 推荐页预加载耳机信息。
场景 1:列表翻页预加载

注意:避免过度预加载浪费资源。
策略 1:空间局部性预加载(列表翻页
代码如下所示:
java
@GetMapping("/products")
public PageResult listProducts(@RequestParam(defaultValue = "1") int page) {
// 返回当前页
List<Product> current = getPageFromCacheOrDB(page);
// 异步预加载下一页(仅当用户活跃)
if (isUserActive()) {
CompletableFuture.runAsync(() -> {
preloadPage(page + 1); // 提前加载下一页到缓存
}, prefetchExecutor);
}
return new PageResult(current, page);
}
private void preloadPage(int nextPage) {
List<Product> next = productMapper.selectPage(nextPage, 20);
// 缓存 30 秒,避免浪费内存
redis.setex("product:list:" + nextPage, 30, next);
}
场景 2:关联推荐预加载
- 用户看"手机" → 预加载"耳机";
- 搜索"连衣裙" → 预加载"热销款"。
但要注意:**不能盲目预加载!**比如用户只看了一页就离开,预加载的数据就浪费了。
所以我们要加判断:
- 用户是否活跃(如 5 秒内有操作)?
- 是否是高频用户(VIP/老用户)?
- 当前系统负载是否允许?
代码如下所示:
java
// 用户查看手机商品
public Product viewPhone(Long phoneId) {
Product phone = getProduct(phoneId);
// 异步预加载"买了手机的人也买了"的耳机
CompletableFuture.runAsync(() -> {
List<Product> recommended = recommendationService.getEarphonesByPhone(phoneId);
redis.setex("rec:phone:" + phoneId, 60, recommended);
});
return phone;
}
目标:在他点击前,数据已就绪;效果:命中率 85%~92%,用户体验丝滑。
Level 5:AI 驱动缓存(终极形态)
目标:用数据预测未来;效果:命中率 95%+,资源精准投放。
如下所示:

落地建议(无需真 AI):
用规则引擎模拟智能决策:
java
public void smartPrefetch(UserContext ctx) {
LocalDateTime now = LocalDateTime.now();
int hour = now.getHour();
// 规则1:晚高峰 + 女性 → 预加载服饰
if (hour >= 19 && hour <= 22 && ctx.getGender() == FEMALE) {
prefetchCategory("dresses");
}
// 规则2:用户刚搜了"手机",预加载配件
if ("search".equals(ctx.getLastAction())
&& ctx.getSearchKeyword().contains("手机")) {
prefetchAccessories();
}
// 规则3:大促期间,预加载秒杀商品
if (isFlashSalePeriod()) {
prefetchFlashSaleItems();
}
}
3、选型建议
如下所示:

4、常见误区
1、"所有数据都缓存"
→ 只缓存高频、高价值数据,冷数据直接查 DB。
2、"TTL 越长越好"
→ 动态数据需短 TTL,避免脏读;静态数据可永久缓存。
3、"预加载越多越好"
→ 结合用户意图和系统负载,避免资源浪费。
总结
真正的缓存高手,懂得:
- 何时缓存(冷启动时预热);
- 缓存什么(动静分离,分层管理);
- 缓存多久(TTL 精准匹配数据生命周期);
- 如何预判(从被动响应到主动出击)。
不要追求一步到位,而要持续迭代:
- 先做好 空值缓存 + 启动预热(2 天上线);
- 再实施 动静分离(1 周见效);
- 最后探索 智能预加载(大促前部署)。
当你能做到------
"用户还没点,数据已在路上",你就真正掌握了缓存的艺术。
参考文章: