明天咱们来聊聊缓存一致性问题,对于这个问题,不论在工作中还是面试中,都是一个十分常见的问题。
明天咱们的主题是: 缓存一致性问题
老规矩,上纲要:
1、缓存一致性问题是什么
咱们晓得,缓存的工作原理是先从缓存中获取数据,如果有数据则间接返回给用户,如果没有数据则从慢速设施上读取理论数据并且将数据放入缓存。就像这样:
然而,这样的架构是存在问题的,因为数据库与缓存是不同的组件,操作必须有先后顺序,无奈像数据库的事务一样满足 ACID 的个性,所以就会呈现数据在缓存中与在数据库中不统一的问题。
缓存一致性问题的体现:同一份数据,缓存中的数据与数据库中的数据不统一,那么回升到业务层面就有着千奇百怪的景象了,比方每次读都是读的老数据,或者每次读是一份过期的数据等。
2、解决方案
对于写入操作:
- 只写 DB,不写 Cache,依赖下次查问
- 先写 DB,(同步 / 异步)再写 Cache
- 先写 Cache,再写 DB
对于更新操作:
- 先更新 DB,再删除 Cache
- 先删除 Cache,再更新 DB
- 先删除 Cache,再更新 DB,再删除 Cache
2.1、只写 DB,不写 Cache,依赖下次查问
这种是咱们常见的设计方案,这种计划只写数据库不写缓存,依赖下一次申请从数据库取出数据再放入缓存。仔细的读者曾经发现了,这种设计有可能引发新的问题:缓存击穿(温习缓存击穿:DB 有数据,Cache 无数据,霎时流量将 DB 击穿)。
这种可能性是存在的,然而可能性比拟小,因为缓存击穿的前提条件是大量申请透过缓存打入数据库层,然而因为咱们探讨本次小标题的前提条件是新写入,个别不会有很大的霎时流量进来。就算有,那也不属于本文缓存一致性问题的探讨范畴了。
2.2、先写数据库,再写缓存
这种也是咱们常见的设计方案,先写数据库,再写缓存,下面的图也有体现这一点。
所以在这种场景下,线程 1 再去读数据的时候,读数据则优先走缓存,缓存此时值为 1,所以读到的值是 1,此时线程 1 懵逼了啊 …… 我方才不是更新成 2 了吗?
在面临缓存穿透的时候,咱们其中一个解决方案是:查询数据库如果没有数据,则约定一个空数据格式放入缓存中,当再次查问的时候,先查问缓存,发现是一个空数据格式,则间接返回空,防止数据库被霎时流量击垮。在这个计划下,还有第二个步骤,当数据保留后,须要被动将数据放入缓存,以便下次可能查问。
所以如果你的零碎中如果有做缓存穿透的防护,有可能你写完数据库后还须要记得写缓存。
2.3、先写缓存,再写数据库
顾名思义,就是一个写操作,先写缓存,胜利后,再写数据库。
那么,如果写数据库失败呢?
如果写失败了,在下次读的时候那么就会读取到脏数据的状况。
如果写数据库失败,有两种计划
- 删除缓存
- 异步工作持续写数据库
这两个计划都有问题!
上面咱们挨个剖析。
删除缓存。如果删除缓存失败呢?再用异步工作重试删除?那你是否有思考重试的时候这种短暂不统一的状况?还是说承受这种数据不统一的状况?零碎复杂度被你进步了多少?
异步工作持续写数据库。异步工作如果写失败呢?重试?重试也始终失败呢?重试工作落库 + 定时工作兜底?能够,那么,短暂的数据不统一是否承受?零碎复杂度被你进步了多少?
所以,这种先写缓存再写数据库的计划个别不会正式应用,一旦出问题,很难保证数据的最终一致性。
接下来咱们讨论一下更新数据的状况。
2.4、先更新数据库,再删除缓存
这种状况下,你可能想说这是你们当初正在应用的技术计划,然而我想说是这个计划是存在问题的,别慌反驳,大家看看这张图:
首先,这种技术计划,的确是咱们在日常开发中是最常见的,然而作为开发的咱们,也应该明确它存在什么问题,以及可能有哪些应答措施,上面谈谈我集体对这个解决方案的一些改良。
- 提早删除缓存。
- 先删除缓存,再更新数据库。
- 提早双删策略。
- 定时工作增量 / 全量更新缓存数据。
- 监听数据库 binlog 增量数据更新缓存。
计划一:提早删除缓存。这种改良计划的长处是能无效的避免数据不统一,但不可能齐全避免。为什么说不可能齐全避免呢?因为查问数据的那个线程有可能也提早了肯定工夫才去更新缓存。这个改良计划的毛病是无奈严格的管制工夫,这个工夫须要开发人员依据教训给出,第二个毛病是提早行为有可能让零碎引入一些新的依赖,你可能想说是否能够用 jdk 自带的提早队列呢?能够,然而如果提早期间,服务重启了,怎么解决?第三个毛病是可能导致系统的复杂度进步、保护老本进步、可读性升高。
计划二:先删除缓存,再更新数据库。这个计划咱们上面独自细说,这里咱不介绍。
计划三:提早双删策略。这个计划咱们上面独自细说,这里咱不介绍。
计划四:定时工作增量 / 全量更新缓存数据。这种解题形式是最间接最暴力的,它的长处是可能保证数据的最终一致性。它的毛病有:可能须要引入散布式调度工作(如果不引入则又存在多实例同时更新的状况,纯属浪费资源,或者加分布式锁)、如果是增量同步的话则须要有一种形式办法辨别出什么数据才是增量数据,这种形式可能有业务侵入和性能影响、如果是全量同步的话数据量太多又太耗时,重大的话可能导致工作阻塞以及减轻数据不统一的问题。通过剖析,长处很显著,个别状况下,异步被动的对缓存数据更新是一种不可采取的形式。然而也会有一些业务场景,数据变更不太频繁,然而拜访十分频繁,并且更新数据更新工夫曾经同步更新缓存了,再应用这种异步将 DB 数据载入缓存作为兜底的策略是可行的。
计划五:监听数据库 binlog 增量数据更新缓存。
这种形式让开发不再关注缓存层,专一于业务开发,只关注于数据库,而不必关怀缓存。
能够看到这种计划对研发人员来说比拟轻量,不必关怀缓存层面,而且这个计划尽管比拟重,然而却容易造成对立的解决方案。
2.5、先删除缓存,再更新数据库
这种形式也比拟容易了解,先删除缓存数据,再更新 DB 的数据,如果删除缓存失败了,间接返回失败;如果更新 DB 失败了,影响的也只是删除缓存而已,下次查问的时候从新种一次即可。
那如果,会不会因为删除了缓存的数据,从而导致 DB 被击穿呢?这种可能性是存在的,然而可能性比拟小。
再说了,这种计划真的能够解决问题吗?如果在删除缓存后,马上有新线程查问缓存,新线程发现缓存不存在(刚被删),新线程查询数据库后将数据放于缓存,老线程删除数据库胜利。此时数据库无数据,缓存有数据。
2.6、先删除缓存,再更新数据库,再删除缓存
基于 2.5,在这个根底上能够做出一些改良,那就是提早双删。
提早双删的流程:删除缓存 -> 删除 DB-> 提早一段时间再删除缓存。
提早双删能解决大部分的问题,然而在极其状况下,还是会呈现问题,造成数据不统一。
这里存在一个问题,提早一段时间,是提早多久?1s?3s?这是一个经验值,个别状况是 1s~2s。具体取值依据监控理论状况而定。那既然是估计值,那么就肯定存在误差,所以必然极其状况下的数据不统一问题。
解决这个问题的办法之前也说了,监听数据库 binlog 增量数据更新缓存,或者还能够应用异步音讯等。
3、总结
在理论的工作中,或者在面试中,如果有人问你各种没有场景化的纯正的技术问题,比如说有人看了下面的种种计划还是会提出疑难,你的这些计划依然存在数据不统一的问题啊,那怎么解决呢?
技术是为了业务服务的,所以,在所有不同的业务场景下,对于技术的抉择,和计划的设计都是不同的。咱们须要反诘他,具体的业务场景是什么?咱们须要依据具体的业务场景来抉择最合适的技术计划。
咱们要明确的是:一个技术计划不可能 cover 住所有的场景,脱离业务的技术都是刷流氓。