缓存与数据库的一致性

https://blog.csdn.net/huazhongkejidaxuezpp/article/details/88945627

背景
           cache如memcache,redia等缓存来缓存数据库读取出来的数据,以提高读性能。但凡是使用缓存的项目,几乎都会遇到一个普遍的问题: 在不断增删改数据的过程中,如何保持缓存与数据库中数据的一致性。在支付、下单类业务中,此类问题尤为普遍。下面就自己对此的一些理解。浅谈一下自己的看法。

缓存的衡量指标
命中率、响应时间;缓存一致性。

命中率:请求的命中率与miss率。可以通过一定的工具监控到。

响应时间:包括命中时的响应时间,非命中时的响应时间。

缓存一致性:在不断进行增删改操作后,缓存中的数据是否与数据库数据保持一致,避免二者不一致,获取数据不正确的情况发生。

使用缓存可能带来的问题
         分布式环境下非常容易出现缓存和数据库间的数据一致性问题。在使用缓存前,如果你的项目对缓存的要求是强一致性的,那么请不要使用缓存。我们只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性。

经典的几个问题如下:

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

问题描述:缓存和数据库间的数据不一致。缓存与数据库不一致的情况,大致分为三类:

数据库有数据,缓存没有数据;

数据库有数据,缓存也有数据,数据不相等;

数据库没有数据,缓存有数据。

合适的策略:包括 合适的缓存更新策略,更新数据库后要及时更新缓存、缓存失败时增加重试机制,例如MQ模式的消息队列。

2)缓存击穿问题

问题描述:缓存击穿表示恶意用户模拟请求很多缓存中不存在的数据,由于缓存中都没有,导致这些请求短时间内直接落在了数据库上,导致数据库异常。

场景举例:抢购活动、秒杀活动的接口API被大量的恶意用户刷,导致短时间内数据库宕机了

解决方案:

使用互斥锁排队
业界比较普遍的一种做法,即根据key获取value值为空时,锁上,从数据库中load数据后再释放锁。若其它线程获取锁失败,则等待一段时间后重试。这里要注意,分布式环境中要使用分布式锁,单机的话用普通的锁(synchronized、Lock)就够了。

从一定程度上减轻数据库压力,但是锁机制使得逻辑的复杂度增加,吞吐量也降低了,有点治标不治本

布隆过滤器(推荐)
bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小

3)缓存雪崩问题

问题描述:缓存在同一时间内大量键过期(失效),接着来的一大波请求瞬间都落在了数据库中导致连接异常。

解决方案:

加锁排队
建立备份缓存,缓存A和缓存B,A设置超时时间,B不设值超时时间,先从A读缓存,A没有读B,并且更新A缓存和B缓存;
4)缓存并发问题

问题描述:多个redis的client同时set key引起的并发问题

解决方案:

把redis.set操作放在队列中使其串行化,必须的一个一个执行
加锁
缓存使用/更新套路
1. 首先尝试从缓存读取,读到数据则直接返回;如果读不到,就更新数据库,并将数据会写到缓存,并返回。

2. 需要更新数据时,先更新数据库,然后把缓存里对应的数据失效掉(删掉)。

进一步思考:
先删除缓存,然后再更新数据库:如果A,B两个线程同时要更新数据,并且A,B已经都做完了删除缓存这一步,接下来,A先更新了数据库,C线程读取数据,由于缓存没有,则查数据库,并把A更新的数据,写入了缓存,最后B更新数据库。那么缓存和数据库的值就不一致

为什么最后是把缓存的数据删掉,而不是把更新的数据写到缓存里。这么做引发的问题是,如果A,B两个线程同时做数据更新,A先更新了数据库,B后更新数据库,则此时数据库里存的是B的数据。而更新缓存的时候,是B先更新了缓存,而A后更新了缓存,则缓存里是A的数据。这样缓存和数据库的数据也不一致。

效果:理论上也是有不一致的风险的,但概率很小。不一致原因:产生的原因是更新数据库成功,但是删除缓存失败。可以进一步考虑的方案:

1. 对删除缓存进行重试,数据的一致性要求越高,我越是重试得快。

2. 定期全量更新,简单地说,就是我定期把缓存全部清掉,然后再全量加载。

3. 给所有的缓存一个失效期。失效期越短,数据一致性越高。但是失效期越短,查数据库就会越频繁。因此失效期应该根据业务来定

进一步思考,当更新数据库时候,缓存应该如何更新  
更新缓存VS淘汰缓存

答:更新缓存很直接,但是涉及到本次更新的数据结果需要一堆数据运算(例如更新用户余额,可能需要先看看有没有优惠券等),复杂度就增加了。而淘汰缓存仅仅会增加一次cache miss,代价可以忽略,所以建议淘汰缓存。 当然了可以采用更新缓存而不是淘汰缓存,前提是更新的代价比较低

测试思路
          测试缓存的时候,除了业务在正常情况下需要正常交互外,尤其是热点key过期的时候,需要测试击穿,以及雪崩,穿透等情形对下游DB的并发请求带来的影响。

测试场景:

1、命中情况下:响应情况

2、并发下、非命中下响应情况

思考点

测试点

(雪崩)缓存会不会集中失效?如会,怎么避免?

1、 (如果会失效)模拟集中失效场景

2、查看服务响应情况

3、如果有措施,测试措施的效果。

措施例如:

在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。
缓存key是什么?为什么?

1、key的内容

2、缓存占用的大小

会不会出现缓存穿透?如何避免?

缓存穿透是指查询一个一定不存在的数据

1、模拟穿透情况

2、查看服务响应情况

3、测试应对方案:

1)使用互斥锁

2)从数据库找不到的时候,我们也将这个空对象设置到缓存里边去

3)布隆过滤器(BloomFilter)或者压缩filter提前拦截,不合法就不让这个请求到数据库层

如果缓存挂了,业务是否会有影响?怎么控制这里的影响面?

1、模拟缓存挂了的情况

2、查看服务响应情况

3、测试应对缓存挂掉的方案:

事发前:实现redis的高可用,尽量避免缓存全部挂掉

事发中:万一redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),确保业务正常

事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。

什么时候更新缓存?如何确保更新成功?

1、模拟业务(并发)各种增删改查的情况

2、检查缓存更新情况

3、测试不同更新策略

ps-两种策略各自有优缺点:

先删除缓存,再更新数据库:在高并发下表现不如意,在原子性被破坏时表现优异;
先更新数据库,再删除缓存(Cache Aside Pattern设计模式):在高并发下表现优异,在原子性被破坏时表现不如意
失效时间是多少?为什么?

1、失效时间设置多少

2、失效后影响

缓存方案命中率/miss率多少

1、模拟缓存使用场景

2、评估命中率/miss率

3、使用工具监控命中率/miss率

原文地址:https://www.cnblogs.com/cuiqq/p/12005972.html

时间: 2024-10-12 02:10:28

缓存与数据库的一致性的相关文章

缓存与数据库的一致性思考

时隔两年,重新启动这个博客.熟悉又有点陌生,这两年我的技术方向有了很大改变,但由于一直在使用为知笔记,因此这些改变没有提现在本博客上.之所以重启这个博客,主要是因为博客是一个开放的东西,可以带来一些交流,而笔记则是个人的东西,缺少思维碰撞.闲话少叙,这就开始. 问题:怎么保持缓存与数据库一致? 要解答这个问题,我们首先来看不一致的几种情况.我将不一致分为三种情况: 1. 数据库有数据,缓存没有数据: 2. 数据库有数据,缓存也有数据,数据不相等: 3. 数据库没有数据,缓存有数据. 在讨论这三种

缓存与数据库一致性保证

本文主要讨论这么几个问题: (1)啥时候数据库和缓存中的数据会不一致 (2)不一致优化思路 (3)如何保证数据库与缓存的一致性 一.需求缘起 上一篇<缓存架构设计细节二三事>引起了广泛的讨论,其中有一个结论:当数据发生变化时,"先淘汰缓存,再修改数据库"这个点是大家讨论的最多的. 上篇文章得出这个结论的依据是,由于操作缓存与操作数据库不是原子的,非常有可能出现执行失败. 假设先写数据库,再淘汰缓存:第一步写数据库操作成功,第二步淘汰缓存失败,则会出现DB中是新数据,Cach

缓存与数据库一致性保证(转)

本文主要讨论这么几个问题: (1)啥时候数据库和缓存中的数据会不一致 (2)不一致优化思路 (3)如何保证数据库与缓存的一致性 一.需求缘起 上一篇<缓存架构设计细节二三事>(点击查看)引起了广泛的讨论,其中有一个结论:当数据发生变化时,"先淘汰缓存,再修改数据库"这个点是大家讨论的最多的. 上篇文章得出这个结论的依据是,由于操作缓存与操作数据库不是原子的,非常有可能出现执行失败. 假设先写数据库,再淘汰缓存:第一步写数据库操作成功,第二步淘汰缓存失败,则会出现DB中是新数

【58沈剑架构系列】缓存与数据库一致性保证

本文主要讨论这么几个问题: (1)啥时候数据库和缓存中的数据会不一致 (2)不一致优化思路 (3)如何保证数据库与缓存的一致性 一.需求缘起 上一篇<缓存架构设计细节二三事>(点击查看)引起了广泛的讨论,其中有一个结论:当数据发生变化时,“先淘汰缓存,再修改数据库”这个点是大家讨论的最多的. 上篇文章得出这个结论的依据是,由于操作缓存与操作数据库不是原子的,非常有可能出现执行失败. 假设先写数据库,再淘汰缓存:第一步写数据库操作成功,第二步淘汰缓存失败,则会出现DB中是新数据,Cache中是旧

Java进阶面试必问:如何保证缓存与数据库的双写一致性?

面试题 如何保证缓存与数据库的双写一致性? 面试官心理分析 你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题? 面试题剖析 一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求 "缓存+数据库" 必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去. 串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多

经典好文--如何保证缓存和数据库的双写一致性

面试题如何保证缓存与数据库的双写一致性? 面试官心理分析你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题? 面试题剖析一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求 “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去. 串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑

Redis缓存和数据库一致性问题

工作中,经常会遇到缓存和数据库数据一致性问题.从理论上设置过期时间,是保证最终一致性的解决方案.这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可.也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存.因此,接下来讨论的思路不依赖于给缓存设置过期时间这个方案. 在这里,我们讨论三种更新策略: 1) 先更新数据库,再更新缓存 2) 先删除缓存,再更新数据库 3) 先更新数据库,再删除缓

PHP中高级面试题 一个高频面试题:怎么保证缓存与数据库的双写一致性?

分布式缓存是现在很多分布式应用中必不可少的组件,但是用到了分布式缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题? Cache Aside Pattern 最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern.读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应.更新的时候,先更新数据库,然后再删除缓存. 为什么是删除缓存,而不是更新缓存? 原因很简单,很多时候,在复杂点的缓存场景

9.如何保证缓存与数据库的双写一致性?

作者:中华石杉 面试题 如何保证缓存与数据库的双写一致性? 面试官心理分析 你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题? 面试题剖析 一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求 “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去. 串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情