43
MySQL -- 主从切换
source link: http://zhongmingmao.me/2019/02/27/mysql-master-slave-switch/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
- 虚线箭头为 主从关系 ,
A
和A'
互为主从,B
、C
、D
指向主库A
- 一主多从的设置,一般用于 读写分离 ,主库负责 所有的写入 和 一部分读 ,其它读请求由从库分担
主库故障切换
A'
成为新的主库, B
、 C
、 D
指向主库 A'
基于位点的切换
B
原先是 A
的从库,本地记录的也是 A
的位点,但 相同的日志 , A
的位点与 A'
的位点是 不同 的
-- 节点B设置为节点A'的从库 CHANGE MASTER TO MASTER_HOST=$host_name MASTER_PORT=$port MASTER_USER=$user_name MASTER_PASSWORD=$password MASTER_LOG_FILE=$master_log_name MASTER_LOG_POS=$master_log_pos
寻找位点
- 很难精确,只能大概获取一个位置
- 由于在切换过程中 不能丢数据 ,在寻找位点的时候,总是找一个 稍微往前的位点 ,跳过那些已经在
B
执行过的事务
常规步骤
- 等待新主库
A'
将所有relaylog
全部执行完 - 在
A'
上执行SHOW MASTER STATUS
,得到A'
上最新的File
和Position
- 获取原主库
A
发生故障的时刻T
- 使用
mysqlbinlog
解析A'
的File
,得到时刻T
的位点
-- end_log_pos=123,表示在时刻T,A'写入新binlog的位置,作为B的CHANGE MASTER TO命令的MASTER_LOG_POS参数 $ mysqlbinlog /var/lib/mysql/slave-bin.000009 --start-datetime='2019-02-26 17:44:00' --stop-datetime='2019-02-26 17:45:00' | grep end_log_pos #190226 17:42:01 server id 2 end_log_pos 123 CRC32 0x5b852e9b Start: binlog v 4, server v 5.7.25-log created 190226 17:42:01 at startup
位点不精确
- 假设在时刻
T
,原主库A
已经执行完成了一个INSERT
语句,插入一行记录R
- 并且已经将
binlog
传给A'
和B,然后原主库A
掉电
- 并且已经将
- 在
B
上,由于已经同步了binlog
,R
这一行是已经存在的 - 在新主库
A'
上,R
这一行也是存在的,日志写在了123
这个位置之后 - 在
B
上执行CHANGE MASTER TO
,执行A'
的File
文件的123
位置- 就会把插入
R
这一行数据的binlog
又同步到B
去执行 -
B
的同步线程会报 重复主键 错误,然后停止同步
- 就会把插入
跳过错误
方式1:主动跳过一个事务,需要 持续观察 ,每次碰到这些错误,就执行一次跳过命令
SET GLOBAL sql_slave_skip_counter=1; START SLAVE;
方式1:设置 slave_skip_errors=1032,1062
, 1032
错误是删除数据时 找不到行 , 1062
错误是插入数据时报 唯一键冲突
在 主从切换过程 中,直接跳过 1032
和 1062
是 无损 的,等主从间的同步关系建立完成后,需要将 slave_skip_errors
恢复为 OFF
mysql> SHOW VARIABLES LIKE '%slave_skip_errors%'; +-------------------+-------+ | Variable_name | Value | +-------------------+-------+ | slave_skip_errors | OFF | +-------------------+-------+
基于GTID的切换
- GTID: Global Transaction Identifier, 全局事务ID
- 在事务 提交 时生成,是事务的唯一标识,组成
GTID = server_uuid:gno
-
server_uuid
是实例第一次 启动 时自动生成的,是一个 全局唯一 的值 -
gno
是一个整数,初始值为1
,每次 提交事务 时分配,+1
-
- 官方定义:
GTID = source_id:transaction_id
-
source_id
即server_uuid
-
transaction_id
容易造成误解-
transaction_id
一般指事务ID,是在事务 执行过程 中分配的,即使事务 回滚 了,事务ID也会 递增 - 而
gno
只有在事务 提交 时才会分配,因此GTID
往往是 连续 的
-
-
- 开启
GTID
模式,添加启动参数gtid_mode=ON
和enforce_gtid_consistency=ON
- 在
GTID
模式下,每个事务都会跟一个GTID
一一对应,生成GTID
的方式由参数gtid_next
(Session)控制 - 每个MySQL实例都维护了一个
GTID
集合,用于表示: 实例执行过的所有事务
gtid_next
-
gtid_next=AUTOMATIC
,MySQL会将server_uuid:gno
分配给该事务- 记录
binlog
时,会先记录一行SET @@SESSION.GTID_NEXT=server_uuid:gno
,将该GTID
加入到本实例的GTID
集合
- 记录
-
gtid_next=UUID:NUMBER
,通过SET @@SESSION.GTID_NEXT=current_gtid
执行- 如果
current_gtid
已经 存在 于实例的GTID
集合中,那么接下来执行的这个事务会直接被系统 忽略 - 如果
current_gtid
并 没有存在 于实例的GTID
集合中,那么接下来执行的这个事务会被分配为current_gtid
-
current_gtid
只能给 一个事务 使用,如果执行下一个事务,需要把gtid_next
设置成另一个GTID
或者AUTOMATIC
- 如果
-- gtid_next=AUTOMATIC mysql> SHOW BINLOG EVENTS IN 'master-bin.000003'; +-------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------------------------------------------------------+ | Log_name | Pos | Event_type | Server_id | End_log_pos | Info | +-------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------------------------------------------------------+ | master-bin.000003 | 4 | Format_desc | 1 | 123 | Server ver: 5.7.25-log, Binlog ver: 4 | | master-bin.000003 | 123 | Previous_gtids | 1 | 194 | b8502fe3-3b4a-11e9-9562-0242ac110002:1-5 | | master-bin.000003 | 194 | Gtid | 1 | 259 | SET @@SESSION.GTID_NEXT= 'b8502fe3-3b4a-11e9-9562-0242ac110002:6' | | master-bin.000003 | 259 | Query | 1 | 484 | GRANT REPLICATION SLAVE ON *.* TO 'replication'@'%' IDENTIFIED WITH 'mysql_native_password' AS '*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9' | | master-bin.000003 | 484 | Gtid | 1 | 549 | SET @@SESSION.GTID_NEXT= 'b8502fe3-3b4a-11e9-9562-0242ac110002:7' | | master-bin.000003 | 549 | Query | 1 | 643 | CREATE DATABASE test | +-------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------------------------------------------------------+
表初始化
CREATE TABLE `t` ( `id` INT(11) NOT NULL, `c` INT(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB; INSERT INTO t VALUES (1,1);
对应的binlog
mysql> SHOW MASTER STATUS; +-------------------+----------+--------------+------------------+-------------------------------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +-------------------+----------+--------------+------------------+-------------------------------------------+ | master-bin.000004 | 877 | | | b8502fe3-3b4a-11e9-9562-0242ac110002:1-12 | +-------------------+----------+--------------+------------------+-------------------------------------------+ mysql> SHOW BINLOG EVENTS IN 'master-bin.000004'; +-------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------+ | Log_name | Pos | Event_type | Server_id | End_log_pos | Info | +-------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------+ | master-bin.000004 | 4 | Format_desc | 1 | 123 | Server ver: 5.7.25-log, Binlog ver: 4 | | master-bin.000004 | 123 | Previous_gtids | 1 | 194 | b8502fe3-3b4a-11e9-9562-0242ac110002:1-9 | | master-bin.000004 | 194 | Gtid | 1 | 259 | SET @@SESSION.GTID_NEXT= 'b8502fe3-3b4a-11e9-9562-0242ac110002:10' | | master-bin.000004 | 259 | Query | 1 | 373 | use `test`; DROP TABLE `t` /* generated by server */ | | master-bin.000004 | 373 | Gtid | 1 | 438 | SET @@SESSION.GTID_NEXT= 'b8502fe3-3b4a-11e9-9562-0242ac110002:11' | | master-bin.000004 | 438 | Query | 1 | 620 | use `test`; CREATE TABLE `t` ( `id` INT(11) NOT NULL, `c` INT(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB | | master-bin.000004 | 620 | Gtid | 1 | 685 | SET @@SESSION.GTID_NEXT= 'b8502fe3-3b4a-11e9-9562-0242ac110002:12' | | master-bin.000004 | 685 | Query | 1 | 757 | BEGIN | | master-bin.000004 | 757 | Table_map | 1 | 802 | table_id: 109 (test.t) | | master-bin.000004 | 802 | Write_rows | 1 | 846 | table_id: 109 flags: STMT_END_F | | master-bin.000004 | 846 | Xid | 1 | 877 | COMMIT /* xid=27 */ | +-------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------+
- 事务
BEGIN
之前有一条SET @@SESSION.GTID_NEXT
- 如果实例X有从库Z,那么将
CREATE TABLE
和INSERT
语句的binlog
同步到从库Z执行- 执行事务之前,会先执行两个
SET
命令,这样两个GTID
就会被加入到从库Z的GTID
集合
- 执行事务之前,会先执行两个
主键冲突
- 如果实例X是实例Y的从库,之前实例Y上执行
INSERT INTO t VALUES (1,1)
- 对应的
GTID
为aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:12
- 实例X需要同步该事务过来执行,会报 主键冲突 的错误,实例X的同步线程停止,处理方法如下
- 对应的
-- 实例X提交一个空事务,将该GTID加到实例X的GTID集合中 SET gtid_next='aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:12'; BEGIN; COMMIT; -- 实例X的Executed_Gtid_Set已经包含了aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:12 mysql> SHOW MASTER STATUS; +-------------------+----------+--------------+------------------+------------------------------------------------------------------------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +-------------------+----------+--------------+------------------+------------------------------------------------------------------------------------+ | master-bin.000004 | 1087 | | | aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:12,b8502fe3-3b4a-11e9-9562-0242ac110002:1-12 | +-------------------+----------+--------------+------------------+------------------------------------------------------------------------------------+ -- 恢复GTID的默认分配行为 SET gtid_next=AUTOMATIC; -- 实例X还是会继续执行实例Y传过来的事务 -- 但由于实例X的GTID集合已经包含了aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:12,因此实例X会直接跳过该事务 START SLAVE;
主从切换
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
,取binlog
的逻辑如下
START SLAVE
- 实例
B
指定新主库A'
,基于 主从协议 建立连接 - 实例
B
把set_b
发送给A'
- 实例
A'
计算出seb_a
和set_b
的GTID
差集(存在于set_a
,但不存在于set_b
的GTID
集合)- 判断实例
A'
本地是否包含了 差集需要的所有binlog
事务 - 如果 没有全部包含 ,说明实例
A'
已经把实例B
所需要的binlog
删除掉了,直接返回错误 - 如果 全部包含 ,实例
A'
从自己的binlog
文件里面,找到第1个不在set_b
的事务,发送给实例B
- 然后从该事务开始,往后读文件,按顺序读取
binlog
,发给实例B
去执行
- 然后从该事务开始,往后读文件,按顺序读取
- 判断实例
位点 VS GTID
- 基于
GTID
的主从关系里面,系统认为只要 建立了主从关系 ,就必须保证 主库发给从库的日志是完整 的 - 如果实例
B
需要的日志已经不存在了,那么实例A'
就拒绝将日志发送给实例B
- 基于 位点 的协议,是由 从库决定 的,从库指定哪个位点,主库就发送什么位点,不做 日志完整性 的判断
- 基于
GTID
的协议,主从切换 不再需要找位点 ,而找位点的工作在实例A'
内部 自动完成
日志格式
- 切换前
- 实例
B
的GTID
集合:server_uuid_of_A:1-N
- 实例
- 新主库
A'
自己生成的binlog
对应的GTID
集合:server_uuid_of_A':1-M
- 切换后
- 实例
B
的GTID
集合:server_uuid_of_A:1-N,server_uuid_of_A':1-M
- 实例
参考资料
《MySQL实战45讲》
转载请注明出处:http://zhongmingmao.me/2019/02/27/mysql-master-slave-switch/
访问原文「MySQL -- 主从切换」获取最佳阅读体验并参与讨论
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK