赞
踩
目录
在这样一个架构中,会出现一个问题,就是一份数据,同时保存在数据库和Redis 里面,当数据发生变化的时候,需要同时更新 Redis 和 Mysql,由于更新是有先后顺序的,并且它不像 Mysql 中的多表事务操作,可以满足 ACID 特性。所以就会出现数据一致性问题。
Redis与MySQL双写一致性是指在使用缓存和数据库同时存储数据的场景下( 主要是存在高并发的情况),如何保证两者的数据一致性(内容相同或者尽可能接近)。
线程A:更新缓存(第1s)——> 更新数据库(第10s)
线程B:更新缓存(第3s)——> 更新数据库(第5s)
缓存中是线程B的新值,而数据库中是线程A的旧值。
线程A:更新数据库(第1s)——> 更新缓存(第10s)
线程B:更新数据库 (第3s)——> 更新缓存(第5s)
并发场景下,这样的情况是很容易出现的,每个线程的操作先后顺序不同,这样就导致请求B的缓存值被请求A给覆盖了, 数据库中是线程B的新值,缓存中是线程A的旧值,并且会一直这么脏下去直到缓存失效(如果你设置了过期时间的话)。
通过这种方式,我们很惊喜地发现,前面困扰我们的并发场景的问题确实被解决了!两个线程都只修改数据库,不管谁先,数据库以之后修改的线程为准。
但这个时候,我们来思考另一个场景:两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的。很显然,这种状况也不是我们想要的。
这种方式,在方案3的基础上,又将二者的顺序进行了调换。我们再把前面的场景在这种方案下进行验证:一个是查询操作,一个是更新操作的并发,我们先更新了数据库中的数据,此时,缓存依然有效,所以,并发的查询操作拿的是没有更新的数据,但是,更新操作马上让缓存的失效了,后续的查询操作再把数据从数据库中拉出来。而不会方案3一样,后续的查询操作一直在取老的数据。
而这,也正是缓存使用的标准的design pattern,也就是cache aside。包括Facebook的论文《Scaling Memcache at Facebook》也使用了这个策略。
那么,是否这种方案就是万无一失的完美策略呢?其实也并不然,再来看看这种场景:一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,所以,会造成脏数据。
因此,在使用更新缓存操作时,无论谁先谁后,但凡后者发生异常,就会对业务造成影响。
我们重点在将先更新数据库,在删除缓存。那如果我要先删除缓存,再更新数据库呢?
回顾之前讲的先删除缓存,再更新数据库,它会出现旧值覆盖缓存的问题,那好办,我们直接把这个旧值给删了不就完了吗,延时双删就是这个原理,它的基本思路是:
这样做的目的是为了防止在更新数据库后,有其他线程读取到旧的缓存数据,并将其写回缓存,导致数据不一致。
使用binlog实现一致性的基本思路是利用binlog日志来记录数据库的变更操作,然后通过主从复制或者增量备份的方式来同步或者恢复数据。
举例来说,如果我们有一个主数据库和一个从数据库,我们可以在主数据库上开启binlog日志,并设置从数据库作为它的复制节点。这样,当主数据库上发生任何变更操作时,它会将对应的binlog日志发送给从数据库,从数据库则会根据binlog日志来执行相同的操作,从而保证数据一致性。
另外,如果我们需要恢复某个时间点之前的数据,我们也可以利用binlog日志来实现。首先,我们需要找到对应时间点之前的最近一个全量备份文件,并将其恢复到目标数据库。然后,我们需要找到对应时间点之前的所有增量备份文件(即binlog日志文件),并按照顺序将其应用到目标数据库。这样,我们就可以恢复出目标时间点之前的数据状态了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。