大多数应用场景都是读多写少,很多架构都是一主多从。
虚箭头是主从关系,也就是说A和A’互为主备关系。
接下来要讨论的问题就是一主多从夹头下,主库故障后的主备切换的问题。切换后的结果如下图
A故障后,A’成为新的主库,从库B,C,D也要该接到A’,正是由于多了从库B、C、D重新指向的这个过程,所以主备切换的复杂性也相应增加了。
接下来就学习一个切换系统会怎么完成一主多从的主备切换过程。
基于位点的主备切换
将B节点设置成A的从库的时候需要执行change master,需要指定主库的binlog文件名和起始的偏移位置。 现在A故障,A’成为新的主库,那么B也要切换成为A’的从库,同样需要指定A’中的binlog文件名和起始偏移位置。但是相同的日志在A和A’的位置是不一样的(因为如何保证高可用中的并行复制策略)。
所以需要找到一个稍微靠前的,然后再通过判断跳过那些在从库上执行过的事务。一种取同步为位点的方法就是:
- 等待新主库 A’把中转日志(relay log)全部同步完成;
- 在 A’上执行 show master status 命令,得到当前 A’上最新的 File 和 Position;
- 取原主库 A 故障的时刻 T;
- 用 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和主从的切换。