如何保证缓存(redis)与数据库(mysql)的一致性

转自:
https://blog.csdn.net/wwd0501/article/details/106902856/

【前言】

读:命中缓存、未命中缓存;
修改:缓存中与数据库都要修改;
考虑:
1、更新缓存和更新数据库的执行存在先后顺序;
2、更新缓存的策略问题:当缓存中内容变化时,是修改缓存(update),还是淘汰缓存(delete)
有四种方案:
1、更新缓存,更新数据库
2、更新数据库,更新缓存
3、淘汰缓存,更新数据库
4、更新数据库,淘汰缓存

分析正式开始

【疑问一】更新cache还是淘汰cache(淘汰good)

淘汰cache:直接delete缓存中的旧数据
更新cache:直接更新

淘汰cache
优点:操作简单,无论操作复杂与否,直接delete旧值
缺点:delete后,下一次查询会有一次cache miss,需读取数据库

更新cache
优点:命中缓存率高,不会有cache miss情况
缺点:消耗较大
当更新操作简单,更新与淘汰cache消耗差不多
当操作复杂,需要涉及到其他数据,或者与数据库打交道时,消耗大。

综上所述:用delete好,如果之后需要再次读取这个数据,最多会有一次缓存失败

【更新cache的另一个问题】
对于上文列举的四种方案的前两种,即:
    1、先更新(update)缓存,再更新数据库;
    2、先更新数据库,再更新(update)缓存;

当并发较大,同时有两个线程需要对同一个数据进行更新时,可能会出现以下问题:

方案一、先更新(update)缓存,再更新数据库
线程A更新了缓存
  线程B更新了缓存
  线程B更新了数据库
  线程A更新了数据库

方案二、先更新数据库,再更新(update)缓存
线程A更新了数据库
  线程B更新了数据库
  线程B更新了缓存
  线程A更新了缓存

两种方案导致数据不一致
不同的线程对同一个数据更新时,更新的顺序要有明确要求,解决不一致的思路是“串行化”,即对同一个数据的修改,要以串行化的方式先后执行

结论

更新cache消耗更大,可能导致数据不一致,所以推荐淘汰cache

【疑问二】执行顺序的问题

3、淘汰缓存,更新数据库
4、更新数据库,淘汰缓存

考虑:
1、更新数据库和delete缓存,需要有先后顺序,如果执行中有一个失败,哪种方案影响小
2、如果不考虑失败情况,先后顺序中,如果上一个执行完,下一个操作还未完成时,在并发很大的情况,仍旧会造成缓存和数据库中数据不一致,哪种方案影响小

本文考虑单节点,即没有主从数据库架构

【数据库是单节点】

情景一 更新数据库和delete缓存,需要有先后顺序,如果执行中有一个失败,哪种方案影响小

方案一、先delete cache,后更新数据库
第一步成功,第二步失败,此时再次查询缓存,一次缓存miss

方案二、先更新数据库,后delete cache
第一步成功,第二步失败,cache中是旧数据,数据库是新数据,数据不一致,解决办法:为确保缓存删除成功,用“重试机制”,即当删除cache失败后,返回错误,由业务代码再次重试,直至删除OK。

【结论】
总体而言,虽然方案二导致数据不一致的可能性更大,但在业务中,无论是淘汰缓存还是更新数据库,我们都需要确保它们真正完成了,所以个人认为在情景一下两种方案并没有什么优劣之分。

情景一

情景二:假设没有操作会执行失败,但执行前一个操作后无法立即完成下一个操作,在并发较大的情况下,可能会导致数据不一致。此时,哪种方案对业务的影响最小?

方案一、先delete cache,后更新数据库

1、在正常情况下,A、B两个线程先后对同一个数据进行读写操作:

A写,先delete cache,后更新数据库
B读,读的数据库中数据,没问题

2、在并发量较大的情况下,采用同步更新缓存的策略:
A写,先delete cache,由于网络或其他原因,未更新成功
B读,读到旧的数据(因为A没有更新数据库成功,cache被删除了),并且B将旧数据放入cache,注意此时没问题,因为数据库中的数据还未更新成功,所以cache和数据库都是旧值,数据没有不一致

在B 线程将旧数据放入缓存后,A线程终于将数据更新完成,此时是有问题的, 缓存中是旧值,数据库新的,数据不一致,若缓存中没有对该值设置过期时间,很长一段时间,数据将一直不一致,直到之后再次对该值进行修改时才会在缓存中淘汰该值。

此时可能会导致cache与数据库的数据一直或很长时间不一致

3、在并发量较大的情况下,采用异步更新缓存的策略:

A写,先delete cache,由于网络或其他原因,未更新成功
B读,读到旧的数据,并不将这个数据放入缓存,所以不会导致数据不一致

A线程更新数据库后,通过订阅binlog来异步更新缓存
此时数据库与缓存的内容将一直都是一致的

进一步分析
同步策略优化方法:
1、用串行的思路
即保证对同一个数据的读写严格按照先后顺序串行化进行,避免并发较大的情况下,多个线程同时对同一数据进行操作时带来的数据不一致性。
2、延时双删+设置缓存的超时时间
不一致原因是,delete cache后,旧数据再次写入cache,之后没有淘汰机制,所以解决思路是,在旧数据再次读入缓存后,再次淘汰缓存,即淘汰缓存两次(延迟双删)

A写,先delete cache,由于网络或其他原因,未更新成功
B读,从数据库中读入旧数据,共耗时N秒
在B线程将旧数据读入缓存后,A线程将数据更新完成,此时数据不一致
A线程将数据库更新完成后,休眠M秒(M比N稍大即可),然后再次淘汰缓存,此时缓存中即使有旧数据也会被淘汰,此时可以保证数据的一致性
其它线程进行读操作时,缓存中无数据,从数据库中读取的是更新后的新数据

利用延迟双删,可以很好的解决数据不一致的问题,其中A线程休眠的M秒,需要根据业务上读取的时间来衡量,只要比正常读取消耗的实际稍大就可以。但是个人感觉实际业务中需要根据场景来设置休眠的时间,这个不好确定。

引入延时双删后,存在两个新问题:
 1、A线程需要在更新数据库后,还要休眠M秒再次淘汰缓存,等所有操作都执行完,这一个更新操作才真正完成,降低了更新操作的吞吐量
解决办法:用“异步淘汰”的策略,将休眠M秒以及二次淘汰放在另一个线程中,A线程在更新完数据库后,可以直接返回成功而不用等待。

2、如果第二次缓存淘汰失败,则不一致依旧会存在
解决办法:用“重试机制”,即当二次淘汰失败后,报错并继续重试,直到执行成功个人

“异步淘汰”策略:

image.png

A线程执行完步骤2不再休眠Ms,而是往消息总线esb发送一个消息,发送完成之后马上就能返回

结论

在单节点下,用“先删缓存,再更新”的策略,如果采用同步更新缓存的策略,可能会导致数据长时间的不一致,可以通过一些方法来尽量避免不一致;如果采用异步更新缓存的策略,就不会导致数据不一致

方案二、先更新数据库,再淘汰缓存

正常情况:
A写,更新数据库,淘汰缓存
B 读,从数据库中读取新的数据
不会有问题

较大并发情况下,情形1:
A写,更新数据库,缓存未淘汰
B读,从缓存中读旧的数据,此时数据不一致
A完成淘汰cache
其他线程进行读,从数据库中读入新的数据,此时数据一致
这种情况问题不大,数据不一致时间很短,最终数据一致

较大并发情况下,情形2:
A写,更新数据库,更新较慢,缓存未淘汰
B读,从缓存中读取旧数据,此时数据不一致
但这种情况没什么问题,毕竟更新操作都还未完成,数据库与缓存中都是旧数据,没有数据不一致

在并发较大的情况下,情形3:
A线程进行读操作,缓存中没有相应的数据,将从数据库中读数据到缓存,
此时分为两种情况,还未读取数据库的数据,已读取数据库的数据,不过由于网络等问题数据还未传输到缓存
B线程执行写操作,更新数据库,淘汰缓存
B线程写操作完成后,A线程才将数据库的数据读入缓存,对于第一种情况,A线程读取的是B线程修改后的新数据,没有问题,对于第二种情况,A线程读取的是旧数据,此时数据会不一致
不过这种情况发生的概率极低,因为一般读操作要比写操作要更快

万一担心存在这种可能,可以用“延迟双删”策略,在A线程读操作完成后再淘汰一次缓存

【结论】

在该方案下,无论是采用同步更新缓存(从数据库读取的数据直接放入缓存中),还是异步更新缓存(数据库中的数据更新完成后,再将数据同步到缓存中),都不会导致数据的不一致
该方案主要只需要担心一个问题:如果第二步淘汰缓存失败,则数据会不一致
解决办法之前也提到过,用“重试机制”就可以,如果淘汰缓存失败就报错,然后重试直到成功

【单节点下两种方案对比】
先淘汰cache,再更新数据库:
采用同步更新缓存的策略,可能会导致数据长时间不一致,如果用延迟双删来优化,还需要考虑究竟需要延时多长时间的问题——读的效率较高,但数据的一致性需要靠其它手段来保证
  采用异步更新缓存的策略,不会导致数据不一致,但在数据库更新完成之前,都需要到数据库层面去读取数据,读的效率不太好——保证了数据的一致性,适用于对一致性要求高的业务

先更新数据库,再淘汰cache:
无论是同步/异步更新缓存,都不会导致数据的最终不一致,在更新数据库期间,cache中的旧数据会被读取,可能会有一段时间的数据不一致,但读的效率很好——保证了数据读取的效率,如果业务对一致性要求不是很高,这种方案最合适

【其它】
重试机制可以采利用“消息队列MQ”来实现
通过订阅binlog来异步更新缓存,可以通过canal中间件来实现

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容