一、前言
最近在线上遇到了因缓存和数据库数据不统一而引发的问题。经排查后,发现是因为咱们写操作采纳的策略是先写数据库,再删缓存。如果写操作后马上去读的话,因为缓存被删,会去数据库查数据。又因为数据库主从提早,会加载从库的旧数据到缓存。于是产生了数据库已被批改为新数据,但缓存仍然是旧数据的状况。
这次线上的事件,引申出了一个陈词滥调的话题,如何保障数据库与缓存一致性?明天咱们就来谈谈这个事。
二、不统一场景剖析
1、 无并发时
首先看一下最简略的状况。当一个写申请心愿把数据从D1改成D2。
因为咱们写申请须要进行两个操作:1、写数据库;2、更新/删除缓存。这两个操作并非是一个事务,所以必然有可能产生其中一个胜利一个失败的状况(个别是后一个,因为如果前一个操作失败,个别不会再做第二个操作)。这样会引申出以下两种状况。
- 先将DB数据由D1批改为D2
- 再删除或更新缓存(该步骤产生异样,即失败了)
最终,数据库数据是D2,缓存数据仍旧为D1。
- 先更新缓存数据由D1到D2
- 更新数据库产生异样
最终,数据库数据是D1,缓存数据已被批改为D2。
2、 并发时
接下来探讨更简单的状况,也就是在读写并发时可能产生的不统一。
首先明确的是,在读未命中缓存的时候,咱们的做法个别是去查数据库,而后把查到的值写入到缓存中。但在写数据时,策略往往不尽相同。咱们经常会思考两个问题。1)先操作缓存还是先操作数据库;2)删缓存还是更新缓存。
1)先DB后缓存
1. 写数据库后更新缓存
- 首先是未命中读+写操作并发的场景。
- 线程A读缓存,未命中
- 线程A读DB,失去Data1
- 线程B写DB,将数据从Data1更新至Data2
- 线程B写缓存,更新为Data2
- 线程A写缓存,更新为之前读到的Data1
最终,DB值为Data2,但缓存中值为Data1。
- 其次是写操作并发的场景。
- 线程A写DB,写入Data1
- 线程B写DB,写入Data2
- 线程A更新缓存,写入Data1
- 线程B更新缓存,写入Data2
最终,DB值为Data2,缓存值为Data1.
2. 写数据库后删除缓存
写数据库之后删除缓存,仿佛能够解决以上的问题,如下图所示。
但这不是万能的,就例如我在开篇提到的线上问题,采纳的正是写DB+删缓存策略。因为咱们我的项目读QPS十分大,但写QPS不高。故采纳了读写拆散的主从架构。写申请都在主库上进行,读申请则拜访从库,并依赖主从同步保证数据统一。因为主从同步须要工夫,就可能产生以下的状况导致DB与缓存数据不统一。
2)先缓存后DB
1. 删缓存后写数据库
- 线程A写申请,先删缓存
- 线程B读缓存,未命中
- 线程B读DB,失去D1
- 线程A写数据库,D1更新为D2
- 线程B写缓存D1
最终,DB的数据是D2,而缓存是D1。
2. 更新缓存后写数据库
- 线程B读缓存,未命中
- 线程A写申请,缓存由D1更新至D2
- 线程B读DB,失去D1
- 线程A写DB,有D1更新至D2
- 线程B写缓存D1
最终,DB的数据是D2,缓存的数据是D1。
三、不统一的解决形式
在缓存与数据库不统一之后,若过期工夫十分长,且期间没有写操作,会造成读的时候有很长一段时间数据是谬误的。那么如何去修改或者说尽量保障统一呢?
1、提早双删
顾名思义,提早双删就是在写完数据库之后,隔一小段时间\( \Delta(T) \),再删一次缓存。当然第二次删缓存是异步进行的。
对以下两种状况,采纳提早双删策略后,都能保障在一段时间后,缓存中的脏数据被删除。也就是达到了最终一致性。但期间可能有申请读到的是脏数据。
这一小段时间\( \Delta(T) \)该怎么取值呢?首先晓得,\( \Delta(T) \)之后再删一次的目标是为了删除并发的未命中读产生的脏数据。所以个别要略大于一次读的申请,且略大于主从同步的提早。
2、删除缓存重试机制
删缓存这一步可能会产生异样,为了保障删缓存胜利,能够引入重试机制。对于删缓存失败的操作,进入重试队列。重试队列选型能够是Kafka,也能够是Redis中的列表。对于一致性要求没那么高的,甚至能够在单机内存中寄存队列。
3、读取binlog校对缓存
应用组件/中间件获取数据库的binlog。binlog若采纳Row模式,解析后个别会有数据行最新数据的信息。通过这个信息去查缓存,若发现不统一则删除缓存;若统一,则不作解决。
四、总结
其实最终应用哪种策略去写数据,都要根据本人服务的个性来做取舍,并没有万能的策略(除非应用串行化或者做很多限度保证数据强统一,这时会升高零碎可用性)。
业界常常应用的Cache Aside
策略,也就是对于写申请先更新数据库再删缓存的这种做法,在咱们的服务中会遇到不少问题。所以最终改成了先更新数据库再更新缓存。
对于线上的状况,能够尝试不同的策略,并在后盾做数据库与缓存的一致性统计,联合业务特点抉择最合适的计划。