大多数应用场景都是读多写少,很多架构都是一主多从。 虚箭头是主从关系,也就是说A和A’互为主备关系。

接下来要讨论的问题就是一主多从夹头下,主库故障后的主备切换的问题。切换后的结果如下图 A故障后,A’成为新的主库,从库B,C,D也要该接到A’,正是由于多了从库B、C、D重新指向的这个过程,所以主备切换的复杂性也相应增加了。

接下来就学习一个切换系统会怎么完成一主多从的主备切换过程。

基于位点的主备切换

将B节点设置成A的从库的时候需要执行change master,需要指定主库的binlog文件名和起始的偏移位置。 现在A故障,A’成为新的主库,那么B也要切换成为A’的从库,同样需要指定A’中的binlog文件名和起始偏移位置。但是相同的日志在A和A’的位置是不一样的(因为如何保证高可用中的并行复制策略)。

所以需要找到一个稍微靠前的,然后再通过判断跳过那些在从库上执行过的事务。一种取同步为位点的方法就是:

  1. 等待新主库 A’把中转日志(relay log)全部同步完成;
  2. 在 A’上执行 show master status 命令,得到当前 A’上最新的 File 和 Position;
  3. 取原主库 A 故障的时刻 T;
  4. 用 mysqlbinlog 工具解析 A’的 File,得到 T 时刻的位点。

从库b在切换执行binlog的时候,遇到错误需要跳过:

  • 主动跳过一个事务
set global sql_slave_skip_counter=1;
start slave;
  • 通过设置 slave_skip_errors 参数,直接设置跳过指定的错误。在主备切换的过程中,出现插入重复键和删除不存在的行错误跳过是无损的,不过等主从关系建立完成并稳定一段时间后要关闭,避免真的出现主从不一致了。

GTID

因为通过 sql_slave_skip_counter 跳过事务和通过 slave_skip_errors 忽略错误的方法的复杂和易错,引入了GTID来解决主备切换的问题。

GTID 的全称是 Global Transaction Identifier,也就是全局事务 ID,是一个事务在提交的时候生成的,是这个事务的唯一标识。由两部分组成:

server_uuid:gno
  • server_uuid:一个实例第一次启动的时候生成的,是一个全局唯一的值。
  • gno是一个整数,初始值为1,每次提交事务的时候分配给这个事务,并加1。

GTID 模式的启动也很简单,我们只需要在==启动一个 MySQL 实例的时候,加上参数 gtid_mode=on 和 enforce_gtid_consistency=on 就可以了==。

a. 如果 current_gtid 已经存在于实例的 GTID 集合中,接下来执行的这个事务会直接被系统忽略; b. 如果 current_gtid 没有存在于实例的 GTID 集合中,就将这个 current_gtid 分配给接下来要执行的事务,也就是说系统不需要给这个事务生成新的 GTID,因此 gno 也不用加 1。

基于GTID的主备切换

在此模式下,从库B切换新主库为A’的语法

CHANGE MASTER TO 
MASTER_HOST=$host_name 
MASTER_PORT=$port 
MASTER_USER=$user_name 
MASTER_PASSWORD=$password 
master_auto_position=1 

==master_auto_position=1 就表示这个主备关系使用的是 GTID 协议==。可以看到,前面让我们头疼不已的 MASTER_LOG_FILE 和 MASTER_LOG_POS 参数,已经不需要指定了。

将A’的GTID集合记做set_a,B的GTID集合记做set_b。那么B上执行start slave的流程是这样的:

  • 实例 B 指定主库 A’,基于主备协议建立连接。
  • 实例 B 把 set_b 发给主库 A’。
  • 实例 A’算出 set_a 与 set_b 的差集,也就是所有存在于 set_a,但是不存在于 set_b 的 GTID 的集合,判断 A’本地是否包含了这个差集需要的所有 binlog 事务。
    • 如果不包含,表示 A’已经把实例 B 需要的 binlog 给删掉了,直接返回错误;
    • 如果确认全部包含,A’从自己的 binlog 文件里面,找出第一个不在 set_b 的事务,发给 B;
  • 之后就从这个事务开始,往后读文件,按顺序取 binlog 发给 B 去执行。

这里需要注意一点,那就是建立主备关系就需要保证主库发送给备库的日志是完整的,如果B需要的日志已经不存在了,那么A’就拒绝将日志发送给B。

GTID与在线DDL

对线上数据库的大表加索引可以利用这GTID来解决,

首先是一个双M结构的数据库集群,为了避免新增索引对主库的影响,先在从库新增索引,然后切换。

有两个互为主备关系的库实例 X 和实例 Y,且当前主库是 X,并且都打开了 GTID 模式。这时的主备切换流程可以变成下面这样:

  • 在实例 X 上执行 stop slave。这样下面执行DDL的时候就不会同步到过来
  • 在实例 Y 上执行 DDL 语句。注意,这里并不需要关闭 binlog。
  • 执行完成后,查出这个 DDL 语句对应的 GTID,并记为 server_uuid_of_Y:gno。到实例 X 上执行以下语句序列:
set GTID_NEXT="server_uuid_of_Y:gno";
begin;
commit;
set gtid_next=automatic;
start slave;

接下来,执行完主备切换后,再按照上面的流程执行一遍就好了。 因为在双M模式下从库的DDL语句也会传给主库,所以这样通过切换,让两个库在作为从库的时候建立索引,并利用GTID互相忽略对方的DDL,完成了DDL和主从的切换。