【Redis】如何保证缓存和数据库的一致性

目录

  • 背景
    • 问题
    • 思路
  • 三个经典的缓存模式
    • Cache-Aside
      • 读缓存
      • 写缓存
        • 为什么是删除旧缓存而不是更新旧缓存?
        • 为什么不先删除旧的缓存,然后再更新数据库?
      • 延迟双删
      • 如何确保原子性
    • Read-Through/Write-Through
      • Read-Through
      • Write-Through
    • Write Behind
  • 方案抉择

背景

  • 我们在日常开发中,为了提高数据响应速度,可能会将一些热点数据保存在缓存中,这样就不用每次都去数据库中查询了,可以有效提高服务端的响应速度,那么目前我们最常使用的缓存就是 Redis 了。

  • 以电商项目为例,主要有三个主要环节:

    1. 订单数据和支付流水数据:这两块数据对实时性和精确性要求很高,所以一般是不需要添加缓存的,直接操作数据库即可。
    2. 用户相关数据:这些数据和用户相关,具有读多写少的特征,所以我们使用 redis 进行缓存。
    3. 支付配置信息:这些数据和用户无关,具有数据量小,频繁读,几乎不修改的特征,所以我们使用本地内存进行缓存。
  • 选中合适的数据存入 Redis 之后,接下来,每当要读取数据的时候,就先去 Redis 中看看有没有,

    • 如果有就直接返回;
    • 如果没有,则去数据库中读取,并且将从数据库中读取到的数据缓存到 Redis 中,

问题

大致上就是上面这样一个流程,读取数据的这个流程实际上是比较清晰也比较简单的,然而,当数据存入缓存之后,如果需要更新的话,往往会来带另外的问题:

  1. 当有数据需要更新的时候,先更新缓存还是先更新数据库?
  2. 如何确保更新缓存和更新数据库这两个操作的原子性?
  3. 更新缓存的时候该怎么更新?修改还是删除?

思路

正常来说,我们有四种方案:

  1. 先更新缓存,再更新数据库。
  2. 先更新数据库,再更新缓存。
  3. 先淘汰缓存,再更新数据库。
  4. 先更新数据库,再淘汰缓存。

三个经典的缓存模式

  1. Cache-Aside
  2. Read-Through/Write through
  3. Write Behind

Cache-Aside

  • Cache-Aside,中文也叫旁路缓存模式,如果我们能够在项目中采用 Cache-Aside,那么就能够尽可能的解决缓存与数据库数据不一致的问题,注意是尽可能的解决,并无法做到绝对解决。
  • Cache-Aside 又分为读缓存和写缓存两种情况,我们分别来看。

读缓存

在这里插入图片描述

  • 其实对于读缓存的流程而言,一般没什么数据影响,有影响的主要是写流程

写缓存

  • 流程:写缓存——更新数据——删除旧缓存
  • 两个问题:
    • 为什么是删除旧缓存而不是更新旧缓存?
    • 为什么不先删除旧的缓存,然后再更新数据库?
为什么是删除旧缓存而不是更新旧缓存?
  • 更新缓存,说着容易做起来并不容易。很多时候我们更新缓存并不是简简单单更新一个 Bean。很多时候,我们缓存的都是一些复杂操作或者计算(例如大量联表操作、一些分组计算)的结果,如果不加缓存,不但无法满足高并发量,同时也会给 MySQL 数据库带来巨大的负担。那么对于这样的缓存,更新起来实际上并不容易,此时选择删除缓存效果会更好一些。
  • 对于一些写频繁的应用,如果按照更新缓存->更新数据库的模式来,比较浪费性能,因为首先写缓存很麻烦,其次每次都要写缓存,但是可能写了十次,只读了一次,读的时候读到的缓存数据是第十次的,前面九次写缓存都是无效的,对于这种情况不如采取先写数据库再删除缓存的策略。
  • 在多线程环境下,这样的更新策略还有可能会导致数据逻辑错误,来看如下一张流程图:
    在这里插入图片描述

当有两个并发的线程 A 和 B:

  1. 首先 A 线程更新了数据库。
  2. 接下来 B 线程更新了数据库。
  3. 由于网络等原因,B 线程先更新了缓存。
  4. A 线程更新了缓存。

那么此时,缓存中保存的数据就是不正确的,而如果采用了删除缓存的方式,就不会发生这种问题了。

为什么不先删除旧的缓存,然后再更新数据库?
  • 同样考虑到并发请求,假设我们先删除旧的缓存,然后再更新数据库,那么就有可能出现如下这种情况:
    在这里插入图片描述

线程A 和 B,其中 A 写数据,B 读数据,具体流程如下:

  1. A 线程首先删除缓存。
  2. B 线程读取缓存,发现缓存中没有数据。
  3. B 线程读取数据库。
  4. B 线程将从数据库中读取到的数据写入缓存。
  5. A 线程更新数据库。

一套操作下来,我们发现数据库和缓存中的数据不一致了!所以,在 Cache-Aside 中是先更新数据库,再删除缓存。

延迟双删

  • 其实无论是先更新数据库再删除缓存,还是先删除缓存再更新数据库,在并发环境下都有可能存在问题:

    • 假设有 A、B 两个并发请求:
      • 先更新数据库再删除缓存:当请求 A 更新数据库之后,还未来得及进行缓存清除,此时请求 B 查询到并使用了 Cache 中的旧数据。
      • 先删除缓存再更新数据库:当请求 A 执行清除缓存后,还未进行数据库更新,此时请求 B 进行查询,查到了旧数据并写入了 Cache。
  • 前面已经分析过了,尽量先操作数据库再操作缓存,但是即使这样也还是有可能存在问题,解决问题的办法就是延迟双删。

  • 延迟双删的处理流程:先执行缓存清除操作,再执行数据库更新操作,延迟 N 秒之后再执行一次缓存清除操作,这样就不用担心缓存中的数据和数据库中的数据不一致了。

  • 那么这个延迟 N 秒,N 是多大比较合适呢?一般来说,N 要大于一次写操作的时间,如果延迟时间小于写入缓存的时间,会导致请求 A 已经延迟清除了缓存,但是此时请求 B 缓存还未写入,具体是多少,就要结合自己的业务来统计这个数值了。

如何确保原子性

  • 其实说到底更新数据库和删除缓存毕竟不是一个原子操作,要是数据库更新完毕后,删除缓存失败了咋办?
  • 对于这种情况,一种常见的解决方案就是使用消息中间件来实现删除的重试。众所周知,MQ 一般都自带消费失败重试的机制,当我们要删除缓存的时候,就往 MQ 中扔一条消息,缓存服务读取该消息并尝试删除缓存,删除失败了就会自动重试。

Read-Through/Write-Through

  • Read-Through/Write-Through(读写全程)是一种计算机缓存策略,用于管理内存和磁盘之间的数据传输。

  • 在这种策略中,当计算机需要从磁盘读取数据时,它会首先检查缓存中是否存在该数据。

    • 如果缓存中有数据,就直接从缓存读取,这称为“读取全程”。
    • 如果缓存中没有数据,计算机会从磁盘读取数据,并将数据复制到缓存中,这称为“写入全程”。
  • 在写入全程过程中,当计算机更新缓存中的数据时,它会同时将数据写入磁盘,以保持缓存和磁盘中数据的一致性。这样可以确保在缓存中的数据在发生故障时,仍然能够从磁盘中恢复。

  • Read-Through/Write-Through策略的优点是可以确保数据的实时一致性,并且在发生故障时能够快速恢复。

  • 然而,由于每次读取操作都要检查缓存,读取性能可能会受到一定影响。

Read-Through

在这里插入图片描述

  • Read-Through 是一种类似于 Cache-Aside 的缓存方法,区别在于
    • 在 Cache-Aside 中,由应用程序决定去读取缓存还是读取数据库,这样就会导致应用程序中出现了很多业务无关的代码;
    • 而在 Read-Through 中,相当于多出来了一个中间层 Cache Middleware,由它去读取缓存或者数据库,应用层的代码得到了简化,
  • 和 Cache-Aside 相比,其实就相当于是多了一个缓存中间件(处理判断缓存并设置缓存的操作),这样我们在应用程序中就只需要正常的读写数据就行了,并不用管底层的具体逻辑,相当于把缓存相关的代码从应用程序中剥离出来了,应用程序只需要专注于业务就行了。

Write-Through

  • Write-Through 其实也是差不多,所有的操作都交给缓存中间件来完成,应用程序中就是一句简单的更新就行了
    在这里插入图片描述

  • 在 Write-Through 策略中,所有的写操作都经过 缓存中间件,每次写入时,Cache Middleware 会将数据存储在 DB 和 Cache 中,这两个操作发生在一个事务中,因此,只有两个都写入成功,一切才会成功。

  • 这种写数据的优势在于,应用程序只与 缓存中间件 对话,所以它的代码更加干净和简单。

Write Behind

  • Write-Behind 缓存策略类似于 Write-Through 缓存,应用程序仅与 缓存中间件 通信,缓存中间件 会预留一个与应用程序通信的接口。
  • Write-Behind 与 Write-Through 最大的区别在于,前者是数据首先写入缓存,一段时间后(或通过其他触发器)再将数据写入 Database,并且这里涉及到的写入是一个异步操作。
    • 这种方式下,Cache 和 DB 数据的一致性不强,对一致性要求高的系统要谨慎使用
    • 如果有人在数据尚未写入数据源的情况下直接从数据源获取数据,则可能导致获取过期数据
    • 不过对于频繁写入的场景,这个其实非常适用。
  • 将数据写入 DB 可以通过多种方式完成:
    • 一种是收集所有写入操作,然后在某个时间点(例如,当 DB 负载较低时)对数据源进行批量写入。
    • 另一种方法是将写入合并成更小的批次,例如每次收集五个写入操作,然后对数据源进行批量写入。

流程图如下
在这里插入图片描述

方案抉择

在选择具体方式来保证缓存和数据库的一致性时,可以考虑以下几个因素:

  1. 数据一致性要求:首先,明确应用对数据一致性的要求。如果数据一致性是绝对必要且不可容忍任何差异,那么使用事务是最可靠的选择。如果数据一致性要求较低,可以考虑使用缓存更新策略或消息队列。

  2. 系统性能需求:考虑应用的性能需求。事务可能会对性能产生较大的影响,因为它需要加锁、提交和回滚等操作。如果应用对性能有较高的要求,可以选择使用缓存更新策略或消息队列,并对缓存的更新进行优化。缓存更新策略可以在写入数据库之前更新缓存,减少数据库访问次数。

  3. 并发控制:在多线程或分布式环境下,需要考虑并发控制的问题。事务提供了强大的并发控制机制,但也会带来性能开销。如果并发操作较少或可以容忍一定程度的冲突,可以使用缓存更新策略或消息队列。

  4. 可靠性和容错性:考虑可靠性和容错性。事务通常提供较高的可靠性和容错性,因为它们可以回滚操作以确保数据一致性。缓存更新策略和消息队列需要额外的措施来处理故障或消息丢失的情况,例如使用缓存失效机制或消息重试机制等。

  5. 开发和维护复杂性:最后,考虑实现和维护所需的复杂性。事务管理着复杂的并发控制和回滚机制,可能需要更多的代码和配置。缓存更新策略和消息队列可能需要额外的逻辑和配置来确保一致性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu22.04 下安装Curl库

1. apt 安装: sudo apt-get install curl 2. 官网压缩包: 下载地址:curl downloads wget https://curl.haxx.se/download/curl-7.78.0.tar.gz tar -xzvf curl-7.78.0.tar.gz cd curl-7.78.0 ./configure --with-openssl make sudo make i…

ubuntu系统上快速直接获取ip地址

文章目录 前言总结 前言 ubuntu系统上查看ip地址 $ hostname -I示例输出: 192.168.1.10总结 作者:加辣椒了吗? 简介:憨批大学生一枚,喜欢在博客上记录自己的学习心得,也希望能够帮助到你们!

基于若依的ruoyi-nbcio流程管理系统增加所有任务功能(一)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码: h…

CMake个人理解和使用

100编程书屋_孔夫子旧书网 前言 CMake是一个构建工具,通过它可以很容易创建跨平台的项目。通常使用它构建项目要分两步,通过源代码生成工程文件,通过工程文件构建目标产物(可能是动态库,静态库,也可能是可执行程序)。使用CMake的一个主要优势是在多平台或者多人协作的…

Python学习打卡:day10

day10 笔记来源于:黑马程序员python教程,8天python从入门到精通,学python看这套就够了 目录 day1073、文件的读取操作文件的操作步骤open()打开函数mode常用的三种基础访问模式读操作相关方法read()方法readlines()方法readline()方法for循…

Golang | Leetcode Golang题解之第168题Excel表列名称

题目&#xff1a; 题解&#xff1a; func convertToTitle(columnNumber int) string {ans : []byte{}for columnNumber > 0 {columnNumber--ans append(ans, Abyte(columnNumber%26))columnNumber / 26}for i, n : 0, len(ans); i < n/2; i {ans[i], ans[n-1-i] ans[n…

VSCode 安装NeoVim扩展(详细)

目录 1、安装NeoVim扩展 2、windows安装Neovim软件 3、优化操作相关的配置&#xff1a; 5、Neovim最好的兼容性配置 6、技巧和特点 6.1 故障排除 6.2、Neovim 插件组合键设置 6.3、跳转列表 1、安装NeoVim扩展 在扩展商店搜索NeoVim&#xff0c;安装扩展 2、windows安装…

吉时利Keithley2602B数字源表

吉时利Keithley2602B数字源表 2601B、2602B、2604B 系统 Sourcemeter SMU 仪器 2601B、2602B 和 2604B 系统 Sourcemeter SMU 仪器为 40W DC / 200W 脉冲 SMU&#xff0c;支持 10A 脉冲&#xff0c;3A 至 100fA 和 40V 至 100nV DC。它们将精密电源、实际电流源、6 位数字万用…

[Linux] 文件系统

UNIX操作系统将文件组织成一个有层次的树形结构&#xff1a; 标准目录&#xff1a; 根目录&#xff1a; /tmp目录 主目录&#xff1a; 这就是主目录 一般与系统有关的信息都存放在etc目录下 注意&#xff1a; /etc/passwd存放的是用户账户信息&#xff0c;不是密码信息&#xf…

项目中选择Entity Framework Core还是Dapper?

我是将 Dapper 还是 Entity framework core 用于下一个 .NET 项目&#xff1f;当你必须做出这个决定时&#xff0c;总是令人困惑&#xff0c;为了项目的成功&#xff0c;你需要做出正确的决定。让我来帮你... 介绍 使用 .NET 开发的应用程序可以根据其使用的对象关系映射器 &…

课程设计---哈夫曼树的编码与解码(Java详解)

目录 一.设计任务&&要求&#xff1a; 二.方案设计报告&#xff1a; 2.1 哈夫曼树编码&译码的设计原理&#xff1a; 2.3设计目的&#xff1a; 2.3设计的主要过程&#xff1a; 2.4程序方法清单&#xff1a; 三.整体实现源码&#xff1a; 四.运行结果展示&…

SkyWalking 极简入门

1. 概述 1.1 概念 SkyWalking 是什么&#xff1f; FROM Apache SkyWalking 分布式系统的应用程序性能监视工具&#xff0c;专为微服务、云原生架构和基于容器&#xff08;Docker、K8s、Mesos&#xff09;架构而设计。 提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体…

milvus元数据解析工具milvusmetagui介绍使用

简介 milvusmetagui是一款用来对milvus的元数据进行解析的工具&#xff0c;milvus的元数据存储在etcd上&#xff0c;而且经过了序列化&#xff0c;通过etcd-manager这样的工具来查看是一堆二进制乱码&#xff0c;因此开发了这个工具对value进行反序列化解析。 在这里为了方便交…

建材租赁管理系统软件教程,操作简单佳易王租赁管理系统操作教程

建材租赁管理系统软件教程&#xff0c;操作简单佳易王租赁管理系统操作教程 一、软件操作教程 以下软件操作教程以&#xff0c;佳易王租赁管理系统为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 租赁登记&#xff1a; a、租赁登记可以记录日…

sklearn之各类朴素贝叶斯原理

sklearn之贝叶斯原理 前言1 高斯朴素贝叶斯1.1 对连续变量的处理1.2 高斯朴素贝叶斯算法原理 2 多项式朴素贝叶斯2.1 二项分布和多项分布2.2 详细原理2.3 如何判断是否符合多项式贝叶斯 3 伯努利朴素贝叶斯4 类别贝叶斯4 补充朴素贝叶斯4.1 核心原理4.2 算法流程 前言 如果想看…

Python | Leetcode Python题解之第160题相交链表

题目&#xff1a; 题解&#xff1a; class Solution:def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:A, B headA, headBwhile A ! B:A A.next if A else headBB B.next if B else headAreturn A

吴恩达机器学习 第三课 week1 无监督学习算法(上)

目录 01 学习目标 02 无监督学习 03 K-means聚类算法 3.1 K-means聚类算法原理 3.2 k-means算法实现 3.3 利用k-means算法压缩图片 04 总结 01 学习目标 &#xff08;1&#xff09;了解无监督学习算法 &#xff08;2&#xff09;掌握K-means聚类算法实现步骤 &#xff…

it职业生涯规划系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;职业介绍管理&#xff0c;答题管理&#xff0c;试题管理&#xff0c;基础数据管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;在线答题&#xff0…

基于SSM+Jsp的书店仓库管理系统

摘要&#xff1a;仓库作为储存货物的核心功能之一&#xff0c;在整个仓储中具有非常重要的作用&#xff0c;是社会物质生产的必要条件。良好的仓库布局环境能够对货物进入下一个环节前的质量起保证作用&#xff0c;能够为货物进入市场作好准备&#xff0c;在设计中我们根据书店…