Redis的缓存与数据库双写不一致问题

本文探讨了Redis缓存与数据库双写不一致的问题,分析了CacheAsidePattern模式下的读写操作,并针对可能产生的不一致性提出了三种解决方案:消息队列串行化、加分布式锁及使用阿里开源的Canal。

一、Cache Aside Pattern

在了解这个问题之前,我们有必要知道缓存+数据库读写数据的模式,也就是Cache Aside Pattern

(1)读的时候:先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应;
(2)写的时候:先更新数据库,然后再删除缓存。
写的时候模式分析为什么是删除缓存,而不是更新缓存?
①假设我们有10次写,若是更新缓存的话需要更新10次缓存,10次数据库,我们优化为只需要删除一次缓存,更新10次数据库,当有一个读请求过来只是没有命中,去数据库读取一下,这样对性能的影响也不是很大;
②这是 一个 lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。像mybatis,hibernate,都有懒加载思想。

二、缓存与数据库双写不一致

1、双写不一致情况

线程1在写数据库与更新缓存之间卡顿了一下,然后线程2在线程1卡顿的这个空隙去写了数据库并刷新了缓存,然后线程2都已经执行完了,线程1又把脏数据更新到了缓存,造成了数据库与缓存不一致。
在这里插入图片描述

2、读写并发不一致

先更新写操作的,由于中间网络问题或者什么问题造成更新数据推后,最后造成了脏数据的更新,导致数据库与缓存双写不一致。
在这里插入图片描述

三、解决方案

(1)对于并发几率很小的数据(如个人维度的订单数据、用户数据等),这种几乎不用考虑这个问题,很少会发生缓存不一致,可以给缓存数据加上过期时间,每隔一段时间触发读的主动更新即可。
(2)就算并发很高,如果业务上能容忍短时间的缓存数据不一致(如商品名称,商品分类菜单等),缓存加上过期时间依然可以解决大部分业务对于缓存的要求。

1、消息队列串行化

(1)主要思路:在后台进程中我们可以创建多个队列,然后根据hash算法将写请求路由到不同的队列中,当来读请求的时候,就加入队列中,当写请求处理完毕后,再去处理读请求。
(2)分析:如果对于同一份数据有多个写请求同时在队列中,那么来一个读请求中加入队列中之后,一般写请求耗时比较久,那么读请求会需要很久才能返回,这样会特别影响性能,但能保证一致性(一般情况下建议不要用)

2、加分布式锁

通过加分布式读写锁保证并发读写写写的时候按顺序排好队读读的时候相当于无锁

3、用阿里开源的canal

可以用阿里开源的canal通过监听数据库的binlog日志及时的去修改缓存,但是引入了新的中间件,增加了系统的复杂度
在这里插入图片描述

### 三级标题:Redis 缓存数据库数据一致时的处理方法 在 Redis 缓存数据库组合使用过程中,数据一致问题可能导致业务逻辑错误,例如库存信息准确、订单状态异常等。缓存数据库一致通常发生在操作之后,由于缓存更新滞后或失败,导致缓存中的数据数据库中的真实数据同步[^1]。 为了解决这一问题,可以采用以下几种策略: #### 1. 穿透(Write Through) 穿透策略是指在更新数据库的同时更新缓存,确保缓存数据库始终保持一致。该策略适用于操作频繁且对数据一致性要求较高的场景。然而,这种策略可能导致缓存更新失败时需要额外的重试机制或事务支持[^3]。 ```java // 示例代码:穿透策略 public void updateData(Data data) { // 更新数据库 database.update(data); // 更新缓存 redis.set(data.getKey(), data.getValue()); } ``` #### 2. 延迟删(Delayed Double Delete) 延迟删是一种常用的缓存更新策略,主要针对读多少的场景。具体流程为:在更新数据库前删除缓存,更新完成后再次删除缓存,确保下一次读取时能够从数据库中加载最新数据并重新缓存。该策略通过删除缓存的方式避免缓存中出现旧数据[^1]。 ```java // 示例代码:延迟删策略 public void updateData(Data data) { // 删除缓存 redis.delete(data.getKey()); // 更新数据库 database.update(data); // 延迟一段时间后再次删除缓存 scheduleTask(() -> redis.delete(data.getKey()), 1000); } ``` #### 3. 异步更新(Cache Aside) 异步更新策略是指在读取缓存时发现数据存在,从数据库中加载后缓存;在更新数据库时删除缓存,由下一次读取触发缓存更新。这种策略适用于读操作远多于操作的场景,能够有效减少缓存更新的开销。但需要注意,异步更新可能导致短时间内缓存数据库数据一致[^2]。 ```java // 示例代码:异步更新策略 public Data getData(String key) { Data data = redis.get(key); if (data == null) { // 缓存存在,从数据库加载 data = database.get(key); // 缓存 redis.set(key, data); } return data; } public void updateData(Data data) { // 更新数据库 database.update(data); // 删除缓存 redis.delete(data.getKey()); } ``` #### 4. 消息队列机制 通过引入消息队列(如 Kafka、RabbitMQ 等),可以将数据库更新操作和缓存更新操作解耦,确保缓存数据库数据的一致性。数据库更新完成后,将更新事件发送到消息队列,由消费者负责更新缓存。这种方式可以降低数据一致的风险,但需要处理消息丢失或重复消费的问题[^3]。 ```java // 示例代码:消息队列机制 public void updateData(Data data) { // 更新数据库 database.update(data); // 发送更新事件到消息队列 messageQueue.send("data_updated", data); } // 消费者处理缓存更新 public void consumeMessage(Message message) { Data data = message.getData(); redis.set(data.getKey(), data.getValue()); } ``` #### 5. Redis 事务支持 Redis 提供了事务支持(如 `MULTI` 和 `EXEC` 命令),可以将多个缓存操作打包执行,确保缓存更新的原子性。虽然 Redis 事务无法完全解决缓存数据库一致问题,但在某些场景下可以作为辅助手段[^3]。 ```java // 示例代码:Redis 事务支持 public void updateCacheWithTransaction(Data data) { redis.multi(); redis.delete(data.getKey()); redis.set(data.getKey(), data.getValue()); redis.exec(); } ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值