缓存预热必须异步触发,不可阻塞服务启动;推荐监听ContextRefreshedEvent后异步执行,或通过CI/CD调用/warmup接口;应使用scan+mget分批加载,避免pipeline大payload;写入须用setIfAbsent或Lua保证不覆盖线上更新;需校验key数量、TTL及内容一致性。

缓存预热该在服务启动时做,还是上线后异步触发
必须异步触发,不能卡在服务启动流程里。Redis 连接未就绪、依赖服务(如 DB、配置中心)还没 ready,硬塞数据会失败或超时,导致应用起不来。
常见错误现象:ConnectionRefusedError、TimeoutError 在 spring.factories 或 ApplicationRunner 里直接调用 redisTemplate.opsForValue().set();或者 k8s pod 因 readiness probe 失败被反复重启。
- 推荐做法:监听
ContextRefreshedEvent后开新线程,加@Async或手动提交到隔离线程池 - 更稳妥的做法:通过运维脚本或 CI/CD 流水线最后一步,调用一个预热 HTTP 接口(如
/internal/warmup),由业务方自己控制时机和重试 - 注意区分环境:dev 环境跳过;staging 强制校验 key 数量;prod 必须带限流和失败告警
用 pipeline 还是 scan + mget 批量加载
优先用 scan + mget 组合,而不是 pipeline 写入。预热本质是「从源头读 + 往 Redis 写」,瓶颈通常在源端(DB 查询),不是 Redis 写入吞吐。
性能影响明显:全量 keys * 会阻塞 Redis 单线程;而 scan 游标式遍历不阻塞,配合分页查 DB 更可控。
-
scan每次返回 100–500 条 key,再用这些 key 去 DB 查对应数据,组装成Map<String, String>后调用redisTemplate.opsForValue().multiSet() - 避免用
pipeline包裹几百个set——网络往返少但单次 payload 大,容易触发io.lettuce.core.RedisCommandTimeoutException - 如果源数据支持批量导出(如 MySQL 的
SELECT ... INTO OUTFILE),可离线生成 RDB 快照或 AOF 片段,用redis-cli --pipe导入,速度最快但灵活性差
预热数据怎么避免覆盖线上正在更新的缓存
核心原则:预热写入必须带过期时间,且不能用 set 覆盖,要用 setnx 或带条件的 set。
否则会出现「刚预热完,用户下单更新了库存缓存,结果下一次预热又把旧库存刷回去」这种经典脏写。
- 用
redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS)—— 只有 key 不存在才写入 - 或者用 Lua 脚本原子判断:
EVAL "if redis.call('exists', KEYS[1]) == 0 then return redis.call('setex', KEYS[1], ARGV[1], ARGV[2]) else return 0 end" 1 mykey 3600 "val" - 更彻底的方案:预热前先清掉旧缓存(
redisTemplate.delete(pattern)),但要确保清空范围精准,别误删用户 session 类缓存
如何验证预热是否成功,而不是“以为成功了”
不能只看日志里有没有 “warmup finished”,得查真实 key 存在性、数量、TTL、内容一致性。
最容易被忽略的是 TTL 设置错误——比如代码里写了 60 * 60,但单位传成了 MILLISECONDS,结果缓存 1 秒就过期,等于白忙。
- 预热后立刻执行:
redis-cli --scan --pattern "user:*" | wc -l核对 key 总数是否符合预期 - 抽样检查:
redis-cli ttl user:1001和redis-cli get user:1001,确认过期时间和值正确 - 加一层轻量监控:预热模块暴露
/actuator/warmup-status,返回 {“total”: 24500, “success”: 24498, “expiredKeys”: [“order:999”]},供 Prometheus 抓取
复杂点在于业务数据有状态依赖——比如预热了商品信息,但没预热对应类目缓存,前端一查就 fallback 到 DB。这类链路必须按依赖顺序编排预热步骤,不能只盯着 Redis key 数量。

















