mysql复制流程

  1. 在备库 B 上通过 change master 命令,设置主库 A 的 IP、端口、用户名、明码,以及要从哪个地位开始申请 binlog,这个地位蕴含文件名和日志偏移量
  2. 在备库 B 上执行 start slave 命令,这时候备库会启动两个线程,就是图中的 io_thread 和 sql_thread。其中 io_thread 负责与主库建设连贯
  3. 主库 A 校验完用户名、明码后,开始依照备库 B 传过来的地位,从本地读取 binlog,发给 B
  4. 备库 B 拿到 binlog 后,写到本地文件,称为直达日志(relay log)
  5. sql_thread 读取直达日志,解析出日志里的命令,并执行

binlog的三种格局

statement

statement 格局下,记录到 binlog 里的是语句原文

binlog 设置的是 statement 格局,并且语句中有 limit,所以这个命令可能是 unsafe 的,可能会呈现这样一种状况:在主库执行这条 SQL 语句的时候,用的是索引 a;而在备库执行这条 SQL 语句的时候,却应用了索引 t_modified。因而,MySQL 认为这样写是有危险的

在statement模式下,因为他是记录的执行语句,所以,为了让这些语句在slave端也能正确执行,那么他还必须记录每条语句在执行的时候的一些相干信息,也就是上下文信息,以保障所有语句在slave端被执行的时候可能失去和在master端执行时候雷同的后果。这须要应用mysqlbinlog 工具解析进去,而后把解析后果整个发给 MySQL 执行,而不是应该只应用从statement解析出的语句来执行,这样会短少上下文

row

当 binlog_format 应用 row 格局的时候,binlog 外面记录了实在影响的行的主键 id

当 binlog_format 应用 row 格局的时候,binlog 外面记录了实在删除行的主键 id,这样 binlog 传到备库去的时候,就必定会删除 id=4 的行,不会有主备删除不同行的问题

row 格局的毛病是,很占空间。比方你用一个 delete 语句删掉 10 万行数据,用 statement 的话就是一个 SQL 语句被记录到 binlog 中,占用几十个字节的空间。但如果用 row 格局的 binlog,就要把这 10 万条记录都写到 binlog 中。这样做,不仅会占用更大的空间,同时写 binlog 也要消耗 IO 资源,影响执行速度

然而当初越来越多的场景要求把 MySQL 的 binlog 格局设置成 row。因为能够进行复原数据

在row模式下,binlog中能够不记录执行的sql语句的上下文相干的信息,仅仅只须要记录那一条记录被批改了,批改成什么样了,所以row的日志内容会十分分明的记录下每一行数据批改的细节,非常容易了解

mixed

mixed 格局的意思是,MySQL 本人会判断这条 SQL 语句是否可能引起主备不统一,如果有可能,就用 row 格局,否则就用 statement 格局

mixed 格局能够利用 statment 格局的长处,同时又防止了数据不统一的危险

如果你的线上 MySQL 设置的 binlog 格局是 statement 的话,那基本上就能够认为这是一个不合理的设置。你至多应该把 binlog 的格局设置为 mixed

双M架构

节点 A 和 B 之间总是互为主备关系。这样在切换的时候就不必再批改主备关系

如何解决循环复制的问题

  • 规定两个库的 server id 必须不同,如果雷同,则它们之间不能设定为主备关系
  • 一个备库接到 binlog 并在重放的过程中,生成与原 binlog 的 server id 雷同的新的 binlog
  • 每个库在收到从本人的主库发过来的日志后,先判断 server id,如果跟本人的雷同,示意这个日志是本人生成的,就间接抛弃这个日志

主备提早

  • 在备库上执行 show slave status 命令,它的返回后果外面会显示 seconds_behind_master,用于示意以后备库提早了多少秒

seconds_behind_master 的计算方法是这样的:

  1. 每个事务的 binlog 外面都有一个工夫字段,用于记录主库上写入的工夫;
  2. 备库取出以后正在执行的事务的工夫字段的值,计算它与以后零碎工夫的差值,失去 seconds_behind_master,单位是秒

备库连贯到主库的时候,会通过执行 SELECT UNIX_TIMESTAMP() 函数来取得以后主库的零碎工夫。如果这时候发现主库的零碎工夫与本人不统一,备库在执行 seconds_behind_master 计算的时候会主动扣掉这个差值

在网络失常的时候,日志从主库传给备库所需的工夫是很短的,网络失常状况下,主备提早的次要起源是备库接管完 binlog 和执行完这个事务之间的时间差。主备提早最间接的体现是,备库生产直达日志(relay log)的速度,比主库生产 binlog 的速度要慢。

主备提早的起因

  • 备库所在机器的性能要比主库所在的机器性能差
  • 备库的压力大。个别的想法是,主库既然提供了写能力,那么备库能够提供一些读能力。或者一些经营后盾须要的剖析语句,不能影响失常业务,所以只能在备库上跑。漠视了备库的压力管制。后果就是,备库上的查问消耗了大量的 CPU 资源,影响了同步速度,造成主备提早

解决形式:

  • 一主多从。除了备库外,能够多接几个从库,让这些从库来分担读的压力。
  • 通过 binlog 输入到内部零碎,比方 Hadoop 这类零碎,让内部零碎提供统计类查问的能力。
  • 大事务:主库上必须等事务执行实现才会写入 binlog,再传给备库。所以,如果一个主库上的语句执行 10 分钟,那这个事务很可能就会导致从库提早 10 分钟
  • 大表DDL

主备切换形式

可靠性优先策略

  1. 判断备库 B 当初的 seconds_behind_master,如果小于某个值(比方 5 秒)持续下一步,否则继续重试这一步
  2. 把主库 A 改成只读状态,即把 readonly 设置为 true
  3. 判断备库 B 的 seconds_behind_master 的值,直到这个值变成 0 为止
  4. 把备库 B 改成可读写状态,也就是把 readonly 设置为 false
  5. 把业务申请切到备库 B

这个切换流程中是有不可用工夫的。因为在步骤 2 之后,主库 A 和备库 B 都处于 readonly 状态,也就是说这时零碎处于不可写状态,直到步骤 5 实现后能力复原

在这个不可用状态中,比拟消耗工夫的是步骤 3,可能须要消耗好几秒的工夫。这也是为什么须要在步骤 1 先做判断,确保 seconds_behind_master 的值足够小

可用性优先策略

不等主备数据同步,间接把连贯切到备库 B,并且让备库 B 能够读写,那么零碎简直就没有不可用工夫了

可用性优先策略,且 binlog_format=mixed

  1. 主库 A 执行完 insert 语句,插入了一行数据(4,4),之后开始进行主备切换。
  2. 因为主备之间有 5 秒的提早,所以备库 B 还没来得及利用“插入 c=4”这个直达日志,就开始接管客户端“插入 c=5”的命令。
  3. 备库 B 插入了一行数据(4,5),并且把这个 binlog 发给主库 A
  4. 备库 B 执行“插入 c=4”这个直达日志,插入了一行数据(5,4)。
  5. 而间接在备库 B 执行的“插入 c=5”这个语句,传到主库 A,就插入了一行新数据(5,5)。
  6. 最初的后果就是,主库 A 和备库 B 上呈现了两行不统一的数据。能够看到,这个数据不统一,是由可用性优先流程导致的。

可用性优先策略,但设置 binlog_format=row

因为 row 格局在记录 binlog 的时候,会记录新插入的行的所有字段值,所以最初只会有一行不统一。而且,两边的主备同步的利用线程会报错 duplicate key error 并进行。也就是说,这种状况下,备库 B 的 (5,4) 和主库 A 的 (5,5) 这两行数据,都不会被对方执行

总结:

  1. 应用 row 格局的 binlog 时,数据不统一的问题更容易被发现。
  2. 而应用 mixed 或者 statement 格局的 binlog 时,数据很可能悄悄地就不统一了。如果你过了很久才发现数据不统一的问题,很可能这时的数据不统一曾经不可查,或者连带造成了更多的数据逻辑不统一。
  3. 主备切换的可用性优先策略会导致数据不统一。因而,大多数状况下,倡议应用可靠性优先策略。毕竟对数据服务来说的话,数据的可靠性个别还是要优于可用性的。

并行复制

复制时,excecution阶段能够并行执行,binlog flush的时候,按程序进行;所以主节点和从节点的binlog还是统一的

mysql5.6

按库并行;同一个库的binlog由同一个worker解决,不同库的binlog由不同worker解决

mysql5.7

  • DATABASE:默认值,基于库的并行复制形式
  • LOGICAL_CLOCK:基于组提交的并行复制形式。

    • 一个组提交的事务都是能够并行回放,因为这些事务都已进入到事务的 Prepare 阶段,则阐明事务之间没有任何抵触(否则就不可能提交)
    • 一个组提交的事务都是能够并行回放(配合binary log group commit);slave机器的relay log中 last_committed雷同的事务(sequence_num不同)能够并发执行

      • sequence_number 是自增事务 ID,last_commited 代表上一个提交的事务 ID。
      • 如果两个事务的 last_commited 雷同,阐明这两个事务是在同一个 Group 内提交的

    mysql5.7.22

    即便主库在串行提交的事务,只有相互不抵触,在备库就能够并行回放。

新增了一个参数 binlog-transaction-dependency-tracking,用来管制是否启用这个新策略。这个参数的可选值有以下三种

  • COMMIT_ORDER,示意的就是后面介绍的,依据5.7的组提交策略
  • WRITESET,示意的是对于事务波及更新的每一行,计算出这一行的 hash 值,组成汇合 writeset。如果两个事务没有操作雷同的行,也就是说它们的 writeset 没有交加,就能够并行

    • hash 值是通过“库名 + 表名 + 索引名 + 值 + (惟一索引、值)”计算出来的
    • writeset 是在主库生成后间接写入到 binlog 外面的,从库执行时须要解析binlog,判断writeset之间是否能够并行
    • WRITESET 是一个 hash 数组,大小由参数 binlog_transaction_dependency_history_size 决定
  • WRITESET_SESSION,在 WRITESET 形式的根底上,保障同一个 session 内的事务不可并行

主从切换

在传统的复制外面,当产生故障,须要主从切换,须要找到 Binlog 和 位点信息,复原实现数据之后将主节点指向新的主节点。在 MySQL 5.6 外面,提供了新的数据恢复思路,只须要晓得主节点的 IP、端口以及账号密码就行,因为复制是主动的,MySQL 会通过外部机制 GTID 主动找点同步

GTID (global transaction identifier)

全局事务 ID,一个事务对应一个 GTID,保障了在每个在主库上提交的事务在集群中有一个惟一的 ID

  • GTID = source_id:transaction_id

source_id 失常即是 server_uuid,在第一次启动时生成(函数 generate_server_uuid),并长久化到 DATADIR/auto.cnf 文件里。
transaction_id 是程序化的序列号(sequence number),在每台 MySQL 服务器上都是从 1 开始自增长的序列,是事务的惟一标识。

  • GTID 的生成受参数gtid_next的值管制。
    在 Master 上,gtid_next默认是 AUTOMATIC,即 GTID 在每次事务提交时主动生成。它从以后已执行的 GTID 汇合(即 gtid_executed)中,找一个大于 0 的未应用的最小值作为下个事务 GTID。在理论的更新事务记录之前将 GTID 写入到 Binlog。也能够设置gtid_next为一个指定的值。通过 set gtid_next='current_gtid’指定为 current_gtid,那么就有两种可能:

    • a. 如果 current_gtid 曾经存在于实例的 GTID 汇合中,接下来执行的这个事务会间接被零碎疏忽;
    • b. 如果 current_gtid 没有存在于实例的 GTID 汇合中,就将这个 current_gtid 调配给接下来要执行的事务。
  • 留神,一个 current_gtid 只能给一个事务应用。这个事务提交后,如果要执行下一个事务,就要执行 set 命令,把 gtid_next 设置成另外一个 gtid 或者 automatic
    每个 MySQL 实例都保护了一个 GTID 汇合,用来对应“这个实例执行过的所有事务”
  • GTID 不便实现主从之间的 failover(主从切换),不必一步一步的去定位 Binlog日志文件和查找 Binlog 的位点信息
  • GTID 模式的启动也很简略,咱们只须要在启动一个 MySQL 实例的时候,加上参数 gtid_mode=on 和 enforce_gtid_consistency=on 就能够了

在 Slave 上,从 Binlog 先读取到主库的 GTID(即 set gtid_next 记录),而后执行的事务采纳该 GTID。

  • 在原来基于日志的复制中,从库须要告知主库要从哪个偏移量进行增量同步, 如果指定谬误会造成数据的脱漏,从而造成数据的不统一。
  • 而基于 GTID 的复制中,从库会告知主库曾经执行的事务的 GTID 的值,而后主库会将所有未执行的事务的 GTID 的列表返回给从库,并且能够保障同一个事务只在指定的从库执行一次,通过全局的事务 ID 确定从库要执行的事务的形式代替了以前须要用 Binlog 和 位点确定从库要执行的事务的形式

步骤

  • master 更新数据时,会在事务前产生 GTID,一起记录到 Binlog 日志中
  • slave 端的 I/O 线程将变更的 Binlog,写入到本地的 relay log 中,读取值是依据gitd_next变量,通知咱们 slave 下一个执行哪个 GTID
  • SQL 线程从 relay log 中获取 GTID,而后比照 slave 端的 Binlog 是否有记录。如果有记录,阐明该 GTID 的事务曾经执行,slave 会疏忽
  • 如果没有记录,slave 就会从 relay log 中执行该 GTID 的事务,并记录到 Binlog

从库切换主备步骤

A是已经的主库,A‘是A的备库,它们互为主备;B是从库,最开始它从A同步数据,当初须要从A‘同步数据

  1. 把当初这个时刻,实例 A’的 GTID 汇合记为 set_a
  2. 实例 B 的 GTID 汇合记为 set_b
  3. 在实例 B 上执行 start slave 命令,实例 B 指定主库 A’,基于主备协定建设连贯
  4. 实例 B 把 set_b 发给主库 A’。
  5. 实例 A’算出 set_a 与 set_b 的差集,也就是所有存在于 set_a,然而不存在于 set_b 的 GTID 的汇合,判断 A’本地是否蕴含了这个差集须要的所有 binlog 事务

    1. 如果不蕴含,示意 A’曾经把实例 B 须要的 binlog 给删掉了,间接返回谬误;
    2. 如果确认全副蕴含,A’从本人的 binlog 文件外面,找出第一个不在 set_b 的事务,发给 B;之后就从这个事务开始,往后读文件,按程序取 binlog 发给 B 去执行

参考资料

极客工夫,mysql实战45讲