缓存一致性

缓存数据的一致性

读所有的数据,首先去缓存中获取,缓存中没有就去读数据库,最后在缓存中放一份。如果该数据在数据库中发生改变,那么缓存里面的数据如何和数据库保持一致?解决这个问题,目前有两个用的非常多的场景:

  • 双写模式
  • 失效模式

双写模式

修改数据库的数据后再修改缓存中的数据

双写模式

但又引出了一个问题,那就是当有并发时,线程一将数据库的值改为“一号”然后立刻将这个值保存到缓存,而保存过程中遇到了种种原因,如网络波动,导致了数据延迟抵达缓存服务器,而此时线程二已经将数据库数据改为“二号”然后将数据保存到缓存,线程二网络通畅,很快就抵达缓存服务器先于线程一将数据保存到缓存中,而此时线程一的数据才抵达缓存服务器,将数据“一号”保存到了缓存中(把二号覆盖),这时缓存中保存的就是一个脏数据。

脏数据被保存原理图

这是临时性的脏数据问题,但是在数据稳定、缓存过期后,又能得到最新的正确数据。

读到的最新数据有延迟,这就导致了数据的最终一致性问题。

失效模式

在改完数据库后,将缓存中的数据删除,下一次请求进来从缓存获取数据时发现没有对应数据,他会到数据库中查询并将数据保存在缓存中。

失效模式

失效模式也有数据最终一致性问题,例如有三台服务器同时并发,服务器一负载小,修改数据库和删除缓存一气呵成,服务器二负载大,在修改数据库时磨磨蹭蹭耗时很久,而服务器三是获取数据的,他读取缓存没有数据又去数据库中将数据查出来了,此时查询的是服务器一修改的数据。完成这些操作后服务器二才将数据库修改瞬间删除缓存,而服务器三在更新缓存时遇到网络波动,非常久后才成功将数据保存到缓存中,但他保存的却是服务器一修改的旧数据

脏数据被保存原理图

加锁

以上两种模式遇到的问题(乱序)都可以通过加锁来解决,但是加锁后系统可能会变得笨重,所以,如果我们的数据经常修改,那么我们还需要将其放到缓存中咩?

如果数据经常修改,那么我们的锁会经常在,会导致整个系统非常缓慢,如果我们想要实时的读取,数据一致性要求非常高,那么这种情况还不如不加锁,直接访问数据库。

解决方案

  • 我们发现无论是双写模式还是失效模式,都会导致缓存的的不一致问题。即多个实例同时更新就会出现问题,应该怎么办?
    1. 如果是用户纬度数据(订单数据,用户数据),这种并发几率非常小,那么就不需要考虑这个问题,缓存数据加上过期时间,每隔一段时间触发读的主动更新即可。
    2. 如果是菜单,商品介绍等基础数据,也可以去使用 canal 订阅 binlog 的方式。
    3. 缓存数据 + 过期时间也足够解决大部分业务对于缓存的要求。
    4. 通过加锁保证并发读写,写写的时候按顺序排队,读读则无所谓。所以适合使用读写锁。(业务不关心脏数据,允许临时脏数据可忽略)。

总结

  • 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的,所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可。
  • 我们不应该过度设计。增加系统的复杂性
  • 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点,也无所谓。

欢迎访问我的个人博客
博文在博客中的链接: //www.ctong.top/index.php/archives/91