在电商系统中,分布式缓存(如 Redis)的一致性指 “缓存数据与数据库数据最终保持一致”。由于缓存与数据库是独立存储系统,且存在网络延迟、并发更新等问题,完全的实时一致性难以实现,通常通过 **“失效优先、更新兜底、异步补偿”** 的策略保证最终一致性。以下是具体实现方案:
一、核心原则:优先保证 “缓存不脏读”
分布式缓存一致性的核心矛盾是 “更新数据库后,如何同步更新缓存”。需避免两种典型问题:
缓存脏数据:数据库已更新,但缓存仍保留旧值,导致业务读取错误(如商品价格已改,缓存仍显示原价);
缓存穿透:缓存被误删除,导致大量请求直接穿透到数据库,引发性能问题。
因此,设计时需遵循:“更新数据库时,优先让缓存失效(删除 / 过期),而非直接更新缓存”,减少脏数据概率。

二、主流方案:三种更新策略对比与实践
1. 方案一:Cache Aside Pattern(缓存旁路模式)
流程:
读操作:先查缓存,命中则返回;未命中则查数据库,再将结果写入缓存(设置过期时间)。
写操作:先更新数据库,再删除缓存(而非更新缓存)。
适配场景:电商中大部分高频读写场景(如商品库存、价格更新)。
优势:
简单易实现,避免 “更新缓存” 时的并发冲突(如 A、B 同时更新,缓存可能存入旧值);
删除缓存比更新缓存更轻量,减少网络 IO。
风险与解决:
问题 1:更新数据库成功,但删除缓存失败(如网络中断),导致缓存残留旧值。
解决:通过重试机制(如本地消息表、MQ 重试)确保删除成功。例如,删除缓存失败时,将 “缓存 Key + 删除指令” 存入本地消息表,后台线程定时重试,直到删除成功。
问题 2:并发读写冲突(如读请求未命中缓存,正从数据库加载数据时,写请求更新了数据库并删除缓存,导致读请求将旧值重新写入缓存)。
解决:
对写操作加分布式锁,确保同一时间只有一个线程更新数据库和缓存;
读请求写入缓存时,判断数据版本(如数据库字段加version),若缓存中已有更新版本则放弃写入。
2. 方案二:Write Through(写透模式)
流程:
写操作时,先更新缓存,再更新数据库(缓存与数据库同步更新);
读操作同 Cache Aside(先查缓存,未命中则查库并回写缓存)。
适配场景:对缓存实时性要求极高的场景(如秒杀商品库存,需严格防止超卖)。
优势:缓存与数据库更新同步,减少脏数据概率。
风险与解决:
问题 1:更新缓存成功,但数据库更新失败,导致缓存存脏数据(如库存已减,但数据库未扣减,缓存显示错误库存)。
解决:
数据库更新时开启事务,若失败则回滚缓存更新(如 Redis 的MULTI事务或 Lua 脚本,确保 “缓存更新 + 数据库更新” 原子性);
对关键数据(如库存),更新后立即校验(如查库确认库存是否正确,不一致则删除缓存)。
问题 2:性能较差,因写操作需同时完成缓存和数据库两次 IO,不适合高并发写入场景(如大促订单创建)。

3. 方案三:Write Back(写回模式)
流程:
写操作时,先更新缓存,标记缓存为 “脏数据”,异步批量更新数据库(如定时任务、队列批量提交);
读操作优先读缓存,未命中则查库并回写。
适配场景:写操作远多于读操作,且允许短暂延迟的场景(如用户行为日志、浏览历史记录)。
优势:写操作性能极高(仅更新缓存),适合高并发写入。
风险与解决:
问题 1:缓存节点宕机,未同步到数据库的 “脏数据” 丢失(如用户刚添加的购物车商品,缓存宕机后丢失)。
解决:
缓存开启持久化(如 Redis 的 RDB+AOF),并配合集群模式(主从 + 哨兵)减少数据丢失;
关键数据(如购物车)异步更新时,先写入可靠消息队列(如 Kafka),确保至少投递一次。
三、关键补充:缓存失效与过期策略
即使采用上述更新方案,仍可能因网络分区、节点故障导致缓存不一致,需通过以下机制兜底:
1. 强制过期:设置合理的 TTL(生存时间)
为缓存设置短期 TTL(如 1-5 分钟),即使缓存出现脏数据,也会在过期后自动失效,后续请求会从数据库加载最新数据并更新缓存。
电商场景示例:商品价格缓存 TTL 设为 5 分钟,库存缓存设为 1 分钟(因库存更新更频繁)。
2. 主动失效:事件驱动的缓存刷新
数据库更新后,通过消息通知触发缓存删除 / 更新。例如:
商品服务更新价格后,发送商品价格变更事件到 MQ(如 RabbitMQ);
缓存服务监听事件,收到后主动删除对应商品的价格缓存。
优势:减少对数据库的轮询,实时性更高(延迟通常在毫秒级)。
3. 防缓存雪崩:过期时间加随机偏移
若大量缓存同时过期,会导致请求瞬间穿透到数据库,引发性能雪崩。
解决:设置 TTL 时增加随机值(如基础TTL + 0~10秒随机值),避免缓存集中失效。

四、特殊场景:高并发下的一致性保障
1. 商品库存超卖问题
场景:秒杀时,多个请求同时扣减库存,可能导致缓存库存与数据库不一致。
解决:
采用 **“先扣缓存,再扣数据库,失败回滚”**:秒杀请求先检查缓存库存,若充足则扣减缓存(Redis 的DECR原子操作),再扣减数据库;若数据库扣减失败,立即恢复缓存库存。
最终通过定时任务校验:每 10 秒对比缓存库存与数据库库存,不一致则以数据库为准更新缓存。
2. 订单状态同步
场景:订单状态(如 “待付款→已付款”)更新后,需同步更新缓存,避免用户看到旧状态。
解决:
订单服务更新数据库后,通过本地事务表记录状态变更,异步发送消息到缓存服务,删除旧状态缓存;
前端轮询订单状态时,若缓存未命中,直接查询数据库并更新缓存(兜底机制)。
五、总结:最终一致性方案组合
电商系统中,分布式缓存一致性的实践通常是 **“Cache Aside 为主,事件通知 + TTL 兜底”**:
写操作:先更新数据库,再删除缓存(避免脏写);删除失败则通过 MQ 重试确保最终删除。
读操作:缓存未命中时查库并回写缓存,同时校验数据版本(防止并发写入旧值)。
补偿机制:设置合理 TTL,结合事件驱动刷新,定时任务校验缓存与数据库一致性。
通过这种组合,既能保证高并发场景下的性能,又能将缓存不一致的窗口控制在秒级或分钟级,满足电商业务对 “最终一致性” 的要求。