(5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

时间:2023-03-08 17:10:07
(5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

关键词:mysql主从复制,mysql复制,MGR,mysql并行复制

目录

【1】mysql支持的复制类型

【2】mysql的主从4种同步方式介绍

  (1)异步  (2)同步  (3)5.6 半同步  (4)5.7增强半同步

【3】MGR的介绍

【4】mysql并行复制

  【4.1】并行复制的当前大三模式

  【库间并发】【组提交】【WriteSet

正文

【1】mysql支持的复制类型

  基于binlog的3种模式(详情参考:binlog的3种日志记录模式),oracle在mysql5.5版本收购

  【1.1】statement:基于语句的复制(5.5.6之前默认),主服务器执行的SQL语句,在从服务器执行同样的语句

  【1.2】row:基于行的复制(5.5.7之后默认),把改变的内容复制到从库,而不是把SQL命令在从库重新执行一遍。mysql5.0就开始支持

  【1.3】mixed:混合类型的复制,默认是使用 statement 语句方式复制,一旦发现基于语句无法精确复制时(比如now() 因为主从有延迟导致数据不一致)就会采用基于 row 行的方式复制。

  

【2】mysql的主从4种同步方式介绍

      以下图片均引用自《深入浅出mysql开发、优化与管理维护》

       (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

       

  【2.1】异步复制(和MSSQL的高性能模式镜像一样):(3.23 native)

       主库只管binlog dump数据到从库,不保证主从完全一致,断电、崩溃等情况会丢失数据。   

      (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

  【2.2】全同步复制:

      【2.2.1】核心概念:主从复制,主库要等到从库重做事务并且提交成功,接受到ACK成功确认标识后,主库才能提交事务。

      【2.2.2】与半同步的区别:半同步是只需要持久化到relay log阶段即可返回ACK成功标识给主库,而全同步需要等待从库SQL进程完整的运行完该事务内容才能返回ACK成功标识。

      【2.2.3】原理:

      主库事务写入redo log和 binlog 文件后,binlog dump线程提取binlog数据给IO线程,IO线程把数据加载回从库的relay log文件。

      从库SQL线程开启事务重做应用relay log数据操作,等从库提交完后,返回ACK确认标识,主库才会提交。

  【2.3】传统半同步复制:(自5.5开始,插件)

      【2.3.1】原理:

        master事务commit 指令已经写入binlog(注意,这里已经提交了,再去同步,只是在等一个ACK)和 binlog 文件后,binlog dump线程提取binlog数据给IO线程,IO线程把数据加载回从库的relay log文件。

      只要IO线程-》slave的relay log已经flush disk 磁盘落地,slave就返回ACK确认标识给master。

      【2.3.2】特性:

        这里的commit主库上是已经在 binlog、redo log 中提交了的,其他session都可以看到。

        但需要等待从库返回ACK确认标识才会把事务提交到存储引擎持久化(即 ibdata、ibd等磁盘数据文件)且返回到client一个commit成功的指令。

      【2.3.3】宕机情况:

        master上这个事务其实是已经提交了并且落盘master的binlog了,master的其他Session是可以看到这个提交后的事务的,而slave还没有commit。

        这个时候master挂了,master上的数据和slave不一致。master crash后,master根据redo重做提交了这个事务.

        在切换到从后,slave因为没有commit而回滚了这个事务导致数据丢失。导致主从不一致。

      (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

      (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

  【2.4】增强半同步复制(mysql 5.7.4及以上才可以用,与MSSQL镜像高安全模式相同):

      与【2.3】传统半同步复制大致一样。唯一的区别就是,在事务提交过来后:

      核心区别:主库会等从库在relay log 阶段持久化该事务到该文件后,接受ACK成功确认标识后,再进行提交(commit指令写入redo log)

        【2.4.1】传统的半同步复制:

          master 会把binlog和redo log全部写了。

        【2.4.2】增强半同步复制:

          master 只会写binlog,然后等slave的IO线程把事务持久化(flush disk)到 relay log 上后,返回ACK确认标识。

          master收到确认标识后才会commit 写入到redo log,并返回给客户端commit成功的指令。

        【2.4.3】宕机情况:

          主库上这个事务因为没有写入redo log,所以这个事务被认为是未提交。master的其他session是不可以看到这个事务的。

          这个时候主库挂了,master上的数据和slave一致,master crash后,slave不丢数据。        

  【2.5】GTID 复制(mysql 在 5.6.2 及之后开始支持GTID):

      【2.5.1】GTID(Global Transaction Identifiers)概念:

        对于一个已提交事务的编号,事务的唯一编号,并且是一个全局唯一的编号。GTID和事务会记录到binlog中,用来标识事务。

        GTID是用来替代以前,传统复制方法(binlog+position),mysql 5.6.2开始支持GTID。

        mysql支持GTID后,一个事务在集群中就不再孤单,在每一个节点中,如果存在具相同标识符的情况,可以避免同一个事务,在同一个节点出现多次的情况。

          (可以初步理解成row模式的,和statement的区别,前者记得是具体做了什么事,后者记录的是位置)

        GTID的出现最直接的效果就是,每一个事物在集群中具有了唯一性的意义,相对于行复制来讲数据安全性更高,故障切换更简单。

      【2.5.2】简单案例解释概念:

        比如,当我们一主2从,主出故障后切换到从DB1上去了,那么另外2台机器,需要重新手动构建主从;

        具体为:     

-- 使用传统方式构建的主库宕机重新搭建
-- 麻烦点:每台机器的Binlog名字和Postion位置点都不一样,需要重新定位文件在哪里,位置在哪里
change master to
master_host='xxx',
master_user='xxx',
master_password='xxx',
master_port='xxx',
master_log_file='xxx',
master_log_pos='xxx'; -- 使用GTID方式的主库宕机重新搭建
-- 优势点:每台机器上的GTID都是一样的,不需要管文件是哪个,位置在哪里,可以自动根据GTID定位
change master to
master_host='xxx',
master_user='xxx',
master_password='xxx',
master_port='xxx',
master_auto_postion=1;

 

      【2.5.3】GTID的组成

          GTID 是由 server_uuid:Sequence_Number 组成;

            (1)server_uuid:是一个mysql实例的全局唯一表示;存放在 ${datadir}/auto.cnf

            (2)Sequence_Number:是mysql内部的一个事务的标号,一个mysql实例不会重复的序列号(保证服务器内唯一),也表示在该实例上已经提交事务的数量,并且随着事务提交而递增。

            (3)根据GTID可以知道事务最初是在哪个实例上提交的,方便故障排查和切换

  【2.6】复制之间的区别

    (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

【3】MGR 群组复制

  MGR一键部署参考脚本:https://github.com/zhaowengxing/automgr

    (1)整体架构

    (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

    (2)replication plugin 插件

      (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

    (3)GCS

      (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

  【2.3】MGR的模式

    【2.3.1】单主模式(single primary mode)

    (1)单主模式基本架构与形式概念

    (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

    (2)单主模式的运行机制

      (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

    【2.3.2】多主同步模式

    (1)多主同步模式的冲突检测(核心是基于主键),这个检测就是certify

      (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

      这个冲突检测就是最开始运行原理的certify

        (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

      如果真有主键相同的操作执行了怎么办?先执行的提交,后执行的冲突回滚,如图

        (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

    (2)多主同步模式下的限制

限制和规则
•仅InnoDB Engine(Transactional and row level lock)
•表必须有主键
•gtid-mode=ON
•binlog格式为Row-based
•对于同一个对象执行DDL和DML应在同一成员上执行,不支持在不同服务器上执行DDL
•不支持具有多级外键依赖关系的表,特别是已定义CASCADING外键约束的表
•不支持“serializable”隔离级别

  【2.4】MGR的监视  

  (1)常用系统表监视  

两个performance_schema表
•replication_group_members
•replication_group_member_stats
本地成员状态
•扩展Replication performance_schema 表
•group_replication_recovery channel information
•group_replication_applier channel information
•新的全局变量
•group_replication_primary_member

  (2)MGR群组复制的高可用性

更好的容错度
群组复制的高可用性
–故障(F)所需的服务器数量(N)N = 2F + 1.
–最多支持9个成员
•允许4个成员故障。

–没有脑裂的问题
仅当大多数成员(N/2+1)在线时,群组才可用,如下图
  (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

  【2.5】 脑裂问题

    (1)什么是脑裂?(2)MGR为什么会出现脑裂?

      (1)什么是脑裂:我理解的就是,一个大脑控制变成了2个大脑控制,各做各的。产生了不同步,不一致的操作与选择。

      (2)MGR为什么会出现脑裂:大多数情况是因为网络连接问题。如下图

        检测网络分区:Performance_Schema.replication_group_members

        有2个可能-》《1》真就另外3台挂掉了  《2》另外3台没挂,是因为和这2台连不上了无法通信

        我们这里说《2》这种情况

        (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

        即,本来MGR群组的5台机器都能互相连通,突然,S3-S5这3台机器与S1-S2机器失去了连接。

        只有这S1-S2这2台机器被监控到在线,但其他3台机器是多数节点,它们也可能有连接在使用。对于S3-S5而言,S1-S2是宕机概念的。

        然后对于S3-S5而言的MGR是可以正常运行的,就把其S1-S2丢失了。

        这个时候S1-S2 这2台 查询可以正常运行,但不可以进行增删改(如果发生增删改,就无法再次回到MGR群主里了),如下图

          (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

        如何处理呢?强制指定节点生成一个新的MGR。

          (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

    【2.6】MGR读取一致性参数解析

      group_replication_consistency8.0.14之后
        •EVENTUAL,BEFORE,AFTER和BEFORE_AND_AFTER

     (1)EVENTUAL(默认值)事务在执行之前不等待先前的事务应用,也不等待其他成员应用其更改。这是8.0.14之前的

          (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

    (2)BEFORE

      事务将在开始执行之前等待所有先前的事务完成。这可确保此事务将在最新的数据快照上执行,而不用管是哪个成员。

        (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

    (3)AFTER

      事务将等待其更改已应用于其他成员。这可确保一旦此事务完成,所有后续事务都会读取包含其更改的数据库状态,而不管它们在哪个成员上执行。

        (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

    (4)BEFORE_AND_AFTER

      •此事务将等到:

          1)所有先前的事务在开始执行之前完成;

          2)其变更已适用于其他成员。这可确保:

            1)此事务将在最新的数据快照上执行;

            2)一旦此事务完成,所有后续事务都会读取包含其更改的数据库状态,而不管它们在哪个成员上执行

        (5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

    【2.7】了解MGR节点状态

      参考:https://blog.csdn.net/d6619309/article/details/70432640

【4】mysql并行复制

【4.1】并行复制的当前大三模式

  【库间并发】【组提交】【WriteSet

总的来说MySQL关于并行复制到目前为止经历过三个比较关键的时间结点“库间并发”,“组提交”,“写集合”;真可谓是*代有人才出,前

  浪死在沙滩上;总的来说就后面的比前面的不知道高到哪里去了!

库间并发】(5.6)

  库间并发的理论依据是这样的 ---- 一个实例内可能会有多个库(schema),不同的库之间没有什么依赖关系,所以在slave那边为

  每一个库(schema)单独起一个SQL线程,这样就能通过多线程并行复制的方式来提高主从复制的效率。

  这个理论听起来没问题,但是事实上一个实例也就一个业务库,所以这种库间并发就没什么作用了;也就是说这个方式的适用场景

  比较少,针对这个不足直到“组提交”才解决!

组提交】(5.7)

  组提交的理论依据是这样的 --- 如果多个事务他们能在同一时间内提交,这个就间接说明了这个几个事务锁上是没有冲突的,

  也是就说他们各自持有不同的锁,互不影响;逻辑上我们几个事务看一个组,在slave以“组”为单位分配给SQL线程执行,这样

  多个SQL线程就可以并行跑了;而且不在以库为并行的粒度,效果上要比“库间并发”要好一些。

  这个事实上也有一些问题,因为它要求库上要有一定的并发度,不然就有可能变成每个组里面只有一个事务,这样就有串行没什么

  区别了,为了解决这个问题MySQL提供了两个参数就是希望在提交时先等一等,尽可能的让组内多一些事务,以提高并行复制的效率。

  “binlog_group_commit_sync_no_delay_count” 设置一个下水位,也就是说一个组要凑足多少个事务再提交;为子防止永远也凑不足

  那么多个事务MySQL还以时间为维度给出了另一个参数“binlog_group_commit_sync_delay”这个参数就是最多等多久,

  超过这个时间长度后就算没有凑足也提交。

  

  亲身经历呀! 这两个参数特别难找到合的值,就算今天合适,过几天业务上有点变化后,又可能变的不合适了;如果MySQL能自己

  达到一个自适应的效果就好了;这个自适用要到WriteSet才完成(WriteSet并不是通过自动调整这两个参数来完成,

  它采用了完全不同的解决思路)。

WriteSet】(8.0)

  WriteSet解决了什么问题?当然是解决了“组提交”的问题啦! 说了和没说一个样,好下面我们来举个例子(比较学院派);假设你第一天

  更新了id == 1 的那一行,第二天你更新了id == 2 的那一行,第三天有个slave过来同步你的数据啦! 以“组提交”的尿性,这两个更新

  会被打包到不同的“组”,也就是说会有两个组;由于每个组内只有一个事务,所以逻辑上就串行了,起来!

  身为DBA的你一可以看出来这两个事实上是可以打包到同一个组里来的,因为他们互不冲突,就算打包到同一个组也不引起数据的不

  一致。 于是你有两个办法

  办法1): 妹妹你大胆的把“binlog_group_commit_sync_no_delay_count”设置成 2,也就是说一个组至少要包含两个事务,并且把

  “binlog_group_commit_sync_delay”设置成24小时以上!如果你真的做了,你就可以回家了,你的数据库太慢了(第一条update等了一天),

  才完成!

  办法2): 叫MySQL用一本小本子记下它最近改了什么,如果现在要改的数据和之前的数据不冲突,那么他们就可以把包到同一个组;还是

  我们刚才的例子,由于第二天改的值的id==2所以它和第一天的不冲突,那么它完全可以把第二天的更新和第一天的更新打包到同一个组。

  这样组里面就有两个事务了,在slave第三天回放时就会有一种并行的效果。

  这本小本子这么牛逼可以做大一点吗?当然!binlog_transaction_dependency_history_size 这个参数就小本子的容量了;那我的MySQL

  有这本小本子吗? 如果你的mysql比mysql-5.7.22新的话,小本子就是它生来就有的。

  也就是说“WriteSet”是站在“组提交”这个巨人的基础之间建立起来的,而且是在master上做的自“适应”打包分组,所以你只要在master上

  新增两个参数

binlog_transaction_dependency_tracking  = WRITESET                 #    COMMIT_ORDER
transaction_write_set_extraction = XXHASH64

在MySQL 8.0中,该版本引入了参数binlog_transaction_depandency_tracking用于控制如何决定事务的依赖关系。

该值有三个选项:

  • 默认的COMMIT_ORDERE表示继续使用5.7中的基于组提交的方式决定事务的依赖关系;

  • WRITESET表示使用写集合来决定事务的依赖关系;

  • 还有一个选项WRITESET_SESSION表示使用WriteSet来决定事务的依赖关系,但是同一个Session内的事务不会有相同的last_committed值。

  理论说完了,下面我们看一下实践。

WriteSet实践

  基于WriteSet的并行复制环境怎么搭建我这里就不说了,也就是比正常的“组提交”在master上多加两个参数,不讲了;我这里想

  直接给出两种并行复制方式下的行为变化。

  1): 我们要执行的目标SQL如下

(5.3)mysql高可用系列——mysql复制(理论篇)【续写中】
create database tempdb;
use tempdb;
create table person(id int not null auto_increment primary key,name int); insert into person(name) values(1);
insert into person(name) values(2);
insert into person(name) values(3);
insert into person(name) values(5);
(5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

  2): 看一下组提交对上面SQL的分组情况

(5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

  3): 看write_set的对“组提交”优化后的情况

(5.3)mysql高可用系列——mysql复制(理论篇)【续写中】

  可以看到各个insert是可以并行执行的,所以它们被分到了同个组(last_committed相同);last_committedsequence_number

  这两个值在binlog里面记着就有,我在解析binlog的时候习惯使用如下选项

mysqlbinlog -vvv --base64-output='decode-rows' mysql-bin.000002

  

总结

  WriteSet是在“组提交”方式上建立起来的,一种新的并行复制实现;相比“组提交”来说更加灵活;当然,由于并发度上去了,相比“组提交”

  WriteSet在性能上会更加好一些,在一些WriteSet没有办法是否冲突时,能平滑过度到“组提交”模式。

参考文章

  https://www.cnblogs.com/conanwang/p/5935568.html

  详细复制参考文章:https://mp.weixin.qq.com/s/-AL3b4DaXPBN4EKjjSYdUg