预热必须在活动开始前完成,否则缓存击穿风险不降反升;秒杀场景下coupon:stock:1001等库存key需提前加载至redis及本地缓存,设业务语义过期时间并验证存在性,异步队列仅承载扣减结果落库与缓存更新通知,不可替代redis原子校验。

预热必须在活动开始前完成,否则缓存击穿风险不降反升
秒杀场景下,coupon:stock:1001 这类库存 Key 一旦过期或未预热,瞬时几万请求会全部打到数据库。预热不是“加个缓存”,而是要确保该 Key 在流量洪峰到来前已存在于 Redis 中,且本地缓存(如 Caffeine)也已加载。常见错误是只做 Redis 预热,却忽略应用重启后本地缓存为空,导致首波请求仍穿透。
- 预热时机必须卡在低峰期(如凌晨 2–4 点),不能放在服务启动时——启动时 DB 压力大,且无法保证所有实例同步完成
- 预热数据应带业务语义过期时间,比如秒杀活动持续 2 小时,
EXPIRE至少设为7200秒,避免中途自然失效 - 预热后必须验证:用
redis-cli --raw -h x.x.x.x -p 6379 get coupon:stock:1001手动确认值存在、类型正确(INCRBY场景下必须是整数)、TTL 剩余正常
异步队列不是用来“扛住所有请求”,而是把写操作从 Redis 主节点上剥离
很多人误以为加个 Kafka 就能解决热点 Key 写压力,其实不然。INCR 或 DECR 类原子操作无法直接丢进队列——队列不保证顺序和原子性,会导致库存超卖。真正该进队列的是“扣减结果落库”和“缓存更新通知”这两步。
- Redis 层只做快速校验与预扣减(例如
DECR后判断是否 ≥ 0),成功则发消息到 Kafka;失败直接返回“库存不足” - Kafka 消费端负责异步更新 MySQL 库存、记录订单、触发补偿逻辑,不参与实时决策
- 注意消费延迟:若 Kafka 消费积压,
coupon:stock:1001的 Redis 值已扣减,但 DB 未更新,此时运维查 DB 会误判库存异常——需依赖定时对账任务,而非人工盯屏
预热 + 异步队列组合使用时,最容易被忽略的是本地缓存一致性兜底
当 Redis 预热 Key 被意外删除(如误操作 FLUSHDB),或异步队列消费失败导致 DB 和 Redis 数据长期不一致,仅靠这两层防护会失效。本地缓存如果没设 fallback 机制,就会变成单点故障放大器。
- 本地缓存(如 Caffeine)必须配置
refreshAfterWrite(30, TimeUnit.SECONDS),而不是仅靠expireAfterWrite - 刷新回调里要走完整链路:先查 Redis → 查不到再查 DB → 写回 Redis 和本地缓存,三步缺一不可
- 不要在本地缓存里存原始库存数字,而应存结构体(如
{used: 1200, total: 2000, version: 123}),避免数值覆盖冲突
预热和异步队列本身不难实现,难的是它们之间的责任边界是否清晰。一个 Key 的生命周期里,谁负责“首次存在”,谁负责“变更传播”,谁兜底“最终一致”,这三个问题没理清,再多的中间件堆砌也只是把雪崩从数据库转移到了 Kafka 或本地内存里。











