Redis和MySQL的数据一致性问题思考

Redis和MySQL的数据一致性问题思考

最近有在反思自己工作。因为自己这边是面向业务的,而且是和商品数据相关的。所以我平时工作中涉及到的最多的就是MySQL和Redis的数据存储。像我们配置商品是把商品配置到MySQL,但是对外toC接口都是直接读取Redis的。所以自然而然就涉及到MySQL和Redis的数据一致性问题。下面就是聊聊我自己对于这个问题的一个思考吧。有问题或者有更好方案的朋友也希望可以在评论里点出。

在互联网搜索这个问题很容易就看到概念性的经典方案,比如下面的三个经典缓存模式,我之前是没在意过这些的,但是在学习思考的过程中确实觉得有些概念或者有些名词大家可以了解一下。我这里是简单的总结了一下,具体的内容可以参考我看的那篇帖子https://juejin.cn/post/6964531365643550751,他总结的很详情
呐就是下面的三个经典缓存模式

三个经典缓存模式

  1. Cache-Aside(旁路缓存)
    即读取缓存、读取数据库和更新缓存的操作都在应用系统来完成。(业务最常用的缓存策略)
    • 读流程:服务读取数据先读缓存,缓存命中的话,直接返回数据。没命中,读库写回缓存并返回数据。
    • 写流程:服务写数据先写数据库,再删除缓存。
  2. Read- Through/Write- Through(读写穿透)
    即服务端把缓存作为主要数据存储。应用程序跟数据库缓存交互,都是通过抽象缓存层完成的。
    • 读流程(Read- Through):服务读取数据先读缓存,缓存命中的话,直接返回数据。没命中,读库写回缓存并返回数据。
      这里的读流程可以说是和上面一模一样了!那搁这还说啥呢??
      其实这里强调的是在网关层和存储层之间增加了一个缓存层,也就是Read-Through实际只是在Cache-Aside之上进行了一层封装
      就是上面的是应用程序->缓存->MySQL
      下面的是应用程序->Cache Provider -> 缓存->MySQL
    • 写流程(Write-Through):当发生写请求时,也是由缓存抽象层完成数据源和缓存数据的
      应用程序->Cache Provider -> 缓存 && Cache Provider->MySQL
  3. Write behind (异步缓存写入)
    即服务端把缓存作为主要数据存储。应用程序跟数据库缓存交互,通过抽象缓存层完成,其中写操作是只更新缓存,不直接更新数据库,对于数据库的更新采用批量异步方式来更新数据库。
    Write behind跟Read-Through/Write-Through都是由Cache Provider来负责缓存和数据库的读写。
    不同
    Read/Write Through是同步更新缓存和数据的
    Write Behind则是只更新缓存,不直接更新数据库,通过批量异步的方式来更新数据库。
    这种方式下,缓存和数据库的一致性不强,对一致性要求高的系统要谨慎使用。但是它适合频繁写的场景,MySQL的InnoDB Buffer Pool机制就使用到这种模式。

Redis和MySQL一致性的两种场景和解决方案

Redis和MySQL的数据一致性我认为还是要结合业务需求,我这里分为两个场景

  • 第一种情况是接口获取数据以MySQL数据为主,这种情况就是指接口数据获取时先读Redis,如果Redis中数据获取失败就要去读MySQL这种情况。(读写以MySQL数据为主)
  • 第二种情况是接口获取数据以Redis数据为主,这种情况就是指接口获取数据时只读Redis,如果Redis中数据不存在,那么就返回获取数据失败。(写以MySQL为主,读以Redis数据为主)

第一种情况:读写以MySQL数据为主

这种业务场景下一般使用Cache-Aside模式。就是读先读Redis、再读MySQL;写先写MySQL,再删除Redis。
注意这里是删除Redis。原因就是考虑到多个线程写的时候先更新MySQL的线程后更新了Redis,导致Redis中的数据是旧数据(脏数据),个人认为主要是为了解决这种问题。当然了删除Redis就意味着我们选择的是延迟加载的方式。所以对于更新Redis就还存在下面另外个优点:就是延迟加载在下一次有读请求时才会执行更新操作,如果更新Redis的计算是复杂逻辑会在写多读少的情况下减少更新频率,节省了性能损耗。
上面对于Redis和MySQL双写的顺序是先写MySQL再写Redis,如果我想先写Redis呢?
当然也可以先操作Redis,但是这样的话就可能存在多线程下,一个写线程先删了Redis,另一个读线程在这个情况下没读到Redis,就把MySQL中的数据写入到Redis中了。然后线程A写入MySQL中新数据,所以这样MySQL和Redis就数据不一致了。。。。对于这种就要考虑延迟双删机制。
解决方案:

  1. 同步:
    1. 同步双写。Redis和MySQL的更新操作放在同一事务中,整个成功整个失败,保证双写完全一致,此时先更新MySQL和先更新Redis都可以。不过一般以MySQL为主的话还是会优先先更新MySQL中的数据
  2. 异步(这里虽然是同步去触发更新,但是不放在一个事务内,不能保证原子性,就归属于异步更新,当然触发也可以具体选择同步触发还是异步触发的方式)
    1. Cache-Aside(旁路缓存)。先写MySQL,再删Redis
    2. 缓存延迟双删。先删Redis,再写MySQL,再删Redis(这里二次删除Redis的过程应该采用sleep休眠一会再删除或者使用延迟队列进行延迟删除缓存)。
    3. 监听binlog。通过监听MySQL的binlog触发异步删除(如使用阿里的canal将binlog日志采集发送到MQ队列里面,然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性。)
  3. 补偿
    1. 缓存失败重试机制
      写请求更新数据库,缓存因为某些原因删除失败,把删除失败的key放到消息队列,消费消息队列的消息,获取要删除的key,重试删除缓存操作

第二种情况:写以MySQL为主,读以Redis数据为主

这种业务情况是指写操作以MySQL为主,即写操作的时候先写MySQL再写Redis,读数据以Redis中的结果为主,如果Redis中有接口就返回数据,如果Redis中不存在数据,就直接返回数据不存在。所以这个情况下就不能删除Redis了,应该采用更新Redis。更新Redis可以保证有效数据在Redis中是永远存在的。那么这种情况下MySQL如何保证和Redis数据一致性呢?
解决方案:

  1. 同步:
    1. 同步双写。Redis和MySQL的更新操作放在同一事务中,整个成功整个失败,保证双写完全一致。(本地事务或分布式事务)
  2. 异步(这里虽然是同步去触发更新,但是不放在一个事务内,不能保证原子性,就归属于异步更新,当然触发也可以具体选择同步触发还是异步触发的方式)
    1. MQ消息。MySQL更新完成后直接发送异步MQ消息,然后通过消费消息实现Redis数据变更
    2. 监听binlog。通过监听MySQL的binlog触发异步更新(如使用阿里的canal将binlog日志采集发送到MQ队列里面,然后通过ACK机制确认处理这条更新消息,更新缓存,保证数据缓存一致性。)
    3. 数据库触发器。在MySQL中设置触发器,当数据库发生变化时触发相应的操作,将变化的数据同步到Redis中。通过在MySQL中设置触发器,可以在数据发生变化时立即同步更新Redis。
    4. 数据变更日志。记录数据变更日志,将数据变更操作写入日志文件或者数据库中,然后通过定时任务或者实时监控方式将变更的数据同步更新到Redis中。(这个方法实时监控类似监听binlog方式,定时任务的方式就类似于下面提到的补偿机制)
  3. 补偿(在上面异步触发更新的方式中,存在更新完MySQL,但是更新Redis失败的情况,这种情况下,就要考虑补偿更新)
    1. 缓存失败重试机制。
      1. 更新Redis失败后同步执行重试,如果重试成功即更新成功,失败可以采用领起线程重试或下面其他方案。
      2. 更新失败也则可采用另起一个线程,再进行有限次数的重试,重试成功即更新成功,失败则写入MQ或log表记录等。
      3. 更新Redis失败后把更新失败的key放到消息队列,通过消费消息队列要更新的key,达到重试更新Redis操作。
      4. 更新Redis失败后把更新失败的数据记录到日志中,日志可以是日志文件或数据库表,然后通过监控方式再补偿更新。
    2. 定时任务定时补偿机制。
      1. 定时将MySQL中的全量数据更新到Redis中。这里的定时可以采用全量定时和增量定时两种定时更新方式。
      2. 更新Redis失败后把更新失败的数据记录到日志中,定时任务扫描实现失败数据补偿更新。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/778114.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何快速搭建一个ELK环境?

前言 ELK是Elasticsearch、Logstash和Kibana三个开源软件的统称,通常配合使用,并且都先后归于Elastic.co企业名下,故被简称为ELK协议栈。 Elasticsearch是一个实时的分布式搜索和分析引擎,它可以用于全文搜索、结构化搜索以及分…

JavaScript中 数组API

在JavaScript中,数组是一种非常常用的数据结构,它可以用来存储多个元素并按照索引进行访问。JavaScript提供了丰富的数组API,可以方便地对数组进行各种操作和处理。下面将介绍一些常用的JavaScript数组API方法,并给出详细的解释和…

第4章.精通标准提示,引领ChatGPT精准输出

标准提示 标准提示,是引导ChatGPT输出的一个简单方法,它提供了一个具体的任务让模型完成。 如果你要生成一篇新闻摘要。你只要发送指示词:汇总这篇新闻 : …… 提示公式:生成[任务] 生成新闻文章的摘要: 任务&#x…

专题:一个自制代码生成器(嵌入式脚本语言)之应用实例

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的,可以在任何平台上使用。 专题:一个自制代码…

(C++) 属性说明符-标准属性

文章目录 前言标准属性🏷️noreturn⭐(C11) 指示函数不返回 🏷️carries_dependency⭐(C11) 指示在函数内外传播“释放-消费” std::memory_order 中的依赖链 🏷️deprecated⭐(C14) 指示以此属性声明的名字或实体,允许使用但因某…

机器学习之决策树现成的模型使用

目录 须知 DecisionTreeClassifier sklearn.tree.plot_tree cost_complexity_pruning_path(X_train, y_train) CART分类树算法 基尼指数 分类树的构建思想 对于离散的数据 对于连续值 剪枝策略 剪枝是什么 剪枝的分类 预剪枝 后剪枝 后剪枝策略体现之威斯康辛州乳…

Linux基础篇:解析Linux命令执行的基本原理

Linux 命令是一组可在 Linux 操作系统中使用的指令,用于执行特定的任务,例如管理文件和目录、安装和配置软件、网络管理等。这些命令通常在终端或控制台中输入,并以文本形式显示输出结果。 Linux 命令通常以一个或多个单词的简短缩写或单词…

学习vue3第十二节(组件的使用与类型)

1、组件的作用用途 目的: 提高代码的复用度,和便于维护,通过封装将复杂的功能代码拆分为更小的模块,方便管理, 当我们需要实现相同的功能时,我们只需要复用已经封装好的组件,而不需要重新编写相…

(九)图像的高斯低通滤波

环境:Windows10专业版 IDEA2021.2.3 jdk11.0.1 OpenCV-460.jar 系列文章: (一)PythonGDAL实现BSQ,BIP,BIL格式的相互转换 (二)BSQ,BIL,BIP存储格式的相互转换算法 (三…

LeetCode 1997.访问完所有房间的第一天:动态规划(DP)——4行主要代码(不需要什么前缀和)

【LetMeFly】1997.访问完所有房间的第一天:动态规划(DP)——4行主要代码(不需要什么前缀和) 力扣题目链接:https://leetcode.cn/problems/first-day-where-you-have-been-in-all-the-rooms/ 你需要访问 n 个房间,房间从 0 到 n - 1 编号。同…

JAX 来构建一个基本的人工神经网络(ANN)进行分类任务

import jax.numpy as jnp from jax import grad, jit, vmap from jax import random from jax.experimental import optimizers from jax.nn import relu, softmax# 构建神经网络模型 def neural_network(params, x):for W, b in params:x jnp.dot(x, W) bx relu(x)return s…

Flask学习(六):蓝图(Blueprint)

蓝图(Blueprint):将各个业务进行区分,然后每一个业务单元可以独立维护,Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的。 Demo目录结构&#xf…

常见panic场景 (空指针、越界、断言、map相关panic)

在Go语言中,panic是一个内建函数,用于在遇到无法继续执行的错误条件时中断当前函数的执行。panic可以由开发者显式调用,也可能由运行时错误触发。以下是一些常见的panic场景: 空指针解引用 当尝试通过一个nil指针访问其指向的值时…

linux离线安装jenkins及使用教程

本教程采用jenkins.war的方式离线安装部署,在线下载的方式会遇到诸多问题,不宜采用 一、下载地址 地址:Jenkins download and deployment 下载最新的长期支持版 由于jenkins使用java开发的,所以需要安装的linux服务器装有jdk环…

插入排序、归并排序、堆排序和快速排序的稳定性分析

插入排序、归并排序、堆排序和快速排序的稳定性分析 一、插入排序的稳定性二、归并排序的稳定性三、堆排序的稳定性四、快速排序的稳定性总结在计算机科学中,排序是将一组数据按照特定顺序进行排列的过程。排序算法的效率和稳定性是评价其优劣的两个重要指标。稳定性指的是在排…

新版Idea2023.3.5与lombok冲突、@Data失效

新版idea和lombok冲突,加上Data,其他地方get set也不报错,但是一运行就找不到get set方法。 但是直接使用Getter和Setter可以访问、应该是Data失效了。 解决方法: 看推上介绍是 lombok 与 idea 采集 get 、set 方法的时候所用的技…

maya pycharm运行 重定向

目录 maya sdk下载: maya测试代码: 添加sdk 依赖库: pycharm连接 maya 测试ok

day7|错误恢复

其实就是由于越界等问题所导致的panic,我们该如何解决 文中提到了两个方法,一种是使用defer,推迟错误的执行 第二种:recover函数 (需要在defer里面生效)可以避免panic生效而导致整个函数终止 package mainimport &q…

使用 Qlib 在线模式

使用 Qlib 在线模式 简介 Qlib 文档中介绍了离线模式。除此之外,用户还可以使用 Qlib 的在线模式。 在线模式旨在解决以下问题: 集中管理数据,用户无需管理不同版本的数据。减少生成的缓存量。使数据可以远程访问。在在线模式下,Qlib 会通过 Qlib-Server 以集中方式管理…

Jupyter开启远程服务器(最新版)

Jupyter Notebook 在本地进行访问时比较简单,直接在cmd命令行下输入 jupyter notebook 即可,然而notebook的作用不止于此,还可以用于远程连接服务器,这样如果你有一台服务器内存很大,但是呢你又不喜欢在linux上进行操作…