转自:
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、如果第二次缓存淘汰失败,则不一致依旧会存在
解决办法:用“重试机制”,即当二次淘汰失败后,报错并继续重试,直到执行成功个人
“异步淘汰”策略:
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中的旧数据会被读取,可能会有一段时间的数据不一致,但读的效率很好——保证了数据读取的效率,如果业务对一致性要求不是很高,这种方案最合适