预加载是按节奏缓存热点key而非全量塞数据,需分批错峰写入、加随机TTL防雪崩、用Lua原子化防覆盖,并配懒重建兜底与严格监控验证。

预加载不是“提前塞数据”,而是有节奏的缓存占位
大促前盲目把所有商品、用户、活动页全塞进 Redis,不仅浪费内存,还可能因 key 冲突或过期策略混乱,反而加剧击穿风险。预加载的本质是:在流量高峰到来前,把真正会高频访问的热点 key(比如秒杀商品 product:10086、首页 Banner banner:home)用可控方式载入缓存,并确保它们不会在峰值时刻集中过期。
- 只预热已确认的热点数据——通过历史 QPS、AB 测试埋点、或实时日志聚类(如 ELK 中统计
/api/product/{id}的 top 100 ID)识别,而非全量 dump 表 - 预加载 ≠ 一次性全量写入:应分批次、带错峰延迟(如每 200ms 写 50 个 key),避免自身压垮 Redis 或阻塞主线程
- 必须显式设置过期时间,但不能统一设为
EX 3600:要加随机偏移,例如EX (3600 + ThreadLocalRandom.current().nextInt(600)),防止整点雪崩
用 Lua 脚本原子化预热 + 防覆盖,避免并发写乱
如果多个预热任务同时执行,或预热与业务更新线程竞争写同一个 key,可能造成旧值覆盖新值、空值残留、甚至缓存与 DB 状态不一致。Redis 的单线程特性让 Lua 成为最轻量可靠的协调手段。
- 预热时用
EVAL执行脚本,先EXISTS判断 key 是否已存在且非空,仅当不存在时才SET并设 TTL,杜绝误覆盖 - 示例脚本逻辑:
if redis.call("EXISTS", KEYS[1]) == 0 then return redis.call("SET", KEYS[1], ARGV[1], "EX", ARGV[2]) else return 0 end - 不要在应用层做“先查后写”判断——网络往返 + 条件竞态会让这个判断失效;Lua 在服务端原子执行,才是唯一靠谱方案
预加载后必须配“懒重建兜底”,否则等于裸奔
预加载解决的是“高峰前缓存就位”,但无法覆盖所有情况:比如预热后 DB 数据被人工修改、缓存节点异常剔除、或某个 key 恰好在预热后被误删。此时若无兜底,请求仍会瞬间穿透。
- 预热 key 必须搭配互斥锁重建机制:即使它已预热,
GET返回 nil 时仍要走SETNX流程,不能跳过 - 锁的过期时间(
EX)必须明显长于数据库查询耗时(如 DB 查询平均 150ms,锁设EX 5秒),否则可能锁提前释放,引发多线程回源 - 特别注意:预热数据若含敏感字段(如价格、库存),需确保预热内容与 DB 当前一致——建议预热脚本从 DB 直接查,而非读取离线 JSON 文件
监控预热是否真正生效,别信日志里的“success”
很多团队上线预热脚本后看控制台输出 “Preload done”,就以为万事大吉。但真实情况可能是:key 写进去了,但用了错误的序列化格式(如 Java 对象未 toString() 就塞进去)、TTL 被覆盖成 0、或集群模式下 slot 分布不均导致部分节点没写到。
- 上线后立刻验证:用
redis-cli --no-raw -h x.x.x.x -p 6379 GET product:10086看能否直接取出可读值,而不是二进制乱码 - 检查 TTL:
TTL product:10086,确认返回值在预期范围内(比如 3600~4200),而非-1(永不过期)或-2(key 不存在) - 大促中段抽查:用
INFO keyspace查看db0:keys=xxx,expires=xxx,确认带过期的 key 数量与预热总量基本匹配;若expires远小于预期,说明随机 TTL 生效失败或被覆盖










