当前位置: 首页 > article >正文

Redis——双写一致性

文章目录

  • 1. 问题介绍
    • 1.1 定义
    • 1.2 起因
  • 2. 解决方案
    • 2.1 方案一:延迟双删
      • 2.1.1 思想
      • 2.1.2 实现方式
      • 2.1.3 存在的问题
      • 2.1.4 优点
      • 2.1.5 缺点
      • 2.1.6 适用场景
    • 2.2 方案二:使用 Canal 监听 MySQL 从节点的数据变化
      • 2.2.1 介绍
      • 2.2.2 工作原理
      • 2.2.3 解决双写不一致的思想
      • 2.2.4 优点
      • 2.2.5 缺点
      • 2.2.6 适用场景
    • 2.3 方案三:读写锁
      • 2.3.1 思想
      • 2.3.2 做法
      • 2.3.3 优点
      • 2.3.4 缺点
      • 2.3.5 适用场景
  • 3. 总结


1. 问题介绍

1.1 定义

在 Redis 中,双写一致性 指的是当数据更新时,既要更新数据库中的数据,又要更新 Redis 中的数据,并且要保证这两份数据的一致性。相对而言,双写不一致 就指的是数据更新后,数据库中的数据和缓存中的数据不一致。

1.2 起因

更新操作 和 查询操作 可能是并发的,从而导致在更新操作删除 Redis 的旧数据之后,查询操作再次将旧数据缓存到 Redis 中,从而造成两份数据不一致。

假设查询的流程如下:

Created with Raphaël 2.3.0 开始 从 Redis 中读取 Redis 中的数据是否为 null 从数据库中读取 数据库中的数据是否为 null 返回数据 结束 将数据库中的数据缓存到 Redis 中 yes no yes no

更新有两种方案:

  • 第一种方案:先删除缓存,再更新数据库。
  • 第二种方案:先更新数据库,再删除缓存。

对于 查询 和 更新方案一 的并发执行,如果按照如下的时序图,则会缓存旧数据:

更新操作 数据库 Redis 查询操作 删除缓存 查询缓存 发现缓存为 null,查询数据库 由于某些操作浪费了一些时间 更新数据库 缓存旧数据 更新操作 数据库 Redis 查询操作

对于 查询 和 更新方案二 的并发执行,如果按照如下的时序图,则会缓存旧数据:

更新操作 数据库 Redis 查询操作 查询缓存 发现 缓存为 null,查询数据库 更新数据库 删除缓存 由于某些操作浪费了一些时间 缓存旧数据 更新操作 数据库 Redis 查询操作

上面这两种情况在生产中 都有可能发生,双写一致性就是要避免这个问题。

2. 解决方案

2.1 方案一:延迟双删

2.1.1 思想

如果更新操作在完成更新数据库和删除缓存 之后再删除一遍缓存,那么就能解决这个问题,从而得出 延迟双删 的解决方案:在更新操作的最后一步执行延迟删除缓存的操作。

2.1.2 实现方式

  • 通过 ScheduledExecutorServiceschedule() 实现:在更新操作的末尾,使用 ScheduledExecutorServiceschedule(Runnable command, long delay, TimeUnit unit) 方法,指定时间延迟删除 Redis 中的缓存。
  • 通过消息队列的延迟消息实现:在更新操作的末尾,生产一条删除 Redis 中指定缓存的延迟消息,然后让消费者去消费这条消息,删除 Redis 中指定的缓存。

2.1.3 存在的问题

1s 之后再次删除可以避免绝大多数双写不一致问题,因为很少有查询操作的时间会超过 1s。但由于生产中的 MySQL 往往是以集群模式部署的,会有 主从同步 的时间消耗,如果在从节点没有更新数据之前执行查询操作,就会读到旧数据,这时可以相对增加一点延迟时间,比如延迟 3s 后再次删除。所以 延迟双删有双写不一致的风险

2.1.4 优点

  • 性能高:由于读和写是并发的,所以性能会很高。
  • 实现比较简单:由于可以使用定时任务实现,所以实现比较简单。
  • 保证了数据的最终一致性:由于延迟删除缓存,所以缓存中的数据和数据库中的数据最终是一致的。

2.1.5 缺点

  • 无法保证数据的强一致性:由于 延迟删除缓存的时刻 可能与 数据更新完毕(主从同步之后)的时刻 间隔了不少时间,在这期间数据的一致性无法保障。

2.1.6 适用场景

本方案适用于 允许数据短暂不一致、对性能要求较高 的场景,大多数生产场景都是如此,比如文章浏览量的更新。

2.2 方案二:使用 Canal 监听 MySQL 从节点的数据变化

2.2.1 介绍

Canal 是阿里巴巴开源的一个基于 MySQL 数据库增量日志解析的中间件,它提供了增量数据订阅和消费的功能,主要用于捕获数据库数据变更,将其发送给其他系统进行处理。

2.2.2 工作原理

类似于 MySQL 的 主从复制 机制:将主数据库的 binlog (二进制日志) 传输到从数据库,从数据库根据 binlog 修改数据,从而实现数据的同步。

Canal 也是解析 MySQL 的 binlog,不过它不是用于数据同步到另一个数据库,而是把变更数据以消息的形式发送给下游的应用程序。

2.2.3 解决双写不一致的思想

当 MySQL 从节点中的数据被更新时,Canal 通知 Redis 删除缓存。

2.2.4 优点

  • 无代码侵入性:其它方案多少都需要在更新操作中添加代码,而使用本方案无需在更新操作中添加代码。
  • 数据的一致性相较方案一更强:当 MySQL 从节点中的数据被更新时,Canal 通知 Redis 删除缓存,这样依赖,本方案的删除时刻 就会比 方案一中延迟删除时刻 早一点,删除时刻 是 数据更新完毕(主从同步之后)的时刻。
  • 数据库的压力小:Canal 通过直接解析 MySQL 的 binlog 文件来获取数据变更,避免了频繁地查询数据库表,减少了对数据库的压力。

2.2.5 缺点

  • 数据一致性仍不是很强:虽然本方案对比方案一提升了数据的一致性,但在 主节点修改数据 到 从节点同步数据 的时间段内,数据仍是不一致的。
  • 配置和管理复杂:Canal 的配置相对复杂,需要对 MySQL 的 binlog 配置、Canal 自身的服务器配置、客户端配置等多个方面进行正确设置。

2.2.6 适用场景

本方案也适用于 允许数据短暂不一致、对性能要求较高 的场景。

2.3 方案三:读写锁

2.3.1 思想

既然查询和更新操作并发会影响数据的一致性,那么直接禁止查询和更新操作并发即可,这时就可以给查询操作加上 读锁,给更新操作加上 写锁

以下是读锁和写锁的特性:

  • 读锁:共享锁,只会与排他锁发生排斥,与共享锁不会发生排斥。
  • 写锁:排他锁,与所有锁发生排斥。

它们之间的排斥关系如下表所示:

排斥关系读锁写锁
读锁不排斥排斥
写锁排斥排斥

这样一来,查询操作可以并发,但会被更新操作阻塞,从而避免了双写不一致的问题。

2.3.2 做法

使用 Redisson 框架提供的读写锁,代码如下所示:

RReadWriteLock rwLock = redisson.getReadWriteLock("lock");	// 获取读写锁
RLock readLock = rwLock.readLock();							// 从读写锁中获取读锁
readLock.lock();											// 使用读锁
try {
    // 执行查询操作
} finally {
    readLock.unlock();										// 释放读锁
}
RReadWriteLock rwLock = redisson.getReadWriteLock("lock");	// 获取读写锁
RLock writeLock = rwLock.writeLock();						// 从读写锁中获取写锁
writeLock.lock();											// 使用写锁
try {
    // 执行更新操作
} finally {
    writeLock.unlock();										// 释放写锁
}

注意:获取读锁和写锁之前都需要先获取读写锁,而且读写锁的键必须一致。

2.3.3 优点

  • 保证了数据的强一致性:查询操作和更新操作不是并发的,从根源上避免了双写不一致的问题。

2.3.4 缺点

  • 性能相对较差:由于查询操作和更新操作是相互阻塞的,但查询操作却是可以并发的,所以性能相对较差。
  • 对代码的侵入性比较大:相对于方案二 (无侵入) 和方案一 (只在更新操作的末尾加了一段代码),本方案要求在查询时获取读锁,在更新时获取写锁,对代码的侵入性比较大。

2.3.5 适用场景

本方案适用于 允许性能不是很高、要求数据强一致 的场景,尤其是与钱相关的业务。

3. 总结

Redis 中,双写不一致问题发生在 查询操作 和 更新操作 并发的时候,当更新操作只删除一次缓存时,查询操作可能会把旧数据缓存起来,从而导致双写不一致。

解决方案主要有三种:

  • 延迟双删:在删除一遍缓存后,间隔一段时间再次删除缓存。两次删除间隔的时间段内,查询到的所有数据都是旧数据。
  • 使用 Canal 监听 MySQL 从节点的数据变化:使用阿里巴巴开发的 Canal 中间件,监听 MySQL 从节点的数据变化,变化之后通知 Redis 删除数据。在 主节点更新数据 到 从节点同步数据后通知 Redis 删除数据 的时间段内,查询到的所有数据都是旧数据。
  • 读写锁:给查询操作加上读锁,给更新操作加上写锁,从根源上避免读写并发问题。保证了数据的强一致性,但相对前两种方案,性能比较低。

http://www.kler.cn/a/452351.html

相关文章:

  • 使用Excel制作通达信自定义“序列数据“
  • 「Python数据科学」标量、向量、矩阵、张量与多维数组的辨析
  • 网络安全加密
  • 傅里叶变换原理
  • RK3506开发板:智能硬件领域的新选择,带来卓越性能与低功耗
  • 抓取手机HCI日志
  • 【iOS安全】Block开发与逆向
  • jupyter-lab与实验室服务器远程链接
  • 设计模式——工厂方法模式
  • sqli-labs关卡记录13
  • 代码随想录算法训练营第51期第28天 | 122. 买卖股票的最佳时机 II、55. 跳跃游戏、45. 跳跃游戏 II、1005.K次取反后最大化的数组和
  • 实现用户登录系统的前后端开发
  • 第4章 函数
  • mysql主从同步延迟原因分析
  • 当代体育科技杂志当代体育科技杂志社当代体育科技编辑部2024年第33期目录
  • 【Java-tesseract】OCR图片文本识别
  • 微调 BERT:实现抽取式问答
  • 4G云网络广播系统
  • 替换 Docker.io 的 Harbor 安全部署指南:域名与 IP 双支持的镜像管理解决方案
  • 湖南引力:低代码助力实现智慧养老管理系统
  • Ubuntu重命名默认账户
  • YoloV9改策略:卷积改进|SAC,提升模型对小目标和遮挡目标的检测性能|即插即用
  • 操作系统课程设计
  • 通过opencv加载、保存视频
  • React 脚手架使用指南
  • mac系统升级后Homebrew:Mac os 使用brew工具时报错No remote ‘origin‘