如何保证Redis与Mysql双写一致性?

2024-09-02 14:12

本文主要是介绍如何保证Redis与Mysql双写一致性?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

https://www.cnblogs.com/coderacademy/p/18137480

延迟双删

对于上面链接的文章,里面的延迟双删没有给出具体的例子,也没有直接指出具体解决的问题是针对那种缓存策略,这里补充一下,延时双删缓存针对的是Cache aside pattern(缓存旁路策略),处理的是在高并发读写同时存在的情况下可能会出现的问题,详细如下。

什么是延迟双删:

延时双删策略能够有效解决缓存和数据库之间的数据不一致问题。它的核心思想是在更新数据库之后,先删除缓存中的数据,延迟一段时间后再次删除缓存中的数据。其具体步骤如下:

  1. 更新数据库:先将数据更新到数据库中。
  2. 删除缓存:立即删除缓存中对应的旧数据。
  3. 延迟一段时间:等待一段时间(通常是足够长以保证并发写入完成的时间)。
  4. 再次删除缓存:再次删除缓存中的数据,以防止并发操作在缓存中留下旧数据。

tips:延迟双删需要由更新数据的那个程序去处理。

延时双删策略的应用场景

延时双删策略的应用场景通常涉及**高并发写操作**和**读取操作**同时发生的情况。以下是一个典型的具体场景,展示如何出现缓存和数据库数据不一致的问题。

### 场景描述

假设有一个电商网站,使用缓存(如 Redis)来加速商品信息的读取。例如,一个商品的库存信息存储在数据库中,同时也缓存到 Redis 中,以便快速读取。

#### 具体流程

1. **用户A请求读取商品库存信息:**
   - 用户A请求读取某个商品的库存信息,系统会优先从缓存(Redis)中读取该商品的库存。如果缓存中存在,则直接返回;如果不存在,则从数据库读取并将结果缓存起来。

2. **用户B请求更新商品库存信息:**
   - 在用户A读取缓存中的商品库存时,用户B执行了一个购买操作,该操作会更新商品的库存信息。系统会先更新数据库中的商品库存,然后删除缓存中的商品库存信息,以确保下一次读取时会从数据库中获取最新数据。

#### 问题出现

在用户B更新商品库存后,但**在删除缓存之后、更新数据库完成之前的时间窗口**内,用户A再次读取商品库存信息。这会出现以下问题:

- 用户B更新库存后,缓存被删除,但是用户A此时发起读取请求,因为缓存已经被删除,系统会去数据库中读取库存数据。
- 但数据库还没有完成更新操作(可能因为写入操作较慢,或者在执行事务),用户A读取到的仍然是旧的库存信息。
- 最后,数据库更新完成,数据正确,但用户A刚刚读取到了错误(旧)的库存数据,导致**数据不一致**。

### 延时双删策略如何解决这个问题

为了防止上述数据不一致情况的发生,可以使用延时双删策略:

1. **用户B更新商品库存信息:**
   - 更新数据库中的库存信息。
   - **立即删除缓存**中的商品库存信息。
   
2. **用户A读取商品库存信息:**
   - 如果在缓存被删除之后读取,系统会从数据库中读取。
   - 用户A读取时可能得到旧数据(数据库尚未更新完成),但接下来的延时操作将解决这个问题。
   
3. **延迟删除缓存:**
   - 设置一个延迟(例如 500 毫秒),在此延迟之后,再次尝试删除缓存中的商品库存信息。
   - 这段延迟时间应足够长,以确保数据库更新操作已经完成。

### 具体应用中的时间点

- **T1:用户B请求更新库存,系统开始更新数据库。**
- **T2:用户B更新数据库后,立即删除缓存。**
- **T3:数据库更新操作未完成,用户A读取库存,发现缓存不存在,转向读取数据库。**
- **T4:用户A读取到旧的库存数据(数据库写操作未完成)。**
- **T5:延迟一段时间(如500毫秒),再次删除缓存。**
- **T6:用户A再次请求,发现缓存不存在,此时数据库已更新,读取到最新库存信息。**

### 为什么这个策略有效

1. **避免读取旧数据**:通过在更新数据库后立即删除缓存,避免缓存中存在旧数据。
2. **降低不一致的窗口期**:延迟删除缓存提供了一个补偿机制,以防止在数据库写入完成前的缓存穿透现象。
3. **高效读取最新数据**:第二次删除缓存确保了之后的读取操作总能获取最新数据,减少了缓存和数据库之间的数据不一致的风险。

### 总结

延时双删策略主要解决的是在缓存删除和数据库更新之间的短时间窗口内出现数据不一致的问题,特别适用于**高并发写操作和读操作混合**的场景,如电商库存更新、金融交易系统的账户余额更新等。通过这种策略,系统能够更好地保障缓存和数据库之间的数据一致性。

延迟双删除golang代码示例

package mainimport ("context""fmt""time""go.mongodb.org/mongo-driver/bson""go.mongodb.org/mongo-driver/mongo""go.mongodb.org/mongo-driver/mongo/options"
)func main() {// 示例:更新数据库并执行延时双删策略// 示例的数据库操作ctx := context.Background()// 第一步:更新数据库updateDatabase(ctx)// 第二步:立即删除缓存deleteCache()// 第三步:设置一个延迟任务(如 500 毫秒)time.AfterFunc(500*time.Millisecond, func() {deleteCache() // 再次删除缓存})fmt.Println("Database update and delayed cache deletion scheduled.")
}func updateDatabase(ctx context.Context) {// 示例数据库更新操作fmt.Println("Updating database...")// 在此处执行数据库的更新操作...
}func deleteCache() {// 示例缓存删除操作fmt.Println("Deleting cache...")// 在此处执行缓存删除操作...
}

Read-Through 和Cache Aside Pattern的读有什么区别

**Read-Through** 和 **Cache Aside Pattern**(又称为 **Lazy Loading** 或 **Lazy Caching**)是两种常见的缓存策略,它们在缓存读取(读)操作的实现上有一些关键区别。

### Read-Through 缓存策略

**Read-Through** 是一种由缓存层自动管理数据加载的策略,读操作首先检查缓存,如果缓存中没有数据,缓存层会自动从后端数据源(例如数据库)加载数据并将其存储到缓存中,然后返回给调用者。

#### 工作原理

1. **应用程序请求数据**:
   - 应用程序向缓存层请求数据。
2. **缓存层检查数据**:
   - 如果缓存中有数据,直接返回。
   - 如果缓存中没有数据,缓存层会自动从后端数据源(如数据库)加载数据。
3. **缓存层更新缓存**:
   - 将从后端数据源获取的数据存储到缓存中,以便下次快速访问。
4. **返回数据**:
   - 缓存层返回数据给应用程序。

#### 特点

- **自动加载数据**:缓存层自动处理缓存未命中的情况,将数据从后端数据源加载到缓存中。
- **透明性**:应用程序不需要关心数据从哪里来,缓存层会自动管理。
- **常用于缓存代理中**:例如,使用特定的缓存中间件或服务来管理缓存。

#### 例子

如使用 AWS ElastiCache 或 Memcached 的集成模式,其中缓存中间件自动处理数据加载和缓存更新。

### Cache Aside Pattern 缓存策略

**Cache Aside Pattern**(Lazy Loading)是一种由应用程序主动管理缓存的数据加载的策略。应用程序在读取数据时首先检查缓存,如果缓存未命中,应用程序会主动从后端数据源加载数据并手动将数据写入缓存。

#### 工作原理

1. **应用程序请求数据**:
   - 应用程序首先检查缓存中是否有数据。
2. **缓存未命中时应用程序加载数据**:
   - 如果缓存中没有数据,应用程序从后端数据源(如数据库)加载数据。
3. **应用程序更新缓存**:
   - 应用程序将从后端数据源获取的数据写入缓存。
4. **返回数据**:
   - 应用程序返回数据给调用者。

#### 特点

- **主动管理缓存**:应用程序负责检查缓存、加载数据和更新缓存。
- **灵活性**:应用程序可以决定何时加载和更新数据,缓存的逻辑在应用程序中控制。
- **常用于手动控制缓存的场景**:例如,通过代码来管理缓存操作。

#### 例子

在 Go、Java 或 Python 应用中,程序员在业务逻辑中手动管理从缓存中读取数据,未命中时从数据库加载数据,并将其写入缓存的操作。

### 区别

| 特性                          | Read-Through                                          | Cache Aside Pattern                                 |
|-------------------------------|-------------------------------------------------------|-----------------------------------------------------|
| **数据加载责任**              | 缓存层自动负责从后端加载数据                           | 应用程序负责加载数据和更新缓存                      |
| **实现难度**                  | 较低,缓存层自动管理数据加载                           | 较高,应用程序需要管理缓存逻辑                      |
| **灵活性**                    | 较低,缓存策略由缓存层定义                             | 较高,应用程序可以控制何时加载和更新数据            |
| **常见使用场景**              | 通常用于缓存中间件或代理(如 Memcached, AWS ElastiCache)| 手动管理缓存的应用程序                              |
| **缓存未命中后的开销**        | 缓存层负责处理加载,应用程序不感知                      | 应用程序处理加载逻辑,有可能影响性能                |
| **读写操作的复杂度**          | 读操作简单,缓存层透明处理                             | 读操作复杂,需要在应用中显式处理缓存和数据库访问    |

### 总结

- **Read-Through** 更适合希望透明缓存管理的场景,使用缓存中间件或代理自动处理数据加载,简化应用逻辑。
- **Cache Aside Pattern** 更适合需要灵活控制缓存逻辑的场景,应用程序可以根据业务需求主动决定何时加载和更新缓存。

这篇关于如何保证Redis与Mysql双写一致性?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1130179

相关文章

mysql出现ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘localhost‘ (10061)的解决方法

《mysql出现ERROR2003(HY000):Can‘tconnecttoMySQLserveron‘localhost‘(10061)的解决方法》本文主要介绍了mysql出现... 目录前言:第一步:第二步:第三步:总结:前言:当你想通过命令窗口想打开mysql时候发现提http://www.cpp

MySQL大表数据的分区与分库分表的实现

《MySQL大表数据的分区与分库分表的实现》数据库的分区和分库分表是两种常用的技术方案,本文主要介绍了MySQL大表数据的分区与分库分表的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有... 目录1. mysql大表数据的分区1.1 什么是分区?1.2 分区的类型1.3 分区的优点1.4 分

MySQL错误代码2058和2059的解决办法

《MySQL错误代码2058和2059的解决办法》:本文主要介绍MySQL错误代码2058和2059的解决办法,2058和2059的错误码核心都是你用的客户端工具和mysql版本的密码插件不匹配,... 目录1. 前置理解2.报错现象3.解决办法(敲重点!!!)1. php前置理解2058和2059的错误

Mysql删除几亿条数据表中的部分数据的方法实现

《Mysql删除几亿条数据表中的部分数据的方法实现》在MySQL中删除一个大表中的数据时,需要特别注意操作的性能和对系统的影响,本文主要介绍了Mysql删除几亿条数据表中的部分数据的方法实现,具有一定... 目录1、需求2、方案1. 使用 DELETE 语句分批删除2. 使用 INPLACE ALTER T

MySQL INSERT语句实现当记录不存在时插入的几种方法

《MySQLINSERT语句实现当记录不存在时插入的几种方法》MySQL的INSERT语句是用于向数据库表中插入新记录的关键命令,下面:本文主要介绍MySQLINSERT语句实现当记录不存在时... 目录使用 INSERT IGNORE使用 ON DUPLICATE KEY UPDATE使用 REPLACE

MySQL Workbench 安装教程(保姆级)

《MySQLWorkbench安装教程(保姆级)》MySQLWorkbench是一款强大的数据库设计和管理工具,本文主要介绍了MySQLWorkbench安装教程,文中通过图文介绍的非常详细,对大... 目录前言:详细步骤:一、检查安装的数据库版本二、在官网下载对应的mysql Workbench版本,要是

mysql数据库重置表主键id的实现

《mysql数据库重置表主键id的实现》在我们的开发过程中,难免在做测试的时候会生成一些杂乱无章的SQL主键数据,本文主要介绍了mysql数据库重置表主键id的实现,具有一定的参考价值,感兴趣的可以了... 目录关键语法演示案例在我们的开发过程中,难免在做测试的时候会生成一些杂乱无章的SQL主键数据,当我们

浅谈mysql的sql_mode可能会限制你的查询

《浅谈mysql的sql_mode可能会限制你的查询》本文主要介绍了浅谈mysql的sql_mode可能会限制你的查询,这个问题主要说明的是,我们写的sql查询语句违背了聚合函数groupby的规则... 目录场景:问题描述原因分析:解决方案:第一种:修改后,只有当前生效,若是mysql服务重启,就会失效;

MySQL多列IN查询的实现

《MySQL多列IN查询的实现》多列IN查询是一种强大的筛选工具,它允许通过多字段组合快速过滤数据,本文主要介绍了MySQL多列IN查询的实现,具有一定的参考价值,感兴趣的可以了解一下... 目录一、基础语法:多列 IN 的两种写法1. 直接值列表2. 子查询二、对比传统 OR 的写法三、性能分析与优化1.

MySQL新增字段后Java实体未更新的潜在问题与解决方案

《MySQL新增字段后Java实体未更新的潜在问题与解决方案》在Java+MySQL的开发中,我们通常使用ORM框架来映射数据库表与Java对象,但有时候,数据库表结构变更(如新增字段)后,开发人员可... 目录引言1. 问题背景:数据库与 Java 实体不同步1.1 常见场景1.2 示例代码2. 不同操作