本文主要是介绍Mysql的主从同步/复制的原理分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《Mysql的主从同步/复制的原理分析》:本文主要介绍Mysql的主从同步/复制的原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教...
为什么要主从同步?
- 数据容灾、备份。当我们的数据库只使用一台服务时,如果我们的数据库遭到破坏,例如黑客的攻击、人为操作的失误等等情况。这时候我们就可以在一定程度上保障我们数据库的恢复。
- 缓解 MySQL 主服务的压力。主服务器写数据,从服务器读数据(读写分离php)。
Mysql主从同步架构有哪些?
一主一从/多从架构
- 适用于读多写少
双主/多主
- 适用于读写均匀,同时整体的并发量也不算低,至少超出了单库的承载阈值
多主一从
- 适用于写大于读
级联复制架构
- 存在两层从库,这实际上属于一主多从架构的升级版,毕竟如果一个主节点存在多个从节点时,多个从节点都会同时去主节点拉取新数据,如果数据量较大,就会导致主节点的I/O负载过高,因此这种级联复制架构要解决的问题,也就是多个从库会对主库造成太大压力的问题。
- 下面会根据原理介绍为什么级联复制架构更好
主从架构,都会存在致命硬伤,同时也会存在些许问题需要解决:
- 硬伤:木桶效应,一个主从集群中所有节点的容量,受限于存储容量最低的哪台服务器。
- 数据一致性问题:由于同步复制数据的过程是基于网络传输完成的,所以存储延迟性。
- 脑裂问题:从节点会通过心跳机制,发送网络包来判断主机是否存活,网络故障情况下会产生多主。
Mysql主从复制的原理/整体流程
- master 服务器会将 SQL 记录通过多 dump 线程写入到 binary log android中。
- 主库会为每个从库创建一个专属的 dump 线程。
- slave 服务器开启一个 io thread 线程向服务器发送请求,向 master 服务器请求 binary log。master 服务器在接收到请求之后,根据偏移量将新的 binary log 发送给 slave 服务器。
- slave 服务器收到新的 binary log 之后,写入到自身的 relay log 中,这就是所谓的中继日志。
- slave 服务器,单独开启一个 sql thread 读取 relay log 之后,写入到自身数据中。
级联复制架构为什么好?
级联复制架构好的原因大家可以从这个图中可以看出,主库会为每个从库建立一个Dump线程,而Dump线程又担负着重要的作用:将监听到的数据通过网络传输发送给从库。可想而知如果从库过多对给主库带来巨大的IO压力,那如果只给一个从库发送数据,并且这个从库又担任其他从库的复制职责,这样就会减轻我们主库的压力,从而提高我们主库的性能。
Mysql主从复制注意点
从库 I/O 线程主要职责是“接收 + 写 relay log”
- 连接到主库并请求binlog内容
- 接收主库dump线程发送的binlog事件
- 将接收到的binlog事件写入从库的relay log(中继日志)
- 更新从库的master info信息(记录已读取的主库binlog位置)
- 断点续传:如果连接中断,IO线程会按照配置的重试策略定期尝试重新连接
主库 dump 线程负责从 binlog 中读取变更数据,并“源源不断推送”给从库。
- 等待从库连接并发送binlog位置请求
- 根据从库请求的binlog文件名和位置(或GTID)定位读取点
- 以事件(event)为单位读取binlog内容
- 将事件发送给从库的I/O线程
- 在没有新事件时进入等待状态(不是持续推送)
- 即使主库没有数据变化,主库 dump 线程 也会根据 master_heartbeat_period 发送一个 Heartbeat log event 给 从库 IO 线程,作为 binlog 的一部分传输,用来维持连接活跃、防止超时断线。
- 主库是怎么判断从库有没有数据?
- 主库不判断!是从库告诉主库它需要从哪个位置开始同步!
MySQL 支持三种 binlog 格式(也就是传输给从库的数据格式)
STATEMENT (不推荐,有风险)
- 最早期的方式,记录执行的原始 SQL 语句
- 优点:可读(看得懂)、日志体积小、性能开销小
- 缺点:有副作用可能不一致(如 UUID、now())
ROW(依旧是逻辑日志)
- 不记录 SQL,而是记录每一行数据的变更
对比redolog
- redolog:表空间X,页号Y,偏移量Z处的数据从0x1234改为0x5678
- redolog记录的是具体的物理存储位置而ROW仅存储表信息以及数据因此依旧是逻辑日志(这里大家不要混淆以为记录数据就是物理日志)
- 记录信息:表的 database name + table name、表的 列结构、对应数据行的变化
- 优点:准确,几乎无副作用问题
- 缺点:日志体积大、不可读(二进制)、性能开销大(行变更多则更慢)
MIXED
- 自动在 STATEMENT 和 ROW 之间切换,MySQL 自行判断
主从复制数据的模式(重点)
相信大家看有的博客说Mysql主从模式三种或有四种模式的,错大错特错,其实就两种模式,其他两种是基于第二个模式配置出来的,下面给大家具体介绍一下:
异步复制(默认)
- 客户端发送请求先写入主库并返回客户端,然后再异步同步给从库
- 优点:返回给客户端快,不会因为同步从库带来阻塞
- 缺点:主从数据不一致的风险
半同步模式(推荐)
- 在异步同步的基础上等待从库返回结果再返回给客户端编程
- 优点:极大的保证了主库和从库的数据一致性
- 缺点:对客户端的阻塞带来影响
内部细节
- 如果从库相应时间过长默认10秒,会切换为异步同步模式
同时为了避免网络延迟造成主库长时间收不到从库的ACK,因此在配置半同步式复制时,会有一个rpl_semi_sync_master_timeout参数来控制超时时间,其默认值是10000ms/10s,如若主库在10s内依旧未收到从库的ACK,则会将复制模式切换成异步模式,切成异步模式后,会在后续网络正常后再次切回半同步模式。
- 对于多个从库节点可以配置有多少个从库返回就给客户端响应
5.7版本后对主从一致性保障策略
AFTER_SYNC(默认,增强半同步复制)【其他博客的第三种模式】
当主库未收到从库的ACK之前,也不会在主库上提交事务,也就是保证了主从节点的数据强一致性,解决了after-commit中存在的问题。
AFTER_COMMIT(传统半同步复制,可能出现数据不一致)
- 主库在未收到从库的ACK之前,虽然不会给客户端返回写入成功,但本质上在MySQL中会提交事务,也就是主库中的其他事务是可以看见对应数据的,当此时出现宕机时,就会导致旧主上能查询出的数据,在新主(原本的从库)上无法查询出来了。
- 两者之间的区别就在于:对主从节点的数据严格性不同,一般情况下,无损复制会比传统半同步复制开销更大一些,因为事务迟迟不提交,会导致对应的锁资源不会主动释放,其他需要获取对应锁资源的事务只能阻塞等待,这会造成主库的整体性能出现一定影响。
同步复制(本身不支持,需通过半同步复制设置为等待所有从库)【其他博客中第四种模式】
- 对于同步复制而言,Master主机将事件发送给Slave主机后会触发一个等待,直到所有Slave节点(如果有多个Slave)返回数据复制成功的信息给Master。这种复制方式最安
- 全,但是同时,效率也是最差的。
Mysql对于从库的一些优化/策略
- 从库的执行策略
延迟复制
延迟复制通常用于一些特殊场景,它可以支持从库数据的延迟同步,也就是当从库上的I/O线程,将主库的Bin-log日志请求回来后,从节点的SQL线程并不会立刻解析日志执行,而是等待一段时间后再解析日志执行,这个等待的时间可以由开发者来配置,一般建议设为3~6小时之间。
那延迟复制的好处在于什么呢?
可以防止误删操作,如若在主库上不小心误删了大量数据、表、库或其他数据库对象,因为从库并不是立即执行同步过去的记录,因此可以及时通过从节点上的数据回滚数据。除此之外,也能对一些线上Bug进行实时观测,比如一个无法复现的故障问题发生时,如果发现时还在配置的延迟复制时间内,则可以去到从库上观察。
- 从库的优化手段并行复制(mysql8.0更完善)
GTID复制(为什么要先说GTID,因为并行复制是基于组复javascript制,而组复制是基于GTID)
- 在传统的主从架构中,当需要发生主从切换时,需要开发/运维人员手动找到Bin-log的POS同步点,然后执行change master to [new-master-pos]命令,将其他从节点指向新主库,但每个从节点可能同步数据的进度都不一致,因此每个从节点都需要去找到它上次的POS点,然后指向新主库,这个工作是不是听起来就比较繁杂?答案是Yes,不过到了MySQL5.6版本后,开启了GTID复制后,则无需手动寻找POS点!
- GTID(Global Transaction ID)也就是全局事务标识符的意思,它由节点UUID+事务ID两部分组成,MySQL在第一次启动时都会利用UUID随机生成一个server_id,还记得在之前的《MVCC机制》中聊过的事务ID嘛?MySQL会对每一个写事务都分配一个顺序递增的值作为事务ID,而GTID则是由这两玩意儿组成的,格式为server_uuid:trx_id。
- 当主库的事务有了这个全局事务标识后,再发生主从切换时就无需手动寻点了,仅需要执行change master to master_auto_position = 1这条命令即可,它会自动去新主库上寻找数据的同步点,也就是MySQL自身就具备断点复制的功能。
为什么需要用GTID代替POS同步点呢?
因为同一个主从集群中,所有节点加入集群的时间可能会不同
假设这个集群中每个节点加入的时间都不一致
- master:日志文件中的同步点POS=1200。
- slave1:日志文件中的同步点POS=1100。
- slave2:日志文件中的同步点POS=800。
- slave3:日志文件中的同步点POS=200。
此时假设master节点宕机或故障了,slave1成为了新主,那么slave2、slave3也应该成为新主slave1的从节点,但此刻问题就来了:原本slave2、slave3的POS同步点是基于master中的日志而言的,但现在主节点变成了slave1,这时slave2、slave3如何去寻找自己在slave1中的POS点呢?显然MySQL无法自己完成该工作,因此需要人工指定同步点才行。
而GTID出现的原因,就是为了解决上述这个问题,但具体怎么解决的呢?
GTID的工作过程
master在更新数据时,会为每一个写事务分配一个全局的GTID,并记录到Bin-log中。
slave节点的I/O线程拉取数据时,会将读到的记录写到relay-log中,并设置gtid_next值。
slave节点的SQL线程执行前,会读取gtid_next值得知接下来该解析哪条日志并执行。
slave节点的SQL线程在执行时,会先比对自身的Bin-log日志中是否有对应的GTID:
- 有:意味着该GTID对应的事务已经执行过了,slave会自动忽略掉这条记录。
- 没有:SQL解析该GTID对应的relay-log记录并执行,再将GTID记录到Bin-log。
GTID自动寻找同步点的原理
- 开启GTID后,从库会基于它来复制主库的数据,此时发生了主从切换,假设这时主从集群中有多个从节点,MySQL首先会选择距离master的GTID最近的从节点作为新主,然后将其他从节点转变为新主的从库,其他从库会根据自身gtid_next值,去新主的日志文件中做对比,然后找到各自的同步点,继续从新主中复制数据。
- 因为MySQL挑选的是和旧主GTID值,最接近的从节点作为新主,也就意味着作为新主的从节点,绝对会比其他从节点的数据要完善,因此新主中的GTID值也是最大的!同时,每个从节点中都存在一个gtid_next值,记录着自身下一次要同步数据的GTID值,此时剩下的从节点就可以根据该值,直接去新主中寻找到自己的同步点位置,从而避免了之前那种手动介入的尴尬场景出现。
- 不过由于GTID复制是基于事务来实现的,这也就代表不支持事务的存储引擎无法使用这种机制,在之前的章节中也聊过,MySQL众多存储引擎中,基本上只有InnoDB支持事务,所以GTID机制基本上只对InnoDB引擎生效。
- 组复制
GTID复制则是组复制的实现基础,而组复制则是并行复制的基础,那么什么叫做组复制呢?组复制是指将一组并行执行的事务,全部放入到一个GTID中记录,后续从节点同步数据时,会一次性读取这一组事务解析并执行,与传统的GTID区别如下:
- 传统的GTID值由节点ID+事务ID组成:12EEA4RD6-45AC-667B-33DD-CCC55EF718D:88。
- 组复制的GTID通过逗号分隔:12EEA4RD6-45AC-667B-33DD-CCC55EF718D:89, 12EEA4RD6-45AC-667B-33DD-CCC55EF718D:89-94, ......。
MySQL如何实现事务分组的呢?
- MySQChina编程L提交事务时内部会调用ordered_commit函数来处理相关工作,其函数执行的逻辑流程图如下:
当一个事务提交时都会调用ordered_commit函数,首先会将事务加入等待事务组,接着会经过三个核心步骤:FLUSH、SYNC、COMMIT,对应的也会有三个队列,它们三者的工作原理都大致相同:
- ①如果某个事务进入FLUSH队列时,该队列还是空的,则这个事务会担任“队长”的角色。
- ②当后续其他事务进入队列时,发现队列不为空,则会将提交工作委托给队长来完成。
- ③如上图中的「事务1」则是队长,后续的都是队员,但队长不会无限制等待队员到来:
从队长加入的时间点开始,当超出binlog_group_commit_sync_delay规定的时间后,就会进行一次组提交。
- 同一时刻只允许一组事务做这些工作,也就是当有另外一组事务提交时,需要等待上一组事务提交完成。
- 在做组提交工作时,会将当前事务组的内容记录到Bin-log日志中,同时会将这组事务记录成一个GTID,不同事务之间通过,逗号分隔(实际过程更为复杂,这里只做简单讲解)。
并行复制
在MySQL5.6之前的版本中,从库同步数据时,所有数据同步工作都是基于单线程完成的,也就是不管主库上的数据是不是多线程并发写入的,从库上只会有一条SQL线程来执行解析执行工作。
到了MySQL5.6之后,引入了并行复制的思想,但5.6中的并行复制极其鸡肋,基本无人问津,因为是基于库级别的并行复制,也就是一个从节点对应多个主节点时,有几个主节点就开几条SQL线程去解析并写入数据,即多主一从架构中才会用到。
因为官方最初在实现并行复制时,一直纠结锁冲突的问题,所以为了防止并行执行时出现数据冲突,就造出了上面那种库级别的并行复制,为啥要纠结锁/数据冲突呢?
比如从库I/O线程在主库中请求了100条记录回去,从库中开100条SQL线程解析并执行这些记录,如果其中有两条记录操作的是同一条数据,就会出现锁冲突问题。
- 但上述原因不是最主要的,最主要的是并发执行的顺序问题,如果主库上对于一条数据是先改后删,从库在并发执行时,因为多线程执行的无序性,把执行顺序改为了先删后改,这显然就会导致数据冲突,因此变更操作很难实现并行复制。
- 到了MySQL5.7中,才基于组复制技术实现了真正意义上的并行复制,因为能够在同一时间内提交的事务,绝对是不存在锁冲突的,所以可以开启多条线程同时执行一个组中不同的事务,但这个思想是从mariadb中照抄过来的~
一句话来总结就是:主库上是咋样并发写入数据的,从库也会开启对应的线程数去并发写入。
在5.7中官方为这种机制命名为enhanced multi-threaded slave,简称MTS机制,同时为了兼容5.6版本中的并行复制,又多加入了一个slave-parallel-type参数:
- DATABASE:默认的并行复制模式,表示基于库级别的来完成并行复制。
- LOGICAL_CLOCK:表示基于组提交的方式来完成并行复制。
并行复制出现的意义是什么?
能够在很大程度上提升从库复制数据的速度,也就是能够让从库的数据实时性提升,尤其是无损复制模式中,主节点需要等待从节点的ACK才会真正提交事务,从库使用并行复制后,能够在一定程度上解决从库的复制延迟问题。
不过虽然5.7中的并行复制,在一定程度上解决了原有的从库延迟问题,但如果一个新的从节点加入集群时,因为要从头开始同步数据,这种并行复制的模式依旧存在效率问题,而到了MySQL8.0中,对于并行复制技术提出了真正的解决之道,也就是基于writeset的MTS技术。
主从数据一致性的解决方案
读写分离数据一致性场景:一个用户将个人信息修改后,然后再次查看时,发现个人信息依旧是修改之前的原数据,这时用户就有可能再次修改,经过反反复复多次修改后,用户发现依旧未生效
想要解决上述这种读写分离导致的数据不一致性,主要有四种解决方案:
业务逻辑做改变、复制方式做更改、数据库架构做调整、引入第三方中间件。
改变业务逻辑
- 对业务做一定更改,比如当用户立即修改数据后,因为在从库读不到数据,所以先显示一个审核状态,这样能够给出用户的反馈,从而避免用户再次重复操作
- 这种方式属于和数据不一致妥协的方案,接受一定的数据延迟,不过这种方式并不适用于一些对数据实时性要求较高的场景
更改复制方式
- 在之前曾聊过MySQL四种数据同步复制的方式,即全同步、异步、半同步与无损复制,MySQL默认会是异步复制模式,即主节点写入数据后会立即返回成功的状态给客户端,这样能够确保性能达到最佳,但如果对实时性要求较高,可以将其改为全同步或半同步模式。
调整数据库架构
- 如果无法接受主从架构带来的短期数据不一致,那可以升级服务器硬件,并将架构恢复成单库架构,所有读写操作都走单库完成,这就自然不会出现数据不一致问题。
- 但如果升级硬件配置后,无法承载客户端的访问压力时,可将整体架构升级到分库分表架构,制定好合适的分片策略和路由键,每次读写数据都根据业务不同,操作不同的库。
引入第三方中间件
- 前面聊到的三种方案,多多少少都存在一些局限性,因为要么接受数据不一致、要么损失性能、要么使用更高规模的架构处理,但这些方案似乎都存在令人不能接受的后患,那有没有一种万全之策来解决这个问题呢?答案是有的,就是引入Canal中间件来监控主节点的Bin-log日志。
- 在之前讲主从同步数据原理时,曾讲到过,主节点上存在一个log dump线程会监听Bin-log日志,当日志出现变更时会通知从节点来拉取数据,而Canal的思想也是一样的,会监控主节点的Bin-log日志,当发生变更时,就直接去拉取数据,然后直接推送给从节点写入。
- 但这种方式也无法做到真正的数据实时性,毕竟Canal监听变更、拉取数据、推送数据都需要时间,这部分的时间开销必然存在,只是它会比从库去主库上拉取,速度会更快一些罢了。
- 一般企业内部都会引入Canal来解决数据不一致问题,因为它不仅仅只能解决主从延迟问题,还能解决MySQL-ES、MySQL-Redis.....等多种数据不一致的场景
- 也可以制定某些敏感数据走主库查询,这样能够确保数据的实时性,但容易模糊读写分离的界限,不过因为不需要引入额外的技术,所以在某些情况下也是个不错的方案。
总结
这篇关于Mysql的主从同步/复制的原理分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!