转自:https://mp.weixin.qq.com/s/9fbXiQe1sc2xpMKfBXNF3w
只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
Cache Aside Pattern
最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
更新的时候,先更新数据库,然后再删除缓存。
为什么是删除缓存,而不是更新缓存?
主要有以下两点:
- 很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。
- 另外更新缓存的代价有时候是很高的。如有些冷数据,1分钟内修改了N次,但是只读取1次,这样的场景下如果更新缓存N次的话代价就太大了。而删除缓存,只需要在读取时更新1次缓存即可。这其实就是LAZY延迟加载的思想。
比较复杂的数据不一致问题分析
在并发量比较大的情况下,极有可能会出现如下场景:
- 先更新数据库再删除缓存,在更新数据库与删除缓存之间,有读请求过来,这时缓存还未删除,读取到的是缓存脏数据。
- 先删除缓存再更新数据库,在删除缓存与更新数据库之间,有读请求过来,这时缓存中无数据,去读取数据库并加载到缓存,数据库这时还未更新完成,读取到的是数据库快照(也就是数据库脏数据),最终导致缓存中长期为脏数据(除非更新完数据库后再次删除缓存)。
解决方案:操作串行化
理论上,不管是先更新数据库再删除缓存,还是先删除缓存再更新数据库,只要保证读写操作是串行的,就没有数据一致性的问题。但是在实际操作过程中,如果选择先更新数据库再删除缓存的话,读请求无法得知哪些数据的读取需要串行化(除非去遍历更新数据库的请求队列,代价太高)。所以选择先删除缓存再更新数据库。
具体过程:更新数据库之前,先删除缓存,然后根据数据唯一标识,将更新操作路由到一个JVM 内部队列中。读请求过来的时候,发现缓存中无数据(需要串行化),那么将重新进行读取数据+更新缓存的操作,根据数据唯一标识路由到和更新数据库操作的同一个JVM内部队列中。
可选优化点:同一个数据,在队列中可能会存在多个读请求排队,可以做一下过滤,如果发现队列中已经有读请求在排队,那么就不用再放第二个读请求进去了,第二个读请求直接等待前面的操作完成即可(不过这个操作需要遍历队列,另外,第二个读请求还需要做手动同步等待,操作复杂性以及性能不一定会有提高,可选吧)。
一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求 “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去。
串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上请求。
网友评论