精华内容
下载资源
问答
  • 缓存和数据库一致性
    2022-04-08 10:22:08
    • redis和数据库的双写一致性一直是个比较高频的面试题,今天来给大家说一说双写一致性
    • 一致性

    • 强一致性 : 对于用户来说 体验感最好,写入什么读取的就是什么
    • 弱一致性 : 系统不保证写入什么就读取什么,但会尽可能保证在一定的时间后可以保证写入的和读取的相同
    • 最终一致性 : 是业界推崇的模型,也是弱一致性的一种, 在一定的时间后 保证数据一致性
    • redis与数据库双写一致性

    • 一般来说, 写入的时候, 先写入数据库,在删除缓存
    • 抛出一个问题 , 为什么先写入数据库?可以先写入缓存吗? 以及可以先删除缓存,在更新数据库吗?

    • 第一个问题: 为什么先写入数据库?
    • 假设先写入缓存,A B 两个线程,A写入缓存 - > A写入数据库时候失败了->产生脏数据
    • A写入缓存->B写入缓存 ->B写入数据库成功->A写入数据库成功 : 导致不一致性
    • 先删除缓存,在更新数据库

    • A删除缓存->这时候B读取了数据库 并且把数据放入了缓存中->A更新了数据库 : 导致数据不一致
    • 解决双写一致性

    • 可以使用延时删除 , 先删除一遍缓存 ->更新数据库->隔了一定的时间 再次删除缓存
    • 如果二次删除失败怎么办呢?

    • 那么可以使用 mq 来进行重试操作, 可以使用rocketmq , rocketmq可以保证消息消费成功才会被删除
    更多相关内容
  • 缓存和数据库一致性问题

    千次阅读 2022-03-30 10:37:10
    如何保证缓存数据库双写一致性问题


    如何确保缓存和数据库的一致性?是先更新缓存还是先更新数据库?这是我曾经面试遇到的一个问题

    1. 缓存和数据库一致性问题

    在日常开发中,为了提高数据响应速度,会将一些高频数据保存在缓存中,这样就不用每次请求都去查询数据库,可以提高服务接口的响应速度。缓存工具有很多,目前最常使用的应该就是 Redis

    对于缓存的使用,并不是所有场景都要使用缓存,可以根据业务对数据要求的实时性,选择性的使用缓存,例如:

    • 订单和支付数据,这两类数据对实时性和精确性要求很高,所以一般不需要添加缓存,直接操作数据库
    • 用户相关数据,这些数据和用户相关,具有读多写少特点,所以可以使用 Redis进行缓存
    • 某些配置信息,这些数据和用户无关,具有数据量小,频繁读,几乎不修改的特征,所以可以使用本地内存进行缓存

    下面来看一下使用缓存的流程:

    在这里插入图片描述

    第一次请求数据时会去查询数据库,然后把数据存入 Redis中,对于接下来的每次请求都会先去 Redis中看看存不存在,如果存在就直接返回,如果不存在,就去数据库中查询,将从数据库中查询到的数据缓存到 Redis中。

    然而,当数据存入缓存之后,如果数据需要更新的话,往往会来带另外的问题:

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

    对于上述问题,无非就四种方案:

    • 先更新缓存,再更新数据库
    • 先更新数据库,再更新缓存
    • 先淘汰缓存,再更新数据库
    • 先更新数据库,再淘汰缓存

    那到底使用哪种方式呢?

    2. 三个经典的缓存模式

    2.1 Cache-Aside

    最经典的缓存+数据库读写的模式,就是Cache Aside。如果在项目中采用 Cache-Aside模式,那么就可以尽可能的解决缓存与数据库数据不一致的问题,注意是尽可能的解决,并无法做到绝对解决。Cache-Aside分为读缓存和写缓存两种情况,要分别来看。

    • 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应
    • 更新的时候,先更新数据库,然后再删除缓存

    2.1.1 读缓存

    读缓存表示读取数据时,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。看一下流程图:

    在这里插入图片描述

    具体流程:

    • 读取数据
    • 查缓存中是否有需要的数据,如果命中缓存,则直接返回数据
    • 如果没有命中缓存,那么就访问数据库
    • 将从数据库中读取到的数据设置到缓存中
    • 返回数据

    这是 Cache-Aside的读缓存流程,其实对于读缓存的流程而言,一般都没什么异议,有异议的主要是写流程

    2.1.2 写缓存

    写缓存表示更新数据的时候,先更新数据库,然后再删除缓存,看一下流程图:

    在这里插入图片描述

    这个写缓存的流程就比较简单,先更新数据库中的数据,然后删除旧的缓存即可,流程虽然简单,但是却引发出两个问题:

    • 为什么是删除旧缓存,而不是更新旧缓存?
    • 为什么不先删除旧的缓存,然后再更新数据库?

    为什么是删除旧缓存而不是更新旧缓存?

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

    在这里插入图片描述

    可以看到,有两个并发的线程 AB

    • 首先 A线程更新了数据库
    • 接下来 B线程更新了数据库
    • 由于网络等原因,B线程先更新了缓存
    • A线程更新了缓存

    那么此时,缓存中保存的数据就是不正确的,而如果采用了删除缓存的方式,就不会发生这种问题了,因为缓存删除了,就要实时去数据库中查询,数据库中的数据是两个线程更新后的结果。

    那为什么不先删除旧的缓存,然后再更新数据库?

    这个也是考虑到并发请求,假设先删除旧的缓存,然后再更新数据库,那么就有可能出现如下这种情况:

    在这里插入图片描述

    这个操作是这样的,有两个线程,AB,其中 A写数据,B读数据,具体流程如下:

    • A线程首先删除缓存
    • B线程读取缓存,发现缓存中没有数据
    • B线程读取数据库
    • B线程将从数据库中读取到的数据写入缓存
    • A线程更新数据库

    这样下来发现数据库和缓存中的数据不一致了,所以,Cache-Aside 中是先更新数据库,再删除缓存

    2.1.3 延迟双删

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

    假设有 AB两个并发请求:

    • 先更新数据库再删除缓存:当请求 A更新数据库之后,还未来得及进行缓存清除,此时请求 B查询到并使用了 Cache中的旧数据
    • 先删除缓存再更新数据库:当请求 A执行清除缓存后,还未进行数据库更新,此时请求 B进行查询,查到了旧数据并写入了 Cache

    当然前面已经分析过了,尽量先操作数据库再操作缓存,但是即使这样也还是有可能存在问题,解决问题的办法就是延迟双删。

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

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

    2.1.4 如何确保操作缓存和操作数据库的原子性

    更新数据库和删除缓存毕竟不是一个原子操作,要是数据库更新完毕后,删除缓存失败了咋办?

    对于这种情况,一种常见的解决方案就是使用消息中间件来实现删除的重试。MQ一般都自带消费失败重试的机制,当要删除缓存的时候,就往 MQ中扔一条消息,缓存服务读取该消息并尝试删除缓存,删除失败了就会自动重试。

    2.2 Read-Through/Write-Through

    2.2.1 Read-Through

    在这里插入图片描述

    乍一看,感觉和Cache-Aside 一样,没啥区别,单看流程是不太容易看到区别。

    Read-Through是一种类似于Cache-Aside 的缓存方法,区别在于,在Cache-Aside 中,由应用程序决定去读取缓存还是读取数据库,这样就会导致应用程序中出现了很多业务无关的代码;而在 Read-Through 中,相当于多出来了一个中间层 Cache Middleware,由它去读取缓存或者数据库,应用层的代码得到了简化,回忆下 Spring Cache 中的 @Cacheable 注解,感觉像不像 Read-Through

    画一个简单的流程图大家来看下:

    在这里插入图片描述

    可以看到,和 Cache-Aside 相比,其实就相当于是多了一个Cache Middleware,这样在应用程序中就只需要正常的读写数据就行了,并不用管底层的具体逻辑,相当于把缓存相关的代码从应用程序中剥离出来了,应用程序只需要专注于业务。

    2.2.2 Write-Through

    Write-Through 其实也是差不多,所有的操作都交给Cache Middleware 来完成,应用程序中就是一句简单的更新就行了,来看看流程:

    在这里插入图片描述

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

    2.3 Write Behind

    Write-Behind缓存策略类似于 Write-Through 缓存,应用程序仅与 Cache Middleware 通信,Cache Middleware会预留一个与应用程序通信的接口。

    Write-BehindWrite-Through 最大的区别在于,前者是数据首先写入缓存,一段时间后(或通过其他触发器)再将数据写入 Database,并且这里涉及到的写入是一个异步操作。这种方式下,CacheDB数据的一致性不强,对一致性要求高的系统要谨慎使用,如果有人在数据尚未写入数据源的情况下直接从数据源获取数据,则可能导致获取过期数据,不过对于频繁写入的场景,这个其实非常适用。

    将数据写入 DB可以通过多种方式完成:

    一种是收集所有写入操作,然后在某个时间点(例如,当 DB负载较低时)对数据源进行批量写入。
    另一种方法是将写入合并成更小的批次,例如每次收集五个写入操作,然后对数据源进行批量写入。

    在这里插入图片描述

    展开全文
  • 如何保证缓存和数据库一致性

    千次阅读 2021-09-15 08:07:59
    如何保证缓存和数据库一致性?引入缓存提高性能缓存利用率一致性问题并发引起的一致性问题删除缓存可以保证一致性吗?如何保证两步都执行?主从延迟延迟双删问题可以做到强一致性吗?创建一个表格设定内容居中、...

    如何保证缓存和数据库一致性?

    很多人对这个问题依然有很多疑惑:

    • 到底是更新缓存还是删除缓存?
    • 选择先更新数据库再删除缓存,还是先删除缓存再更新数据库?
    • 为什么要引入消息队列保证一致性?
    • 延迟双删会产生哪些问题?到底要不要用?如何用?

    引入缓存提高性能

    我们从最简单的场景开始说起。

    如果项目业务处于起步阶段,流量非常少,读写请求直接操作数据库即可,此时你的架构模型是这样:
    在这里插入图片描述

    但是随着业务量的增长,你的项目请求量越来越大,如果每次都从DB中读取,那必然会产生性能问题。

    通常这个阶段会引入【缓存】来提高读写性能,架构模型就会发生转变:
    在这里插入图片描述
    目前主流的缓存中间件,当属Redis,不仅性能高,还支持多种数据类型,能更好的满足我们的业务需求。

    但是加入缓存之后,就会面临这样的一个问题:之前数据只存放在数据库中,从数据库读取,现在要放到缓存中读取,具体要存储什么呢?

    最简单直接的方案就是【全量数据刷到缓存中】

    • 数据库的数据,全量刷到缓存中(不设置失效时间)
    • 写请求只更新数据库,不更新缓存
    • 启动一个定时任务,定时将数据库中的数据,同步更新到缓存中
      在这里插入图片描述
      这个方案的有点不必多说,所有请求都全部【命中】缓存,不需要经过数据库,性能非常高。

    但是缺点也很明显,主要体现以下两点。

    • 缓存利用率低,不是所有的缓存都是热点,不经常访问的数据长期存放在缓存中,会导致资源浪费
    • 数据不一致,因为采取的是【定时刷新缓存】的机制,导致缓存数据与数据库数据不一致(取决于定时刷新的频率)

    所以,这个方案适用于【体量小】的业务,对数据一致性要求不高的业务场景。

    那么,针对体量很大的业务场景,怎么解决这两个问题呢?

    缓存利用率和一致性问题

    先来看第一个问题,如何提高缓存利用率的问题。

    说到这,想要缓存利用率【最大化】,我们很容易能想到的方案是,缓存中只保留最近访问或经常访问的【热点数据】。

    我们可以这样优化

    • 写请求依旧只写数据库
    • 读请求先读缓存,如果缓存不在,则从数据库读取,并重建缓存
    • 同时,写入缓存中的数据,都设置失效时间(错开失效时间)
      在这里插入图片描述

    这样一来,缓存中不经常访问的key,随着时间的推移,都会时间【过期】淘汰掉,最终缓存中保留的,都是热点数据,从而缓存的利用率得以最大化。

    再看数据一致性的问题。

    想要保证缓存和数据库【实时】一致,那就不能再使用定时任务刷新缓存的方案。

    所以,当数据发生更新时,我们不仅要操作更新数据库,还要一并操作缓存。具体的操作就是修改一条数据时,不仅要更新数据库,连带着缓存一起更新,或者删除相应的缓存,再次访问时是会查询数据库后进行重建缓存。

    但数据库和缓存都更新,有存在先后的问题,那对应的方案就有2个:

    1.先更新缓存,后更新数据库
    2.先更新数据库,后更新缓存

    哪一个方案更好呢?

    这里先不考虑并发的问题,正常情况下,无论谁先谁后,都可以让两者保持一致,但现在我们需要重点考虑【异常】情况。

    因为操作是分两步,那么就有可能在【第一个成功,第二个失败】的情况发生。

    1、先更新缓存,后更新数据库

    如果更新成功了,但数据库更新失败,那么此时缓存中是最新值,但是数据库中是【旧值】

    虽然此时请求可以命中缓存,拿到正确的值,但是一旦缓失,就会从数据库中读取到【旧值】,重建缓存也是这个旧值。

    这时用户就会发现自己之前的修改【又变回去了】,对业务造成影响。

    2、先更新数据库,后更新缓存

    如果更新数据库成功了,但是缓存更新失败了,那么此时数据库中是最新的值,缓存中是【旧值】。

    之后的读请求都读到的是旧数据,只有当缓存【失效】后,才能从数据库中得到正确的值。

    这时用户就会发现,自己刚刚修改了数据,但发现不生效,过一段时间后,数据才变更过来,对业务也会有影响。

    可见,无论是谁先谁后,但凡后者发生异常,就会对业务造成影响。那么怎样解决这个问题呢?

    我们继续分析,除了操作失败问题,还有什么场景会影响数据一致性?

    这里我们还要重点关注:并发问题

    并发引起的一致性问题

    假如我们采用【先更新数据库,在更新缓存】的方案,并且两步都可以成功执行的前提下,如果存在并发,会是什么情况呢?

    有线程A 和线程 B 两个线程,需要更新【同一条数据】,会发生这样的场景:

    1. 线程A 更新数据库(X=1)
    2. 线程B 更新数据库(X=2)
    3. 线程B 更新缓存(X=2)
    4. 线程A 更新缓存(X=1)

    最终 X 的值在缓存中是1 ,数据库中是2,发生不一致。

    也就是说,A虽然先于B发生,但B操作数据库和缓存的时间,却要比B的时间更短,执行时序发生【错乱】,最终导致这条数据结果不符合预期的。

    同样的,采用【新更新缓存,在更新数据库】的方案,也会有类似的问题。

    除此之外,我们从【缓存利用率】的角度来评估这个方案,也是不太难推敲的。

    这是因为每次数据发生变更,都更新缓存,但是缓存中的数据不一定会被马上读取,这就会导致缓存中存放了很多不常访问的数据,浪费缓存资源。

    而且很多情况下,写到缓存中的值,并不是与数据库中的值一一对应的,很可能先查数据库,经过一系列计算得出的值,才把这个值写到缓存中。

    由此可见,这种【更新数据库+更新缓存】的方案,不仅缓存利用率不到,还会造成服务器资源和性能的浪费。

    所以此时我们需要考虑另外一种方案:删除缓存

    删除缓存可以保证一致性吗?

    删除缓存对应的方案也有 2 种:

    1. 先删除缓存,再更新数据库
    2. 先更新数据库,再删除缓存

    经过前面的分析我们可以知道,但凡【第二步】操作失败,都会导致数据不一致。

    1、先删除缓存,后更新数据库

    如果有 2 个线程要并发【读写】数据,可能会发生以下场景:

    1. 线程A 要更新 X=2 (原值X=1)
    2. 线程A 先删除缓存
    3. 线程B 读缓存,发现不在,从数据库中读取到旧值(X=1)
    4. 线程A 将新值写入数据库(X=2)
    5. 线程B 将旧值写入缓存(X=1)

    最终 X 的值在缓存中是1(旧值),在数据库中是2(新值),发生不一致。

    可见,先删除缓存,后更新数据库,当发生【读+写】并发时,还是存在数据不一致的情况。

    2、先更新数据库,后删除缓存

    依旧是两个线程【并发读写】数据 :

    1. 缓存中 X 不存在(数据库中 X=1)
    2. 线程A 读取数据库,得到旧值(X=1)
    3. 线程B 更新数据库(X=2)
    4. 线程B 删除缓存
    5. 线程A 将旧值写入缓存(X=1)

    最终X的值在缓存中是1(旧值),在数据库中是2(新值),也发生不一致。

    这种情况理论来说是可能发生的,但实际中真有可能发生吗?

    其实概率很低,这是因为它必须满足 3 个条件:

    1. 缓存刚已失效
    2. 读写请求并发
    3. 更新数据库 + 删除缓存的时间(步骤3~4),要比读数据库 + 写缓存的时间短(步骤2和5)

    仔细想一下,条件3发生的概率是非常低的。

    因为写数据库一般会先【加锁】,所以写数据库,通常是要比读数据库的时间更长的。
    这么看来,【先更新数据库 + 再删除缓存】的方案,是可以保证数据一致性的。

    所以,我们应该采用这种方案(【先更新数据库 + 再删除缓存】)来操作数据库和缓存。

    嗯,解决了并发问题,我们继续来看前面遗留的,第二步执行失败,导致数据不一致的问题

    如何保证两步都执行?

    通过前面的分析,无论是更新缓存还是删除缓存,只要第二步出现失败,就会导致数据库和缓存的结果不一致。

    保证第二步成功执行,就是解决问题的关键

    程序在执行过程中发生异常,最简单的解决办法是:重试

    但这并不意味着,只要执行失败(出现异常),我们重试就可以了。

    实际情况往往没那么简单,失败后立即重试的问题在于:

    • 立即重试很大概率还会失败
    • 重试次数设置多少才合理
    • 重试会一直占用这个线程资源,无法服务其他客户端请求

    由此可见了,虽然我们想通过重试的方式解决问题,但是这种【同步重试】的方案依旧不严谨。

    那么另一种更好的方案是:异步重试

    异步重试,其实就是把重试请求放到【消息队列】中,然后由专门的消费者来进行重试,直到成功。

    或者更直接的做法,为了避免第二次执行失败,我们可以把操作缓存这一步,直接放到消息队列中,由消费者来操作缓存。

    这里你可能会疑惑,写队列也有可能会失败,而且引入消息队列,这又会增加了更多的维护成本,增加项目复杂度,这样做是否值得?

    这是个好问题,抛开项目复杂度,我们思考这样一个问题:如果在执行失败的线程中一直重试,还没等执行成功,此时如果项目重启了,那么重试的请求就丢失了,这一条数据就不一致了。

    所以,我们必须将重试或者第二步骤放到另一个服务中,这个服务用【消息队列】最为合适,因为消息队列的特性,可以满足我们的需求:

    • 消息队列可靠性:写到队列中的消息,成功消费之前不会丢失(重启也不担心)
    • 消息队列保证消息成功投递:消费者从队列拉取消息,成功消费后才会删除(message_id),否则还会继续投递消息给消费者(符合重试场景)

    至于写队列失败和消息队列成本维护问题:

    • 写入队列失败:操作缓存和写消息队列,同时失败的概率是非常小的
    • 维护成本:达到一定量级,我们项目中都会使用消息队列,维护成本并没有增加很多

    所以,引入消息队列来解决第二个步骤失败重试的问题,是比较合适的,这时候的架构就变成了这样:
    在这里插入图片描述
    如果不想在应用中去写消息队列,是否有更简单的方案,同时又可以保证一致性呢?

    方案还是有的,这就是近几年比较流行的解决方案:订阅数据库变更日志,再操作缓存

    具体来说就是,业务在修改数据时,只需修改数据库,无需操作缓存。

    那什么时候操作缓存呢,这个就与数据库的【变更日志】有关

    当一条数据发生改变时,MySQL就会产生一条binlog(变更日志)我们可以订阅这个日志,拿到具体操作的数据,然后再根据这条数据,去删除对应的缓存。

    在这里插入图片描述
    订阅变更日志,目前也有比较成熟的开源中间件,例如阿里的canal,使用这种方案的有点在于:

    • 无需考虑写消息队列失败情况:只要写MySQL成功,Binlog肯定会有
    • 自动投递到下游队列:canal自动把数据库变更日志【投递】给下游消息队列

    当然,于此同时,我们需要投入经历去维护canal的高可用和稳定性。

    根据数据库的特性,很多数据库都逐渐开始提供【订阅变更日志】的功能,到时候就不用通过中间件来拉取日志,可以自己写程序订阅变更日志,进一步简化流程。

    到这里,可以得出的结论,想要保证数据和缓存一致性,推荐采用【先更新数据库,再删除缓存】的方案,并且配合【消息队列】或【订阅变更日志】的方式来做

    主从延迟和延迟双删问题

    到这里,还有两个问题

    第一个问题:前面【先删缓存,再更新数据库】,导致不一致的问题。
    第二个问题:关于【读写分离 + 主从复制延迟】情况下,缓存和数据库一致性的问题。

    那么,如何解决这类的问题呢?

    最有效的办法就是,将缓存删掉

    但是不能立即删,而是需要【延迟删】,这就是:缓存延迟双删策略

    按照这个延迟双删策略,这两个问题解决方案如下:

    解决第一个问题:在线程A 删除缓存、更新完数据库之后,先【休眠一会】,再【删除】一次缓存。
    解决第二个问题:可以生成一条【延迟消息】,写到消息队列中,消费者延迟删除缓存

    这两个方案的目的都是为了将缓存清掉,这样一来,下次就可以从数据库读取到最新值,写入缓存。

    但是相应的,这个延迟删除,延迟时间设置多久才合适?

    • 问题1:延迟时间要大于主从复制的延迟时间
    • 问题2:延迟时间要大于第二个线程读取数据+写入缓存的时间

    但是,这个在实际分布式和高并发的场景中,很难评估时间的。

    很多时候,我们都是凭借经验估算这个延迟时间,例如1~5s,这样也只是尽可能降低不一致的概率。

    所以,极端的情况下,还是会有可能发生不一致。

    可以做到强一致性吗?

    如果想让缓存和数据到达到【强一致性】,其实很难做到的。

    这往往要 牺牲性能

    一旦我们使用缓存,就必然会出现一致性的问题,性能与一致性,无法做到保持平衡。势必会向一方倾斜。

    如果非要达到强一致性,那就必须在完成更新操作之前,不能有任何请求处理,这在实际高并发的场景中是不可取的。

    虽然也可以使用【分布式锁】来实现,但是加锁与释放的过程,也会降低其性能,有时候甚至会超过引入缓存带来的性能提升。

    所以,我们既然决定使用缓存,就必须容忍【一致性】问题,我们只能尽可能地降低出现问题的概率

    总结

    • 将要提高应用性能,可以引入缓存来解决
    • 引入缓存后,就要考虑缓存和数据库一致性的问题,建议,更新数据库,删除缓存
    • 更新数据库 + 删除缓存的方案,在并发场景下无法保证缓存与数据保持一致性,且存在缓存资源浪费和机器性能浪费的情况。
    • 并发场景下的延迟双删策略,这个延迟时间很难评估,所以推荐【先更新数据库,再删除缓存】的方案
    • 在【先更新数据库,再删除缓存】的方案下,为了保证两步都能执行成功,需要配合【消息队列】或【订阅变更日志】的方案来做,其本质是通过重试的方式保证数据一致性。
    • 在【先更新数据库,再删除缓存】方案下,【读写分离 + 主从延迟】也会导致缓存和数据库不一致,解次问题的方案是【延迟双删】,凭借经验发送【延迟消息】到队列中,延迟删除缓存,同时要也要控制主从库延迟(可以通过暂时剔除延迟高的节点,延迟低的时候再将节点加入集群),尽可能降低不一致发生的概率。
    展开全文
  • (1)啥时候数据库和缓存中的数据会不一致 (2)不一致优化思路 (3)如何保证数据库缓存一致性
  • 本文由以下三个部分组成1、讲解缓存更新策略2、对每种策略进行缺点分析3、针对缺点给出改进方案先做一个说明,从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的...
  • 如何保证Redis缓存和数据库一致性

    千次阅读 2022-04-06 19:44:16
    Redis嘛,就是一种运行速度很快,并发很强的跑在内存上的NoSql数据库,支持键到五种数据类型的映射,(string、list、set、zset、hash),而memecache只能支持简单的数据类型。另外redis可以完成一部份数据的持久化,...

    首先我们先了解下缓存是什么?
    缓存就是把低速存储的结果,临时保存在高速存储的技术。

    为什么使用redis进行缓存数据?

    Redis嘛,就是一种运行速度很快,并发很强的跑在内存上的NoSql数据库,支持键到五种数据类型的映射,(string、list、set、zset、hash),而memecache只能支持简单的数据类型。另外redis可以完成一部份数据的持久化,而memecache完全将数据保存在内存中,不进行持久化,如果服务器出问题,数据将全部丢失,另外一个原因是redis底层实现优化比memecache好。另外采用了多路复用io阻塞机制,数据结构简单,操作节省时间。

    在这里插入图片描述

    那常见的保证缓存与数据库一致的方法有哪些呢?

    想要保证缓存与数据库的双写一致,一共有4种方式,即4种同步策略:
    先更新缓存,再更新数据库;
    先更新数据库,再更新缓存;
    先删除缓存,再更新数据库;
    先更新数据库,再删除缓存。

    那么我们需要做的就是根据不同的场景来使用合理的方式来解决数据问题。

    第一种:先删除缓存,再更新数据库

    在出现失败时可能出现的问题:

    1:线程A删除缓存成功,线程A更新数据库失败;

    2 :线程B从缓存中读取数据;由于缓存被删,进程B无法从缓存中得到数据,进而从数据库读取数据;此时数据库中的数据更新失败,线程B从数据库成功获取旧的数据,然后将数据更新到了缓存。
    最终,缓存和数据库的数据是一致的,但仍然是旧的数据。

    第二种:先更新数据库,再删除缓存

    假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生

    (1)缓存刚好失效
    (2)请求A查询数据库,得一个旧值
    (3)请求B将新值写入数据库
    (4)请求B删除缓存
    (5)请求A将查到的旧值写入缓存
    如果发生上述情况,确实是会发生脏数据。
    然而,发生这种情况的概率又有多少呢?
    发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。
    数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。
    先更新数据库,再删缓存依然会有问题,不过,问题出现的可能性会因为上面说的原因,变得比较低。

    第三种:给所有的缓存一个失效期

    第三种方案可以说是一个大杀器,任何不一致,都可以靠失效期解决,失效期越短,数据一致性越高。但是失效期越短,查数据库就会越频繁。因此失效期应该根据业务来定。
    1.并发不高的情况:
    读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;
    写: 写mysql->成功,再写redis;
    2.并发高的情况:
    读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;
    写:异步话,先写入redis的缓存,就直接返回;定期或特定动作将数据保存到mysql,可以做到多次更新,一次保存;

    第四种:加锁,使线程顺序执行

    如果一个服务部署到了多个机器,就变成了分布式锁,或者是分布式队列按顺序去操作数据库或者 Redis,带来的副作用就是:数据库本来是并发的,现在变成串行的了,加锁或者排队执行的方案降低了系统性能,所以这个方案看起来不太可行。

    第五种:采用双删

    先删除缓存,再更新数据库,当更新数据后休眠一段时间再删除一次缓存。

    方案推荐两种:

    1:项目整合quartz等定时任务框架,去实现延时3–5s再去执行最后一步任务 。(推荐使用)
    2:创建线程池,线程池中拿一个线程,线程体中延时3-5s再去执行最后一步任务(不能忘了启动线程)

    第六种:异步更新缓存(基于订阅binlog的同步机制)

    MySQL binlog增量订阅消费+消息队列+增量数据更新到redis读Redis

    热数据基本都在Redis写MySQL:增删改都是操作MySQL更新Redis数据:MySQ的数据操作binlog,来更新到Redis:

    1)数据操作主要分为两大块:一个是全量(将全部数据一次写入到redis)一个是增量(实时更新)。

    这里说的是增量,指的是mysql的update、insert、delate变更数据。

    2)读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。
    这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。
    其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。
    这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。
    当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis。

    以上就是redis和数据库数据保持一致的方案。
    微信搜索IT说说公众号,获取更多java技术资源!
    在这里插入图片描述

    展开全文
  • 缓存一致性的保证,更是在面试中被反复问到,这里进行一下总结,针对不同的要求,选择恰到好处的一致性方案。 缓存是什么 存储的速度是有区别的。缓存就是把低速存储的结果,临时保存在高速存储的技术。 图片...
  • 最近面试季节,估计「如何保证缓存和数据库一致性」这个问题经常会被问到,这是一个老生常谈的话题了。 但很多人对这个问题,依旧有很多疑惑: 到底是更新缓存还是删缓存? 到底选择先更新数据库,再删除缓存,还是...
  • 如何保障缓存和数据库一致性(超详细案例)

    千次阅读 多人点赞 2022-04-17 22:10:52
    高并发海量数据下的缓存和数据库一致性实战方案,看完起飞
  • 文章目录一、同时更新数据库缓存1. 先更新缓存再更新数据库2. 先更新数据库再更新缓存并发问题二、删除缓存1. 先删除缓存,后更新数据库2. 先更新数据库,后删除缓存如果数据库更新成功,缓存删除失败如何解决?...
  • 缓存是我们经常用到的,而如何解决缓存和数据库一致性也是一个挺让人头疼的问题。
  • 本文主要探讨几种常见的缓存的读写模式,以及如何来保证缓存和数据库的数据一致性。 Cache-Aside Cache-Aside可能是项目中最常见的一种模式。它是一种控制逻辑实现在应用程序中的模式。缓存和数据库直接进行交互...
  • 如何保证缓存和数据库一致性

    千次阅读 2022-03-29 18:27:47
    很多小伙伴在面试的时候,应该都遇到过类似的问题,如何确保缓存和数据库一致性? 如果你对这个问题有过研究,应该可以发现这个问题其实很好回答,如果第一次听到或者第一次遇到这个问题,估计会有点懵,今天我们...
  • 分布式缓存是现在很多分布式应用中必不可少的组件,但是用到了分布式缓存,就可能会涉及到缓存数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题? Cache Aside Pattern 最...
  • 8种方案,保证缓存和数据库的最终一致性

    千次阅读 多人点赞 2021-11-23 21:48:47
    由于对数据库以及缓存的整体操作,并不是原子性的,再加上读写并发,究竟什么样的方案可以保证数据库缓存一致性呢? 下面介绍8种方案,配合读写时序图,希望你能从其中了解到保证一致性的设计要点。
  • 如何保证缓存数据库一致性

    千次阅读 2020-12-26 16:54:04
    先删除缓存数据库还没有更新成功,此时如果读取缓存缓存不存在,去数据库中读取到的是旧值,缓存一致发生。 解决方案 延时双删 延时双删的方案的思路是,为了避免更新数据库的时候,其他线程从缓存中...
  • 事务解决的也是数据一致性的问题(业务层与数据库层面的一致性),看来这个也跟数据库的事务没有关系。 4、我们可以采用队列来实现,read的时候判断当前队列中是否存在删除操作,如果存在直接等待,如果没有直接...
  • 如何保证Redis缓存数据库一致性

    万次阅读 多人点赞 2022-01-07 09:50:38
    想要保证缓存数据库的双写一致,一共有4种方式,即4种同步策略: 先更新缓存,再更新数据库; 先更新数据库,再更新缓存; 先删除缓存,再更新数据库; 先更新数据库,再删除缓存。 从这4种同步策略中,我们需要...
  • 不仅要更新数据库,而且要更新缓存,这两个更新操作存在前后的问题:没想到太多,他觉得最新的数据肯定要先更新数据库,这样才可以确保数据库里的数据是最新的,于是他就采用了「先更新数据库,再更新缓存」的方案。...
  • 数据库缓存一致性解决方案

    千次阅读 2022-04-06 13:03:31
    最近在面试的过程中,有遇到面试官问我这个问题,觉得还是有必要看一下,那就是缓存数据库一致性问题 在自己开发单体应用的时候,往往是一个后端服务加一个数据库服务就ok了,但是,在实际开发中,还需要根据...
  • 缓存和数据库保持一致性主要是指当数据发生更新时如何保证同时更新缓存和数据库的问题。 一致性保证方式 1、设置失效时间,到期自动失效 优点:实现简单。 缺点:有延迟,一旦设置就不可控,存在固定不变的延迟时间...
  • redis缓存数据库一致性问题解决

    千次阅读 2021-07-02 11:34:53
    数据库的数据和缓存中的数据如何达到一致性?首先,可以肯定的是,redis中的数据和数据库中的数据不可能保证事务性达到统一的,这个是毫无疑问的,所以在实际应用中,我们都是基于当前的场景进行权衡降低出现不一致...
  • 如果先更新缓存成功,但是数据库更新失败,会造成数据不一致,不使用 2. 先删除缓存,后更新数据库 2.1 可能出现的问题 请求A进行写操作,删除缓存 请求B查询发现缓存不存在 请求B去数据库查询得到旧值 请求B将...
  • 解决缓存和数据库双写数据一致性问题缓存的作用缓存和数据库双写不一致的原因并发引发的一致性问题先更新数据库,后更新缓存先删除缓存,后更新数据库先更新数据库,后删除缓存如何保证「第二步操作失败」的双写一致...
  • 本篇文章主要内容 数据缓存 为何要使用缓存 哪类数据适合缓存 缓存的利与弊 如何保证缓存和数据库一致性 不更新缓存,而是删除缓存 先操作缓存,还是先操作数据库 非要保证数据库和缓存数据强一致该怎么办 缓存和...
  • 缓存和数据库一致性更新原则缓存是一种高性能的内存的存储介质,它通过key-value的形式来存储一些数据;而数据库是一种持久化的存储复杂关系的存储介质。使用缓存和数据库结合的模式就使得软件系统的性能得到了更好...
  • Redis怎么保持缓存数据库一致性

    万次阅读 多人点赞 2018-08-26 09:42:31
    将不一致分为三种情况: 1. 数据库有数据,缓存没有数据; 2. 数据库有数据,缓存也有数据,数据不相等; 3. 数据库没有数据,缓存有数据。   在讨论这三种情况之前,先说明一下我使用缓存的策略,也是...
  • 如何保证缓存数据库数据的一致性

    千次阅读 2022-05-15 12:27:25
    使用同步删除方案,你必须在所有更新数据库的地方都进行缓存的删除操作,如果你有一个地方漏掉了,对应的缓存就相当于没有删除了,就会导致脏数据问题。 还有就是如果我们通过命令行直接来更新数据库的数据,或者...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 271,633
精华内容 108,653
关键字:

缓存和数据库一致性