缓存与数据库的操作时序,不管是《Cache Aside Pattern》中的方案,还是《究竟先操作缓存,还是数据库?》中的方案,都会遇到缓存与数据库不一致的问题。
缓存一致性问题
写数据频繁而读数据少的场景。 删除而不是更新。
删除缓存,而不是更新缓存,就是一个lazy计算的思想。
先删除缓存,再更新数据库 && 先更新数据库,再删除缓存的比较
考虑到并发问题,不能只是单线程的操作。
1、更新的时候,为什么先删除缓存,然后再更新数据库?
写数据频繁而读数据少的场景并不合适这种解决方案,因为也许还没有查询就被删除或修改了,这样会浪费时间和资源。
2、其实删除缓存,而不是更新缓存,就是一个lazy计算的思想。(如果写入缓存中的值需要进行计算)
线程1先读,线程2写入db并且让缓存失效,线程1在把数据更新到缓存;这样就造成了脏数据。
数据库与缓存双写不一致,很常见的问题。
1、先删除缓存,再修改数据库。如果删除失败,不修改数据库。
删除缓存和修改数据库(需要锁表或者锁行,耗时相对比较长)不是一个同步操作。
为什么上亿流量高并发场景下,缓存会出现这个问题?
如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现上述的数据库+缓存不一致的情况。
2、数据库与缓存更新与读取操作进行异步串行化。
同一个商品在同一个内存队列中,保证删除缓存,和修改数据库的操作不会有其他线程干扰。
缓存一致性的两种方案
读的操作和写的操作。
方案1、读的时候,先读缓存,缓存没有的话,读数据库,取出数据后放入缓存,同时返回响应。
更新的时候,先删除缓存,在更新数据库。
方案2、读的时候,先读缓存,缓存没有的话,读数据库,取出数据后放入缓存,同时返回响应。
更新的时候,先更新数据库,再删除缓存。 更新的时候先更新数据库还是先更新缓存?
第一种方案引入了缓存-数据库双写不一致的问题,即读数据(写缓存)与修改数据(写数据库)并发的情况下,若修改数据数据库事务还没提交,但是已经把缓存从redis中删除,此时来了个读请求,会把旧的数据刷到缓存里面,这样就导致了缓存中的数据直到下一次修改数据库之前肯定是与数据库不一致的。(第一种方案 会一直导致数据不一致的问题)
第二种方案引入了另外一个问题,在提交事务之后,若更新缓存失败,也会导致缓存数据库不一致。
facebook公司用的是第二种方案,因为在高并发的情况下,第一种方案带来的影响肯定比第二种方案要大。因为:
第一:导致更新缓存失败的情况概率是很小的,就算发生了,那么问题就大了,比起解决缓存和数据库不一致,更应该加强Redis架构的可用性。
第二,高并发情况下第一种情况发生的概率是很高的。
第二种方案的优化
1)加强redis架构的可用性;引入redis主从架构解决redis可用性;
2)可以为缓存设置过期时间,减小第二种方案极端情况下数据库缓存不同步造成的影响。
这是不是说第一种方案完全不可以用勒,也不是,在保证双写串行化的情况下,我们也能够使用第一种方案,但这种方式会牺牲一定的性能,如通过内存队列的形式。
如果引入了读写分离
但是如果引入了读写分离怎么办勒,由于主从同步延迟,如果采取上面的两种方案,在极端情况下,有可能导致读请求写入缓存中的可能是旧数据。这里根据网上的资料纸上谈兵分析一下,如果严格要求这种情况下也要保住缓存数据库一致性的话,只有通过引入阿里的canel组件,实现针对从库binlog日志的消费逻辑,等到从库更新之后再去删除缓存了。