关于缓存更新的一些可借鉴套路

简介: 目前随着缓存架构方案越来越成熟化,通常做法是引入「缓存」来提高读性能,架构模型就变成了这样:

一、背景


目前随着缓存架构方案越来越成熟化,通常做法是引入「缓存」来提高读性能,架构模型就变成了这样:

微信图片_20220609111600.png


先来看一下什么时候创建缓存,前端请求的读操作先从缓存中查询数据,如果没有命中数据,则查询数据库,从数据库查询成功后,返回结果,同时更新缓存,方便下次操作。


在数据不发生变更的情况下,这种方式没有问题,如果数据发生了更新操作,就必须要考虑如何操作缓存,保证一致性。


如何保证缓存和数据库的一致性,这算得上是个老生常谈的话题啦,看到好多技术新人在写更新缓存数据代码,采用了非常复杂甚至“诡异”的方案,甚为不解。

今天就一起花点儿时间来聊聊吧~


二、缓存和数据库数据一致性问题


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


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


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


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


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


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


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


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


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


三、缓存更新Design Pattern


介绍几个也许有效的套路给大家吧~ 希望有帮助。


1)Cache Aside Pattern

微信图片_20220609111603.png

微信图片_20220609111606.png

如上图所示,一个是查询操作,一个是更新操作的并发。


首先,没有了删除cache数据的操作了,而是先更新了数据库中的数据,此时,缓存依然有效,所以,并发的查询操作拿的是没有更新的数据,但是,更新操作马上让缓存的失效了,后续的查询操作再把数据从数据库中拉出来。而不会像文章开头的那个逻辑产生的问题,后续的查询操作一直都在取旧数据。


那么,是不是Cache Aside这个就不会有并发问题了?


不是的。比如,一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,所以,会造成脏数据。


2)Read/Write Through Pattern


  • Read Through


Read Through 套路就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出),Cache Aside是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对应用方是透明的。


  • Write Through


Write Through 套路和Read Through相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)


操作逻辑如下图所示:

微信图片_20220609111610.png


3)Write Behind Caching Pattern


基本逻辑如下:

微信图片_20220609111615.png

Write Behind 又叫 Write Back。


简单说就是,在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作飞快无比(直接操作内存的嘛 ),因为异步,write backg还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。


但是,其带来的问题是,数据不是强一致性的,而且可能会丢失(我们知道Unix/Linux非正常关机会导致数据丢失,类似这种情况)。


另外,Write Back实现逻辑比较复杂,因为他需要track有哪些数据是被更新了的,需要刷到持久层上。操作系统的write back会在仅当这个cache需要失效的时候,才会被真正持久起来,比如,内存不够了,或是进程退出了等情况,这又叫lazy write。


四、总结


对于这个老生常谈的问题,分析起来其实并不简单。


额外分享几点自己心得给你:


1、性能和一致性不能同时满足,为了性能考虑,通常会采用「最终一致性」的方案;


2、掌握缓存和数据库一致性问题,核心问题有 3 点:缓存利用率、并发、缓存 + 数据库一起成功问题;


3、失败场景下要保证一致性,常见手段就是「重试」,同步重试会影响吞吐量,所以通常会采用异步重试的方案;


4、订阅变更日志的思想,本质是把权威数据源(例如 MySQL)当做 leader 副本,让其它异质系统(例如 Redis / Elasticsearch)成为它的 follower 副本,通过同步变更日志的方式,保证 leader 和 follower 之间保持一致。


相关文章
|
缓存 Java 数据库连接
|
消息中间件 存储 canal
到底是先更新数据库还是先更新缓存?
很多小伙伴最近都在问我,在系统中引入缓存后,当向数据库中写入数据时,是先写数据库还是先写缓存呢?先写数据库和先写缓存有什么区别吗?今天,我们就一起来聊聊这个话题。
213 0
到底是先更新数据库还是先更新缓存?
|
缓存 Java
【多线程:cas】原子更新器 原子累加器 缓存一致性问题
【多线程:cas】原子更新器 原子累加器 缓存一致性问题
115 0
|
缓存 Java fastjson
实战监听Eureka client的缓存更新
Spring cloud环境中的应用,如果注册到Eureka server,就会从Eureka server获取所有应用的注册信息(也叫服务列表),然后保存到本地,这个操作是周期性的,默认每三十秒一次,今天咱们来通过实战将其观察得清清楚楚
254 0
实战监听Eureka client的缓存更新
|
缓存
【Auto.js】使用命令删除图片后,更新图库缓存
【Auto.js】使用命令删除图片后,更新图库缓存
437 0
|
缓存 数据库 Memcache
你是如何更新缓存的?看懂这篇缓存读写策略(上)
你是如何更新缓存的?看懂这篇缓存读写策略
154 0
你是如何更新缓存的?看懂这篇缓存读写策略(上)
|
存储 消息中间件 缓存
你是如何更新缓存的?看懂这篇缓存读写策略(下)
你是如何更新缓存的?看懂这篇缓存读写策略
152 0
你是如何更新缓存的?看懂这篇缓存读写策略(下)
|
消息中间件 存储 canal
到底是先更新数据库还是先更新缓存?
各系统存储的数据量越来越大,数据的查询性能越来越低。此时,就需要我们不断的进行优化,一种常用的优化手段就是引入缓存。而引入缓存后,我们在向数据库插入数据时,到底是先更新数据库还是先更新缓存呢?
630 1
到底是先更新数据库还是先更新缓存?
|
Web App开发 缓存 JavaScript
js的动态加载、缓存、更新以及复用(一)
使用范围:   OA、MIS、ERP等信息管理类的项目,暂时不考虑网站。   遇到的问题:   完成一个项目,往往需要引用很多js文件,比如jQuery.js、easyUI等。还有自己写的一些列js文件,那么这些文件如何方便的加载,如果文件有变化如何才能让客户端及时更新缓存?如果能够提高点运行效率,那就更好了。
1866 0