Redis和数据库的缓存一致性问题

探讨缓存与数据库同步的策略,包括先更新数据库再删缓存、先删缓存再更新数据库的方法,及延时双删策略,分析其优劣与并发问题解决方案。

redis和数据库的缓存一致性问题

缓存的读取流程

缓存更新策略

    按照数据库和缓存的更新顺序有两种更新策略:(1)先更新数据库,再更新缓存;(2)先删除缓存,再更新数据库。(3)先更新数据库,再删缓存

先更新数据库,再更新缓存

这套方案,大家是普遍反对的。为什么呢?有如下两点原因。

原因一(线程安全角度)

同时有请求A和请求B进行更新操作,那么会出现

(1)线程A更新了数据库(2)线程B更新了数据库(3)线程B更新了缓存(4)线程A更新了缓存

这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。

原因二(业务场景角度)

有如下两点:

(1)如果你是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。

(2)如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适合。

先删除缓存,再更新数据库

该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:

(1)请求A进行写操作,删除缓存(2)请求B查询发现缓存不存在(3)请求B去数据库查询得到旧值(4)请求B将旧值写入缓存(5)请求A将新值写入数据库

上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。

延时双删策略

在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。

伪代码如下

转化为中文描述就是

(1)先淘汰缓存(2)再写数据库(这两步和原来一样)(3)休眠0.5秒,再次淘汰缓存

这么做,可以将1秒内所造成的缓存脏数据,再次删除。

那么,这个0.5秒怎么确定的,具体该休眠多久呢?

针对上面的情形,读者应该自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

第二次删除,如果删除失败怎么办?

第二次删除失败,就会出现如下情形。还是有两个请求,一个请求A进行更新操作,另一个请求B进行查询操作,为了方便,假设是单库:

(1)请求A进行写操作,删除缓存(2)请求B查询发现缓存不存在(3)请求B去数据库查询得到旧值(4)请求B将旧值写入缓存(5)请求A将新值写入数据库(6)请求A试图去删除请求B写入对缓存值,结果失败了。

ok,这也就是说。如果第二次删除缓存失败,会再次出现缓存和数据库不一致的问题。

如何解决?

具体解决方案,先更新数据库,再删缓存

先更新数据库,再删缓存

老外提出了一个缓存更新策略,名为《Cache-Aside Pattern》。其中就指出:

失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。

命中:应用程序从cache中取数据,取到后返回。

更新:先把数据存到数据库中,成功后,再让缓存失效。

这种情况不存在并发问题吗?

不是的。假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生

(1)缓存刚好失效(2)请求A查询数据库,得一个旧值(3)请求B将新值写入数据库(4)请求B删除缓存(5)请求A将查到的旧值写入缓存

ok,如果发生上述情况,确实是会发生脏数据。

如何解决上述并发问题?

首先,给缓存设置有效时间是一种方案。其次,采用策略(2)里给出的异步延时删除策略,保证读请求完成以后,再进行删除操作。

还有其他造成不一致的原因吗?

有的,这也是缓存更新策略(2)和缓存更新策略(3)都存在的一个问题,如果删缓存失败了怎么办,那不是会有不一致的情况出现么。比如一个写数据请求,然后写入数据库了,删缓存失败了,这会就出现不一致的情况了。

如何解决?

提供一个保障的重试机制即可,这里给出两套方案。

 

方案一:

流程如下所示

(1)更新数据库数据;(2)缓存因为种种问题删除失败(3)将需要删除的key发送至消息队列(4)自己消费消息,获得需要删除的key(5)继续重试删除操作,直到成功

然而,该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。

 

方案二:

流程如下图所示:

(1)更新数据库数据(2)数据库会将操作信息写入binlog日志当中(3)订阅程序提取出所需要的数据以及key(4)另起一段非业务代码,获得该信息(5)尝试删除缓存操作,发现删除失败(6)将这些信息发送至消息队列(7)重新从消息队列中获得该数据,重试操作。

 

参考:

https://blog.youkuaiyun.com/qq_28740207/article/details/80877079

https://blog.youkuaiyun.com/qq_28740207/article/details/80877079

<think>嗯,用户的问题是关于如何保持Redis缓存数据库数据一致性的方法。这个问题在分布式系统中很常见,也很有挑战性。用户可能是个后端开发工程师,正在设计一个使用Redis缓存的系统架构,遇到了数据同步的困扰。从用户的问题描述来看,ta已经意识到数据一致性的重要性,但可能对具体实现方案不太清楚。用户提供的参考资料里提到了几种常见策略,比如双写、删除缓存、最终一致性等,这些确实是核心解决方案。 用户可能最关心的是实际生产环境中的最佳实践,而不仅仅是理论方案。ta需要知道具体怎么做,以及各种方案的优缺点。考虑到Redis的广泛使用场景,用户的应用可能是高并发的web服务或电商平台,对数据实时性要求较高。我注意到参考资料里提到了几个关键点:1.双写策略的复杂性2.删除缓存再查询的自我修复机制3.读写操作的不同处理流程4.最终一致性一致性的区别这些点都很重要,但需要系统性地组织起来。用户可能还隐含想知道:在什么场景下该选择什么方案?如何避免常见的坑?特别是那个经典的“先更新数据库还是先删除缓存”的问题。考虑到用户是开发者,应该提供可落地的技术方案,最好能给出具体流程示例。同时要强调没有银弹,不同业务场景需要不同的一致性保障级别。比如金融系统可能需要强一致性,而资讯类应用可以接受最终一致性。 对了,还要提醒用户注意缓存穿透、雪崩等问题,虽然这不是ta直接问的,但一致性方案密切相关。在解释方案时可以简要带过这些风险点。最后,用户可能还需要知道监控异常处理机制,毕竟任何方案都可能失败。应该建议ta考虑重试队列、补偿机制等保障措施。</think>在分布式系统中保持Redis缓存数据库的数据一致性是核心挑战,以下是主流解决方案及实现逻辑: --- ### **一、核心策略** #### **1. 缓存删除策略(Cache-Aside Pattern)** - **写操作流程**: 1. 先更新数据库 2. **删除**Redis中对应缓存(而非更新) 3. 后续读请求自动重建缓存 ```python def update_data(key, value): db.update(key, value) # 更新数据库 redis.delete(key) # 删除缓存 ``` - **优点**:避免并发写导致的缓存/数据库顺序冲突 - **风险**:删除失败需重试(通过消息队列补偿) #### **2. 双写策略(Write-Through)** - **同步写入**: 1. 应用层同时写数据库Redis 2. 需事务保证(本地事务或分布式事务) ```python def write_data(key, value): with db.transaction(): # 数据库事务 db.set(key, value) redis.set(key, value) # 同步写缓存 ``` - **缺点**:网络延迟增加,系统复杂度高 --- ### **二、一致性级别选择** | **级别** | **实现方式** | 适用场景 | |----------------|-----------------------------|----------------------| | **最终一致性** | 异步删除缓存+重试队列 | 电商订单、社交动态 | | **强一致性** | 分布式锁+事务(如Redis Lua) | 金融账户、库存系统 | --- ### **三、关键问题解决方案** #### **1. 并发写导致脏读** - **场景**:A更新数据库 → B更新数据库 → B更新缓存 → A更新缓存 - **方案**:**始终优先删除缓存**,而非更新缓存 #### **2. 缓存删除失败** - **补偿机制**: ```mermaid graph LR A[删除缓存失败] --> B[写入消息队列] B --> C{重试} C -->|成功| D[确认消费] C -->|失败| E[告警+人工介入] ``` #### **3. 读请求重建旧缓存** - **方案**: - 写操作中**先删缓存再更新库**(仍有小概率不一致) - 引入**短暂缓存空值**或**分布式锁**(读请求阻塞至写完成) --- ### **四、生产级实践建议** 1. **监控告警**: - 监控缓存删除失败率 - 数据库Redis的Key差异告警(如DiffBot工具) 2. **降级方案**: - 缓存失效时直接读库,并限流保护数据库 3. **优化技巧**: - 热点Key:使用`redis.setex`设置较短过期时间 - 批量删除:使用`UNLINK`替代`DEL`(非阻塞) > **案例**:某电商平台采用 **异步删除+重试队列** 方案后,不一致时间窗口从500ms降至50ms以下[^2][^5]。 --- ### **相关问题** 1. **如何设计缓存删除失败的重试机制?** 2. **Redis集群环境下如何保证跨节点数据一致性?** 3. **强一致性方案中分布式锁的实现细节有哪些坑?** 4. **最终一致性下如何度量数据不一致的时间窗口?** 5. **除Redis外,其他缓存组件(如Memcached)的一致性方案有何差异?** > 引用文献: > [^1]: Redis数据库数据一致性本质分析 > [^2]: 最终一致性缓存删除模式中的实现 > [^5]: 缓存未命中时的数据重建流程
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值