一主多从的设置,用于读写分离,主库负责所有的写入和一部分读,其他读请求则由从库分担。
一主多从架构下,主库故障后的主备切换问题。相比于一主一备,多了从库指向新主库的过程。
基于位点的主备切换同步
把节点B设置为节点A‘的从库,执行change master
命令:
CHANGE MASTER TO
MASTER_HOST = $host_name
MASTER_POST = $port
MASTER_USER = $user_name
MASTER_PASSWORD = $password
MASTER_LOG_FILE = $master_log_name
MASTER_LOG_POS = $master_log_pos
- MASTER_HOST、MASTER_PORT、MASTER_USER和MASTER_PASSWORD四个参数,分别达标主库A’的IP、端口、用户名、密码
- MASTER_LOG_FILE和MASTER_LOG_POS是主库对应的文件名和日志偏移量,就是同步位点
节点B要设置为A‘的从库,就必须要设置位点参数。
原本节点B是A的从库,本地记录的也是A的位点。 但是相同的日志,A的位点于A’的位点不同。所以在切换的时候需要先找同步位点。
(这个不一致造成的原因之一:备库启用了并行复制,例如使用了组提交并行(prepare和commit状态之间事务组可以并行执行),那么这时候在主库上先执行完成的事务在备库上就不一定先执行完成了,binlog因此就会有所差异。 )
找同步位点的方法:
1、等待新主库A‘把中转日志(relay log)全部同步完成
2、在A’上执行show master status
命令,得到当前A’上最新的file和position
3、取原主库A故障时刻T
4、用mysqlbinlog
工具解析A’的file,得到T时刻的位点123
不过这样并不精确:
假设在T时刻,主库A已经执行完成了一个insert语句插入一行数据R,并且已经将binlog传给A‘和B,然后在传完的瞬间主库A的主机就掉电了。
此时系统状态如下:
1、在从库B上,由于同步了binlog,R这一行已经存在
2、在新主库A’上,R这一行也已经存在,日志卸载123位置后
3、在从库B上执行change master
命令,指向A’的file文件123位置,会把插入R这一行数据的binlog又同步到从库B执行,会发生主键冲突,然后停止同步
所以在切换主库的时候要主动跳过一些"重复操作"引起的错误,避免切换任务阻塞。
1、在从库上执行跳过事务命令:
set global sql_slave_skip_counter = 1;
start slave;
每次碰到错误就停下来,执行以此跳过命令,直到不再报错。
2、通过设置slave_skip_errors
参数,直接设定跳过指定的错误
主备切换时,有两类错误经常遇到:
1062 插入数据时唯一键冲突
1032 删除数据时找不到行
可以把slave_skip_errors
设置为"1032,1062",遇到这两个错误直接跳过。
注意,在主备切换过程中,跳过这两个错误是无损的。同步完成后,需要把整个参数设置为空,防止出现主从不一致时也跳过了。
基于GTID的主备切换同步
GTID全称Global Transaction Identifier ,也就是全局事务ID,是一个事务在提交的时候生成的,是这个事务的唯一标识。格式为:
GTID = server_uuid:gno
-
server_uuid
是一个实例第一次启动时自动生成的,是一个全局唯一的值 - gno是一个整数,初始值为1,每次提交事务的时候分配给这个事务,并+1
官方格式定义:
GTID = source_id:transaction_id
transaction_id指的是事务id,事务id实在事务执行的过程中分配的,如果这个事务回滚了,transaction_id也会增加,而gno实在事务提交的时候才会分分配,所以使用gno更好。
使用方法:在启动一个MySQL实例的时候,加上参数gtid_mode = on
和enforce_gtid_consistency = on
。
在GTID模式下,每个事务都会和一个GTID对应。
在GTID模式下,备库B要设置为新主库A’的从库:
CHANGE MASTER TO
MASTER_HOST=$host_name
MASTER_PORT=$port
MASTER_USER=$user_name
MASTER_PASSWORD=$password
master_auto_position=1
可以发现,现在不需要指定参数了。
实例A‘的GTID集合记为set_a,实例B的GTID集合记为set_b。
在实例B上执行start slave命令,取binlog:
1、实例B指定主库A’,基于主备协议建立连接
2、实例B把set_b发给主库A‘
3、实例A’算出set_a与set_b的差集,也就是所有存在于set_a但是不存在于set_b的GTID集合。
判断A‘本地是否包含了这个差集需要的binlog事务。
如果不包含,表示A’已经把实例B需要的binlog删除了,直接返回错误
如果确认全部包含,A‘从自己的binlog文件里面,找出第一个不在set_b的事务,发给B
4、从这个事务开始,往后读文件,按顺序取binlog发给B去执行
也就是说,基于GTID的主备关系里,系统认为只要建立主备关系,就必须保证主库发给备库的日志是完整的。如果备库要求的日志不存在,主库就拒绝把日志发给备库。
基于位点的主备协议,是由备库决定的,备库指定位点,主库顺着位点取log,不做日志完整性判断。