Redis缓存一致性问题

总结摘要
Redis缓存一致性问题

数据库和缓存双写时,如何保证一致性?

一句话原理 采用“先更新数据库,再删除缓存”的策略(Cache Aside Pattern),配合异步重试机制或 Binlog 订阅(如 Canal),在保证数据库持久化成功的前提下,最终一致性地删除过期缓存。

一句话源码 在业务 Service 层代码中,通常将数据库更新操作与缓存删除操作封装在同一个本地事务 @Transactional 下,顺序执行 db.update()cache.delete(),并通过消息队列记录删除失败的 Key 以便补偿。

一句话项目/场景 在电商“订单状态修改”场景中,先更新 MySQL 订单表,再删除 Redis 中的订单缓存,若因网络抖动导致删除失败,通过监听 MySQL Binlog 自动触发缓存删除重试,确保用户查询时能读到最新状态,避免“退款成功但前端仍显示处理中”的业务故障。

先更新数据库还是先删除缓存?各自有什么问题?

先更新数据库,再删除缓存 问题: 若数据库更新成功但缓存删除失败,会导致缓存中仍为旧数据(脏数据),出现数据不一致。 场景: 数据库已提交事务,但 Redis 网络超时,导致后续请求读取到旧缓存。

先删除缓存,再更新数据库 问题: 在高并发读写场景下,线程 A 删除缓存后、还未更新数据库时,线程 B 此时读取数据库旧值并写入缓存,会导致缓存长期被旧数据污染。 场景: 用户修改个人信息,缓存被删瞬间,另一请求读到旧数据并回填缓存,导致修改操作“丢失”。

什么是 Cache Aside Pattern?为什么是常用的缓存模式?

一句话原理 Cache Aside Pattern(旁路缓存模式)是一种以数据库为基准的读写分离策略,读时“先查缓存,未命中则查库并回填”,写时“先更新数据库,再删除缓存”,将缓存维护责任交由应用程序控制。

一句话源码 在业务代码逻辑中,读操作通过 if (cache.get(key) == null) 判断执行 db.query() 并调用 cache.set(),写操作则严格遵循 db.update() 成功后立即执行 cache.delete() 的顺序。

一句话项目/场景 在“电商商品详情页”查询场景中,绝大多数请求通过读缓存直接响应,仅在数据变更(如价格调整)时由应用主动淘汰缓存,既保证了读性能,又避免了并发写导致的数据错乱。

什么是延迟双删?为什么要延迟?延迟时间如何确定?

一句话原理 延迟双删是一种通过“先删缓存 -> 更新数据库 -> 延时再删缓存”的流程,以牺牲强一致性为代价,解决并发读写场景下“旧数据回填缓存”问题的最终一致性策略。

一句话源码 在执行数据库更新的 Service 方法中,于事务提交后通过异步线程或消息队列延迟执行 redis.delete(key),确保延时时间大于一次数据库读取与缓存写入的耗时总和。

一句话项目/场景 在“高并发商品库存扣减”场景中,防止线程 A 更新数据库期间,线程 B 读取旧库存并写入缓存,通过第二次延时删除彻底清除可能残留的“脏库存”数据。


扩展解答

1. 为什么要延迟? 为了清除“并发读写”产生的脏数据。在“先删缓存 -> 更新数据库”的过程中,若不延迟,第二次删除可能在“读请求查询数据库并写入缓存”的动作完成之前就已经执行了,导致缓存中残留旧数据。延迟是为了让第二次删除动作发生在“旧数据回填缓存”之后

2. 延迟时间如何确定? 理论值:延迟时间 > 数据库查询耗时 + 缓存写入耗时(通常在毫秒级)。 实战建议:不宜设置过长(影响一致性),也不宜过短(无法覆盖慢查询)。一般推荐设置 200ms - 500ms,或根据业务 P99 耗时进行估算,并通过监控进行调优。