共计 2450 个字符,预计需要花费 7 分钟才能阅读完成。
哈喽哈喽大家猴,我是把代码写成 bug 的大头菜。公众号:大头菜技术(bigheadit)。原创不易,但欢送转载。
本文次要分享一下对于缓存一致性问题和其解决方案。上面是本文的次要目录,大家能够挑着看。
目录
- 什么是缓存一致性
- 为什么要保障缓存一致性
- 如何保障缓存一致性
- 如何做到强一致性
- 总结
01 什么是缓存一致性
就是缓存和数据库的数据不统一导致的问题,缓存一致性分为强一致性和最终一致性。
- 强一致性,这个比拟损耗性能,比较复杂,退出之后,可能会比没加缓存更慢。
- 最终一致性,是容许缓存数据和数据库数据一段时间内不统一,但数据最终会保持一致。这个性能较高。
02 为什么要保障缓存一致性
因为业务中存在一些写操作导致的,是要先写缓存,还是先写数据库。二者程序的不同会导致不同的问题。
单纯的读操作,是不会导致缓存一致性问题的,因为读是幂等的哈。读无数次都是不会变的,因而就不存在读操作引起缓存一致性问题。
因而导致缓存不统一的就是写操作了。写操作是导致缓存不统一的起因。但这不是要保障缓存一致性的理由,
归根结底都是业务须要,如果业务需要容许缓存和数据库的不统一,那就不须要保障缓存一致性了。
03 如何保障缓存一致性(解决方案)
置信很多人都晓得经典计划:cache aside pattern。
首先明确的是,读不会产生缓存一致性问题。是写操作,才会产生缓存一致性问题。
第一点,生效:申请过去时,先拜访缓存,缓存不存在,再去拜访数据库,更新缓存。
第二点,读:申请过去时,缓存中有数据,间接返回数据。
第三点,写:先更新数据库,后删除缓存。
要害在第三点,前提,数据库必定是更新的。剩下的问题就是:是要更新缓存?还是要删除缓存?是先对数据库操作?还是先对缓存操作?
俩俩组合有 4 种可能性:
- 先更新缓存,后更新数据库
- 先更新数据库,后更新缓存
- 先删除缓存,后更新数据库
- 先更新数据库,后删除缓存
1. 先更新缓存,后更新数据库
首先咱们要明确,更新数据库或者更新缓存,都面临着更新失败的危险。但在互联网高并发的环境中和依据墨菲定律,这个事是肯定会产生的。
- 先更新缓存,胜利了
- 后更新数据库,失败了,当然你会说重试,好,那我就重试 N 次,但如果数据库彻底挂了,复原不了了,重试也没用
导致问题:数据失落,数据库外面的数据还是老数据
2. 先更新数据库,后更新缓存
假如有两个申请, A 申请是更新,B 申请是更新,A 先 B 后,但二者距离很短
- 线程 A 更新了数据库
- 线程 B 更新了数据库
- 线程 B 更新了缓存
- 线程 A 更新了缓存
导致问题:缓存中是旧数据,数据库中是新数据,这就不统一了。还有就是更新后的缓存,真的会被再读取吗?如果缓存数据不再被读取,那就白白操作了一次缓存更新操作。并且还占用内存空间。
依据这个例子,能够看出,更新缓存是不可取的,那就间接删除缓存吧。接着看
3. 先删除缓存,后更新数据库
假如有两个申请, A 申请是更新,B 申请是读,可能呈现的问题
- 线程 A 删除缓存
- 线程 B 查问不到缓存,间接去数据库查旧值
- 线程 A 将新值写入数据库
- 线程 B 更新缓存
导致问题:缓存中的是旧值,数据库中的是新值,二者不统一。进一步,如果数据库存在读写拆散,那么缓存和数据库数据不统一的状况进一步加剧。
- 线程 A 删除缓存
- 线程 A 将新值写入主数据库,但未同步数据到从数据库
- 线程 B 查问不到缓存,间接去从数据库查,查到旧值
- 线程 B 更新缓存
- 新数据同步到从数据库
导致问题:缓存是旧值,数据库是新值,二者数据不统一
4. 先更新数据库,后删除缓存
假如有两个申请, A 申请是读,B 申请是更新,可能会呈现的问题
- 缓存刚好生效
- 线程 A 查数据库,失去旧值
- 线程 B 更新数据库
- 线程 B 删除缓存
- 线程 A 更新缓存
导致问题:缓存是旧值,数据库是新值,二者不统一。但这种状况的可能性相对来说比拟小,因为须要缓存刚好生效,并且此时有一个线程去读,且刚好又有一个写的线程。而且写的线程实践上是比读的线程慢的,因为写的线程,须要加锁。而查问不必加锁,不包含简单的查问。
在数据库读写拆散的状况下,这种状况会更加显著:
- 线程 B 更新主库
- 线程 B 删除缓存
- 线程 A 查问缓存,没有命中,查问从库失去旧值
- 数据同步到从库
- 线程 A 更新缓存
导致问题:缓存数据和数据库数据不统一
如果思考更新数据库或者更新缓存失败的话,那么更新数据库失败的话,其实数据库和缓存都是旧数据,因而不存在数据不统一的状况。
如果更新缓存失败,那么有过期工夫来保障最终一致性。如果非要较真的话,能够退出重试机制。
重试机制能够用线程池,也能够用 MQ。MQ 更加牢靠。能够间接订阅 MySQL 的 binlog,来触发缓存的删除。当然,其实 MQ 也会挂。然而 MQ 和缓存都一起挂的几率,应该很小吧。
综上所述四种状况
尽管每种计划都有各自的问题,但呈现几率较小的是 先更新数据库,后删除缓存 计划。为什么先更新数据库?因为数据库的长久化能力比缓存好。上述四种状况,还可能呈现缓存并发,缓存穿透,缓存雪崩的问题。这些问题,这里就不探讨了。感兴趣的话,本人去看我的相干文章。
04 如何做到强一致性
计划一:分布式事务
能够用分布式事务,分布式事务,具体的实现有 2PC、3PC、音讯队列等。如果要采纳这个计划,架构设计中要引入很多容错、回退、兜底的措施。业务代码就减少复杂性了。还有人说用分布式一致性算法 paxos 和 raft,这就更简单了。
计划二:分布式读写锁
首先,咱们回到 先更新数据库,后删除缓存,要明确什么时候会呈现脏数据?
呈现脏数据:更新数据库后,删除缓存之前。这时候二者数据是不统一的。
如果实现更新数据库时,所有读申请都被阻塞。这就解决了数据不统一的问题,这其实是串行化思路。但结果,当然是性能下滑。
总结
其实抉择数据的强一致性和数据的最终一致性。得看具体需要,我如同说了一句废话。然而放弃强一致性,意味着咱们零碎的性能失去肯定水平的晋升。相同,如果咱们谋求强一致性,那就会巨简单,而且可能得失相当,可能性能比不加缓存时还低。缓存这货色,想要用得好,就须要好好推敲,比方过期工夫的设置,长久化,故障复原,空间和工夫的均衡,一致性的抉择,这些都要好好斟酌,没有最好的计划,只有适合的计划。