您所在的位置:首页 / 知识分享

关于Redis和mysql数据一致性

2023.03.02

255

ytj

Redis 具有高性能的数据读写功能,广泛应用于缓存场景。一是可以提高业务系统的性能,二是可以在高并发场景下做缓冲,缓解MySQL数据库的压力。 在使用过程中,我们经常会遇到一些场景,需要解决Redis和MySQL数据库之间的数据一致性问题。

什么是数据库和缓存一致性

数据一致性是指: 当缓存中有数据时,缓存的数据值等于数据库中的值。数据不在缓存中时,数据库中的值应该是最新的值。

缓存数据与数据库数据不一致的表现:缓存中的数据值不等于数据库中的值。 缓存或数据库中有旧数据,导致程序读取旧数据。


缓存使用策略


在使用缓存时,通常有以下缓存使用策略来提高系统性能:

Cache-Aside 旁路缓存模式

Read/Write-Through 读写穿透模式 同步更新缓存和数据库

Write-Behind 后写入模式 先更新缓存,批量异步更新数据库

cache aside

最常用的缓存策略,读取数据的逻辑如下:

当应用程序需要从数据库中读取数据时,它首先会检查缓存的数据是否命中。

如果缓存命中,则直接返回

如果缓存未命中,则查询数据库获取数据,同时将数据写入缓存,再次读取相同数据都会命中缓存

cache aside读取数据策略优势:

只有应用程序实际请求的数据才会存入缓存中,避免缓存空间浪费。

实现简单,并且可以获得性能提升。

缺点

由于数据仅在缓存未命中后才加载到缓存中,因此首次调用的数据请求的响应时间会相对较长 。


Read/Write-Through


Read/Write Through模式中,服务端把缓存作为主要数据存储。应用程序跟数据库缓存交互,都是通过抽象缓存层完成的。

Read-Through的流程其实和旁路缓存模式的读请求流程很像,就是多了一层Cache-Provider,实际只是在Cache-Aside之上进行了一层封装

Read-Through 实现了关注点分离的原则。代码只与缓存交互,缓存组件管理自身与数据库之间的数据同步。

与Read-Through类似,当有写入请求发生时,Write-Through将写入职责转移给缓存系统,缓存抽象层完成缓存数据和数据库数据的更新。

Write-Through的主要好处是应用系统不需要考虑故障处理和重试逻辑,交给缓存抽象层来管理实现。 直接使用这个策略是没有意义的,因为这个策略需要先写入缓存,再写入数据库,给写操作带来了额外的延迟。 当 Write-Through 与 Read-Through 结合使用时,可以充分发挥 Read-Through 的优势,同时保证数据的一致性,无需考虑如何使缓存设置失效。

Write-Behind


这张图看起来和 Write-Through 一样,其实不然,不同的是最后一个箭头 这意味着缓存系统会异步更新数据库数据,应用系统只与缓存系统交互。 应用程序不必等待数据库更新完成,从而提高了应用程序性能,因为对数据库的更新是最慢的操作。

该策略下缓存与数据库的一致性不强,不推荐使用 。


Cache Aside模式下的数据一致性问题分析


Cache-Aside 策略在业务场景中使用最多。在这种策略下,读操作不会导致缓存和数据库不一致。 重点是写操作。数据库和缓存都需要修改,两者之间会有先后顺序,可能导致数据不一致。

对于写作,我们需要考虑两个问题:

先更新缓存还是先更新数据库?

当数据发生变化时,选择修改缓存(update)还是删除缓存(delete)?

我们可以组合出四种解决方案来回答上述两个问题

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

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

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

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

1、先更新缓存,再更新数据库。

如果先更新缓存,写数据库失败,则缓存为最新数据,数据库为旧数据,缓存为脏数据。 之后其他查询马上进来就会拿到这个数据,但是这个数据在数据库中是不存在的。 数据库中不存在的数据缓存并返回给客户端是没有意义的。

2、先更新数据库,再更新缓存。

一切正常,如下所示:

先写数据库,成功。

然后更新缓存,成功。

如果这两个操作的原子性被打破了:也就是第一步成功,第二步失败,会发生什么?

此时数据库是最新数据,缓存是旧数据,导致一致性问题。 在高并发场景下,如果多个线程同时写入数据再写入缓存,肯定会出现缓存(旧值)和数据库(新值)不一致的情况。 

一致性解决方案

1.缓存延迟双删 (Cache delay double deletion)


先删除缓存

写数据库

休眠 500 毫秒,然后删除缓存

这样,读取脏数据的时间最多只有500毫秒。关键是如何确定睡眠时间? 延迟时间的目的是为了保证读请求结束,写请求可以删除读请求引起的缓存脏数据。 因此,我们需要自己评估项目的数据读取业务逻辑的耗时,在读取时间的基础上加上几百毫秒的延迟时间。

2. 删除缓存重试机制


缓存删除失败怎么办?比如延迟双删的第二次删除失败,说明脏数据无法删除。 使用重试机制保证缓存删除成功。 比如重试3次,失败3次,就会将日志记录到数据库中,并发出警告进行人工干预。 在高并发场景下,重试最好采用异步方式,比如向MQ中间件发送消息,实现异步解耦。

步骤(5)如果删除失败且未达到最大重试次数,则消息将重新排队,直到删除成功,否则将记录在数据库中以供人工干预。 这种方案的缺点是对业务代码造成了侵入,所以就有了下一个方案,启动一个专门订阅数据库bin-log的服务,读取要删除的数据,进行缓存删除操作。

3. 读取 bin-log 异步删除


更新数据库。

数据库会将操作信息记录在bin-log日志中。

使用 canal 订阅 bin-log 日志获取目标数据和密钥。

缓存删除系统获取canal数据,解析目标key,尝试删除缓存。

如果删除失败,将消息发送到消息队列。

缓存删除系统再次从消息队列中获取数据,再次执行删除操作。 

转载于:https://www.bilibili.com/read/cv17290487

相关新闻

Nanachi发布:基于 React 的多端小程序转译框架

2019.01.23

7580

自微信小程序出来后,互联网进入一个新的纪元。中国移动互联网再次三巨头切割,三大小程序加上以小米为首的快应用,割据这个大蛋糕。

Go time.Parse() 和time.Format()

2020.12.09

1227

在windows下,time.Parse()的时区和time.Format()的时区是一致的。