04 数据同步问题
数据一致性问题
- 更新数据库成功,但更新缓存失败: 当程序先更新了 MySQL,但由于网络问题或 Redis 宕机,导致删除或更新 Redis 缓存失败,此时数据库是最新数据,而缓存中是旧数据(脏数据)。
- 并发写入冲突: 发生在更新数据库+更新缓存场景。两个请求同时更新同一条数据,A 先更新数据库,但更新缓存较慢;然后请求 B 更新数据库,并迅速更新缓存。最终,数据库是请求 B 的最新值,而缓存却是请求 A 的旧值。
- 并发读写冲突:发生在先删除缓存+更新数据库场景。删除缓存后,如果更新 MySQL 还没完成,有其他线程先查询,会到数据库中读取旧数据写入 Redis(缓存回种)。
解决方案
- 普通场景:采用“先更新数据库,再删除缓存”的策略,简单有效。
- 对一致性有更高要求:使用消息队列或 Binlog 异步更新缓存。
- 强一致性:分布式事务
1、最终一致性
- 为什么是‘删除’而不是‘更新’:高并发时,两个线程同时更新数据,A 先更新数据库,但更新缓存较慢;然后请求 B 更新数据库,并迅速更新缓存。最终,数据库是请求 B 的最新值,而缓存却是请求 A 的旧值。如果加分布式锁会对性能有影响,所以采用直接删除缓存的方案。
- 读:先查缓存,缓存没有则去数据库查并写入缓存。
- 写:先更新数据库,再删除缓存。
2、强一致性
- 方案:分布式事务(2PC/TCC),比如用 RocketMQ 事务消息,在 MySQL 事务提交后再更新 Redis。
- 优点:数据绝对一致。
- 缺点:方案复杂、性能差。
分界线
mysql读写分离(主从同步)
-
在单台mysql实例的情况下,所有的读写操作都集中在这一个实例上。
-
当读压力太大,单台mysql实例扛不住时,此时DBA一般会将数据库配置成集群,一个master(主库),多个slave(从库),master将数据通过binlog的方式同步给slave,可以将slave节点的数据理解为master节点数据的全量备份。
如果是写操作(insert、update、delete等),就走主库,主库会将数据同步给从库;读操作(select、show、explain等),就走从库,从多个slave中选择一个,查询。
-
读写分离优点
-
避免单点故障。
-
负载均衡,读能力水平扩展。通过配置多个slave节点,可以有效的避免过大的访问量对单个库造成的压力。
-
-
读写分离挑战
-
对sql类型进行判断。如果是select等读请求,就走从库,如果是insert、update、delete等写请求,就走主库。
-
主从数据同步延迟问题。因为数据是从master节点通过网络同步给多个slave节点,因此必然存在延迟。因此有可能出现我们在master节点中已经插入了数据,但是从slave节点却读取不到的问题。对于一些强一致性的业务场景,要求插入后必须能读取到,因此对于这种情况,我们需要提供一种方式,让读请求也可以走主库,而主库上的数据必然是最新的。
-
事务问题。如果一个事务中同时包含了读请求(如select)和写请求(如insert),如果读请求走从库,写请求走主库,由于跨了多个库,那么jdbc本地事务已经无法控制,属于分布式事务的范畴。而分布式事务非常复杂且效率较低。因此对于读写分离,目前主流的做法是,事务中的所有sql统一都走主库,由于只涉及到一个库,jdbc本地事务就可以搞定。
-
高可用问题。主要包括:
-
新增slave节点:如果新增slave节点,应用应该感知到,可以将读请求转发到新的slave节点上。
-
slave宕机或下线:如果其中某个slave节点挂了/或者下线了,应该对其进行隔离,那么之后的读请求,应用将其转发到正常工作的slave节点上。
-
master宕机:需要进行主从切换,将其中某个slave提升为master,应用之后将写操作转到新的master节点上。
-
-

浙公网安备 33010602011771号