如何保证数据库、缓存的双写一致?

前言

在我们日常研发过程中,由于数据库的一些限制,我们经常使用缓存(如:Redis)来提升访问速率。此时,数据库和缓存双写数据就存在一致性问题,这个问题跟开发语言无关,在高并发场景下,问题更加严重。
另外,在面试、工作中也会经常遇到这个问题。所以这里跟大家一起探讨下数据库和缓存双写一致性问题的解决文案。

常见方案

通常,我们使用缓存的主要目的就是为了提升查询性能。所以,我们一般这样使用缓存:

用户请求
查询缓存
是否存在?
返回
查询数据库
是否存在?
放入缓存
  1. 用户请求数据,先查询缓存中是否有相关数据,如果有则直接返回
  2. 如果缓存没数据,再继续查询数据库
  3. 如果数据库有相关数据,则将查询出来的数据放入缓存中,然后返回该数据
  4. 如果数据库也没数据,则直接返回空

这是缓存的常见用法,粗看之下,好像没啥问题。但这个方案忽略了一个非常重要的细节:如果数据库中的某条数据放入缓存后又立即更新了,那么如何更新缓存呢?
答案是:在很长的一段时间内(取决于缓存的过期时间),用户请求从缓存中取到的数据都可能是旧值,而非数据库的最新值。

更新缓存的方案

那该如何主动更新缓存呢?有以下四种方案:

  • 先写缓存,再写数据库
  • 先写数据库,再写缓存
  • 先删缓存,再写数据库
  • 先写数据库,再删缓存
    接下来,我们分别探讨下这四种方案

1. 先写缓存,再写数据库

很多人第一想法是在写操作中直接更新缓存(写缓存),直接明了。那么问题是:在写操作中,先写缓存,还是先写数据库呢?

缓存数据库场景中,保证数据一致性是一个常见但复杂的挑战。由于缓存数据库是两个独立的存储系统,操作无法通过单一事务保证原子性,因此需要采取一些策略来降低数据不一致的风险。以下是几种常见的解决方案: ### 1. 先更新数据库,再更新缓存(Cache Aside 模式) 这是最常见的一种做法。应用首先更新数据库中的数据,待数据库更新成功后,再更新缓存。这样可以保证缓存中的数据最终与数据库保持一致。 ```python # 伪代码示例 def update_data(key, new_value): # 1. 更新数据库 db.update(key, new_value) # 2. 更新缓存 cache.set(key, new_value) ``` 但这种方式在极端情况下可能出现不一致问题。例如,在更新数据库后、更新缓存前发生异常,缓存中的旧数据仍存在,直到下一次读取触发更新。 ### 2. 先缓存,再更新数据库(Read Through + Delete) 在更新数据库之前,先缓存中的旧值。当后续读取请求到来时,发现缓存中没有数据,会从数据库中加载最新数据并重新缓存。 ```python def update_data(key, new_value): # 1. 缓存 cache.delete(key) # 2. 更新数据库 db.update(key, new_value) ``` 这种方式可以避免缓存中存在过期数据的问题,但可能在并发场景下导致缓存穿透,即多个请求同时访问缓存未命中,进而频繁访问数据库。 ### 3. 使用消息队列异步更新缓存(Eventual Consistency) 通过引入消息队列(如 Kafka、RabbitMQ),将数据库更新操作作为事件发布到队列中,由消费者异步更新缓存,实现最终一致性。 ```python def update_data(key, new_value): # 1. 更新数据库 db.update(key, new_value) # 2. 发送更新事件到消息队列 message_queue.publish("cache_update", {"key": key, "value": new_value}) ``` 消费者端: ```python def consume_message(message): key = message["key"] value = message["value"] cache.set(key, value) ``` 这种方式可以解耦数据库缓存更新操作,适用于对一致性要求不是实时的场景。 ### 4. 使用分布式事务或两阶段提交(2PC) 在某些对数据一致性要求极高的系统中,可以考虑使用分布式事务(如 Seata、XA 协议)来保证数据库缓存一致性。但这类方案通常性能开销较大,适用于金融类等对一致性要求非常高的系统。 ### 5. 使用缓存中间件的穿透机制(Write Through) 某些缓存系统(如 Redis + CacheLib)支持 Write Through 模式,即缓存层在入时自动同步数据库。这种方式将一致性逻辑封装在缓存层内部,简化了应用层的处理。 --- ### 总结 不同场景下可选择不同的策略: - **低一致性要求**:使用消息队列实现最终一致性。 - **中等一致性要求**:采用 Cache Aside 或 Read Through + Delete 模式。 - **高一致性要求**:使用分布式事务或专门的缓存穿透机制。 每种方案都有其适用场景和局限性,实际应用中应根据业务需求、性能约束和系统复杂度进行权衡选择。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值