引言
在现在的零碎开发中,为了进步业务和接口的处理速度,缓存数据曾经变成开发模式的惯例操作。通过引入缓存缩小数据库的查问操作,进步数据的查问速度。但任何一件事件都要从它的两个面去看。引入缓存在带来诸多劣势的同时,也相应的进步了零碎的复杂性,比方:如何保障缓存和数据库的一致性。
缓存策略
在理论业务中,咱们常常采纳的一种缓存策略如下:
- 缓存 - 数据库读流程
- 用户发动查问申请
- 业务服务首先依据要害参数作为 key 查问缓存
- 如果数据在缓存中存在 cache hit,则间接返回缓存中查问后果。
- 如果数据不在缓存中 cache miss,则进行数据库查问操作,将后果缓存并返回查问后果。
- 缓存 - 数据库写流程
- 用户发动申请,须要写数据。
- 业务服务在实现逻辑解决后,开始更新数据库。
- 数据库更新实现后依据 key 删除缓存数据(or 更新?)
上述这种数据缓存策略被称为 旁路缓存 策略(Cache-Aside Strategy),其核心思想是:只有当有利用来申请时,才将对应的对象进行缓存。并且这种策略 实用于读取频繁然而写入或更新不频繁 的场景,即数据一旦写入后次要用于查问展现,根本不会更新。
另外罕用的还有其余两种策略:
- 读写穿透缓存策略(Read-Through/Write-Through Caching Strategy):读写申请由缓存层对立封装解决,业务服务仅操作缓存。
- 异步写入缓存策略(Write-Behind Caching Strategy):数据读取与 Read-Through 相似,然而数据写入由独立线程异步批量解决更新数据库。
以上为罕用的三种缓存策略,前期再做具体阐明,本文仅针对 Cache-Aside 进行剖析阐明。
问题引入
在 Cache-Aside 策略下,当呈现数据写入 / 更新申请解决中有这样两个问题须要抉择:
- 问题一:对缓存中的老数据进行更新还是删除?
- 问题二:在解决时先更新数据库还是先解决缓存
更新 OR 删除
假如咱们抉择的是缓存更新,上面来剖析在理论多申请并发的状况下
- 同时有申请 A 和 B 对数据进行更新操作;
- 在各自的业务线程 A 和 B 中对申请进行解决;
- 线程 A 更新数据库为 90,线程 B 更新数据库为 80;
- 因线程 A、B 并发执行, B 优先更新了缓存,随后线程 A 执行缓存更新,导致数据库中值为 80,缓存中数据为 90,呈现数据库和缓存的不统一。
基于这个场景来看,的确抉择删除缓存能够避免出现相似问题,最多会呈现 cache miss,触发从数据库查问加载。然而,删除缓存就是完满的吗?
先数据库 OR 先缓存
在规范的做法里,咱们抉择的是先实现数据库的更新操作,而后操作缓存。首先业务操作的后果只有在数据库实现长久化,才算是实现的标记,其次咱们来看下先淘汰缓存可能呈现的问题。
- 同时有申请 A 进行数据更新和申请 B 进行查问;
- 线程 A 先实现缓存删除操作;
- 因为并发的存在,线程 B 在 A 删除缓存后执行,因 cache miss 触发数据库查问加载
- 线程 B 实现数据库查问,失去旧的数据 100,并缓存查问后果。
- 线程 A 实现数据库更新,数据库中后果为 90,导致呈现缓存和数据库的不统一。
那如果抉择先更新数据库,肯定能保障一致性吗?不肯定。
- 场景一:缓存删除失败。
在实现数据库的操作后,因为缓存服务等起因导致缓存删除失败,导致数据库和缓存呈现不统一。
- 场景二:缓存生效
通常咱们缓存的数据都会设置肯定的有效期,那么还是回到多申请并发的状况下
- 同时有申请 A 进行数据更新和申请 B 进行查问;
- 线程 A 进行数据库更新操作;
- 线程 B 查问申请时缓存数据已过期,触发数据库查问加载(这里等同于先执行了删除)。线程 B 实现数据库查问拿到老数据 100;
- 线程 A 实现数据库更新为 90,而后删除缓存
- 线程 B 执行缓存操作,设置缓存数据为 100。缓存和数据库呈现了不统一。
然而这些状况呈现须要几个前提:
一是缓存平台出现异常,概率较低;
二是缓存数据过期,并且是数据库查问操作比更新操作耗时更久,导致后设置缓存,概率能够说是极小。
能保持一致吗?
通过下面几种场景的剖析,会发现即便咱们抉择规范的旁路缓存的策略,仍然没方法保障 100% 的数据统一。到这里,就须要引入分布式系统下外围的 CAP 实践。基于 CAP 实践剖析,应用缓存的零碎属于 CAP 实践中的 AP,所以咱们无奈保障强一致性,而只能实现 BASE 实践中最终的统一,即保障缓存和数据库这个数据最终统一。
最终一致性强调的是零碎中所有的数据正本,在通过一段时间的同步后,最终可能达到一个统一的状态。因而,最终一致性的实质是须要零碎保障最终数据可能达到统一,而不须要实时保证系统数据的强一致性。
最终统一计划
延时双删计划
从名字能够看出计划的实质在于在提早肯定工夫后,再进行一次缓存的删除,来解决并发状况下缓存到老数据的问题,即便先操作缓存后操作数据库也能够保障最终数据的统一。
- 计划流程
- 用户发动申请,须要写入更新数据
- 业务服务首先进行删除缓存
- 而后业务服务进行数据库的更新操作
- 在提早肯定工夫 T 后,再执行一次缓存删除。
- 计划剖析
该计划的外围点在于 延迟时间 T ,通常咱们把 T 设置为雷同业务中 一次查问操作耗时 + 几百毫秒,这样保障了第二次的删除能够革除掉因并发导致的缓存脏数据。
该计划的劣势在于:
- 须要针对兴许评估延迟时间,并减少二次删除逻辑,代码强耦合,减少了复杂度。
- 二次删除也可能呈现缓存失败。
缓存删除重试
为了保障缓存删除胜利,须要在缓存失败时减少重试机制。能够借助音讯队列,将删除失败的数据进行异步重试。
- 用户发动申请,须要写入更新数据
- 业务服务首先进行数据库更新操作
- 而后业务服务进行缓存删除,因某些起因导致失败
- 将删除失败缓存 key 进入音讯队列
- 生产音讯队列中的音讯,获取须要重试的缓存 key
- 重试缓存删除操作
- 计划剖析
该计划尽管将重试逻辑拆除独立执行,但须要在失常业务逻辑中退出删除失败解决代码,侵入性很强。上面看借助 MySQL BinLog 实现缓存删除的计划
BinLog 缓存删除计划
数据库的 BinLog 存储了对数据库的更改操作日志记录,通过订阅该日志,来进行缓存的更新,业务代码不再关怀缓存更新操作。
- 用户发动申请,须要写入更新数据
- 业务服务进行数据库更新操作实现业务申请
- 数据库操作写入 BinLog 日志
- 通过中间件订阅数据库 BinLog 日志(如:canel),获取须要更新缓存的 key 和数据
- 依据解析后果进行缓存删除,如果删除失败则放入音讯队列
- 生产音讯队列中的音讯,获取须要重试的缓存 key
- 重试缓存删除操作
总结
缓存和数据库一致性问题的呈现在于高并发申请下缓存操作和数据库操作不是原子性的导致,尽管能够通过引入诸多的计划来保证数据的最终统一,但无论哪种计划都大大增加了零碎的复杂度,同时引入更多问题。因而须要正当的评估业务,对数据一致性的敏感水平来抉择适合的计划,没必要为了谋求统一而统一。