MySQL主备的基本原理
在状态1中,客户端的读写都直接访问节点A,而节点B是A的备库,只是将A的更新都同步过来,到本地执行。这样可以保持节点B和A的数据是相同的。
当需要切换的时候,就切成状态2。这时候客户端读写访问的都是节点B,而节点A是B的备库。
主备完整流程图
一个事务日志同步的完整过程:
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读取中转日志,解析出日志里的命令,并执行。
为什么会有mixed格式的binlog?
1.有些statement格式的binlog可能会导致主备不一致,所以要使用row格式。
2.row格式的缺点是,很占空间。比如你用一个delete语句删掉10万行数据,用statement的话就是一个SQL语句被记录到binlog中,占用几十个字节的空间。但如果用row格式的binlog,就要把这10万条记录都写到binlog中。这样做,不仅会占用更大的空间,同时写binlog也要耗费IO资源,影响执行速度。
3.恢复数据
所以,MySQL就取了个折中方案,也就是有了mixed格式的binlog。mixed格式的意思是,MySQL自己会判断这条SQL语句是否可能引起主备不一致,如果有可能,就用row格式,否则就用statement格式。
循环复制问题
binlog的特性确保了在备库执行相同的binlog,可以得到与主库相同的状态。
因此,我们可以认为正常情况下主备的数据是一致的。也就是说,A、B两个节点的内容是一致的。
节点A和B之间总是互为主备关系。这样在切换的时候就不用再修改主备关系。
业务逻辑在节点A上更新了一条语句,然后再把生成的binlog 发给节点B,节点B执行完这条更新语句后也会生成binlog。
那么,如果节点A同时是节点B的备库,相当于又把节点B新生成的binlog拿过来执行了一次,然后节点A和B间,会不断地循环执行这个更新语句
解决:
MySQL在binlog中记录了这个命令第一次执行时所在实例的server id。因此,我们可以用下面的逻辑,来解决两个节点间的循环复制的问题:
1.规定两个库的server id必须不同,如果相同,则它们之间不能设定为主备关系;
2.一个备库接到binlog并在重放的过程中,生成与原binlog的server id相同的新的binlog;
3.每个库在收到从自己的主库发过来的日志后,先判断server id,如果跟自己的相同,表示这个日志是自己生成的,就直接丢弃这个日志。
按照这个逻辑,如果我们设置了双M结构,日志的执行流就会变成这样:
1.从节点A更新的事务,binlog里面记的都是A的server id;
2.传到节点B执行一次以后,节点B生成的binlog 的server id也是A的server id;
3.再传回给节点A,A判断到这个server id与自己的相同,就不会再处理这个日志。
什么情况下双M结构会出现循环复制?
一种场景是,在一个主库更新事务后,用命令set global server_id=x修改了server_id。等日志再传回来的时候,发现server_id跟自己的server_id不同,就只能执行了。
另一种场景是,有三个节点的时候,如图7所示,trx1是在节点 B执行的,因此binlog上的server_id就是B,binlog传给节点 A,然后A和A’搭建了双M结构,就会出现循环复制。
这种场景做数据库迁移的时候会出现。
解决:
如果出现了循环复制,可以在A或者A’上,执行如下命令:
stop slave;
CHANGE MASTER TO IGNORE_SERVER_IDS=(server_id_of_B);
start slave;
这样这个节点收到日志后就不会再执行。过一段时间后,再执行下面的命令把这个值改回来。
stop slave;
CHANGE MASTER TO IGNORE_SERVER_IDS=();
start slave;
[资料来源]
1.Mysql实战45讲-丁奇