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

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

 

正文

 

【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开发、优化与管理维护》

       

        

【2.1】异步复制

和MSSQL的高性能模式镜像一样):

(3.23 native)

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

      

 

【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而回滚了这个事务导致数据丢失。导致主从不一致。

      

      

 

 

【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】复制之间的区别

    

 

【3】MGR 群组复制

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

(1)整体架构

    

    (2)replication plugin 插件

      

    (3)GCS

      

(2)MGR的模式

    单主模式(single primary mode)

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

    

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

      

    多主同步模式

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

      

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

        

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

        

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

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

(3)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)在线时,群组才可用,如下图
  

 

(4) MGR脑裂问题

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

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

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

        检测网络分区:Performance_Schema.replication_group_members

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

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

        

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

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

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

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

           

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

          

(5)MGR读取一致性参数解析

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


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

          

    (2)BEFORE

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

        

    (3)AFTER

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

        

    (4)BEFORE_AND_AFTER

      •此事务将等到:

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

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

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

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

        

 

(6)了解MGR节点状态

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

【4】mysql并行复制

并行复制的当前大三模式

  【库间并发】【组提交】【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如下

复制代码
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);
复制代码

 

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

 

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

   可以看到各个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

posted @ 2019-08-04 18:29  郭大侠1  阅读(574)  评论(0编辑  收藏  举报