如何保证缓存与数据库的一致性
This is a hidden message
对于读多写少的场景,为了减缓数据库的压力,提高请求响应速度,我们会引入缓存;而引入缓存之后随之而来的就是数据一致性问题。
谈谈一致性
一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。
- 强一致性:这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大
- 弱一致性:这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态
- 最终一致性:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型
缓存策略
对于缓存有两种常见的方式:Cache-Aside和Write-Through
Cache-Aside
旁路缓存也叫延迟加载,是一种被动的缓存方式,更新数据的时候不会更新缓存,当需要读取数据时,再去更新缓存。
Cache-Aside 读取流程:
- 先读取缓存,如果命中缓存,则直接返回缓存数据;
- 如果没有命中,则先从数据库读取数据,然后更新缓存,最后返回数据。
Cache-Aside 写入流程:
写入数据时,先更新数据库,然后再删除缓存。
这里大家可能会有个疑问,为什么Cache-Aside
模式写入时要删除缓存呢?
让我们来看看下面的例子:
- 线程A发起了一个写操作,先更新了数据库;
- 线程B也发起了写操作,接着更新了数据库;
- 由于网络延时等原因,线程B先更新了缓存;
- 最后线程A更新了缓存。
这时,缓存保存的是A线程的数据(老数据),数据库保存的是B线程的数据(新数据),这里就出现了数据不一致的问题,如果用删除缓存的方式,就可以避免这个问题。
Write-Through
直写是一种主动的缓存方式,在数据库上的数据被更新之后,会立即更新缓存。
Write-Through 写入流程:
Write-Through的优点:
缓存与数据库的数据一直是最新的,能保持数据的一致性;
命中缓存的概率要大很多,能减少读库的次数。
缺点:
在多线程情况下可能会出现数据不一致的问题。
如何实现一致性
缓存延时双删
无论是先删除缓存还是先写数据库,都会出现缓存与数据库不一致的情况,可以使用延时双删策略。
- 先删除缓存;
- 再写入数据库;
- 休眠500毫秒(休眠时间根据具体的业务时间来定,一般为读请求的平均耗时+几百毫秒即可);
- 再次删除缓存。
删除缓存重试机制
在删除缓存的过程中,如果出现了删除失败,就会出现缓存数据不一致的情况,这时我们就需要引入删除缓存重试机制。
- 写请求更新数据库
- 因为某些原因,缓存删除失败
- 将删除失败的key推送至消息列队
- 接受消息列队的消息,获取删除失败的key
- 删除缓存重试
读取binlog
日志异步删除缓存
虽然上面的方式都可以解决数据不一致的问题,但是会造成业务代码入侵;
我们还可以通过binlog
日志的来异步的删除缓存。
如何保证缓存与数据库的一致性