| ||||||||||||||||||||||||||||||||||||
在电商系统中,分布式缓存(如 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,结合事件驱动刷新,定时任务校验缓存与数据库一致性。 通过这种组合,既能保证高并发场景下的性能,又能将缓存不一致的窗口控制在秒级或分钟级,满足电商业务对 “最终一致性” 的要求。 | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||
|













