MySQL高可用之主备同步:MySQL是如何保证主备一致的_mysql 主备同步日志

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

🏆今日学习目标

🍀MySql是如何保证主备一致的
✅创作者林在闪闪发光
⏰预计时间30分钟
🎉个人主页林在闪闪发光的个人主页

 🍁林在闪闪发光的个人社区欢迎你的加入: 林在闪闪发光的社区

目录

一 什么叫主备同步

二 主备同步的好处

三 主备同步的实现原理

四. binlog的三种格式

 五. 为什么会有mixd格式的binlog

 六 常见的两种主备切换流程

M-S结构

双M结构

双M结构的循环复制问题


一 什么叫主备同步

主备同步也叫主从复制是MySQL提供的一种高可用的解决方案保证主备数据一致性的解决方案。在生产环境中会有很多不可控因素例如数据库服务挂了。为了保证应用的高可用数据库也必须要是高可用的。因此在生产环境中都会采用主备同步。在应用的规模不大的情况下一般会采用一主一备。

二 主备同步的好处

除了上面提到的数据库服务挂了能够快速切换到备库避免应用的不可用外采用主备同步还有以下好处

提升数据库的读并发性大多数应用都是读比写要多采用主备同步方案当使用规模越来越大的时候可以扩展备库来提升读能力。

备份主备同步可以得到一份实时的完整的备份数据库。

快速恢复当主库出错了比如误删表通过备库来快速恢复数据。对于规模很大的应用对于数据恢复速度的容忍性很低的情况通过配置一台与主库的数据快照相隔半小时的备库当主库误删表就可以通过备库和binlog来快速恢复最多等待半小时。

三 主备同步的实现原理

如下图展示的是基本的主备切换流程

在状态1中主库是A备库是B所以客户端的读写都直接方法节点A。由于节点B是节点A的备库所以备库B只是将A的更新都同步过来本地执行这样可以保证节点B和节点A的数据一致性。

如果发生主备切换就会从状态1变成状态2节点A成为备库节点B成为主库。

在状态1中虽然节点B没有被客户端直接方法但是还是建议将节点B备库设置成只读readonly模式主要有以下几个理由

避免某些服务访问了备库造成误操作
防止切换逻辑有bug比如切换过程中出现双写造成主备不一致
可以用readonly状态来判断节点的角色
注意readonly对于超级管理员是无效的而用于同步更新的线程就拥有超级权限所以是可以修改备库的。

接下来我们看下节点A到节点B的流程图

实际上备库B和主库A之间维持了个长连接主库A中有一个线程dump_thread专门用于服务和备库B的长连接。日志同步的完整过程如下

1.在备库B上通过change master命令设置主库A的相关信息以及要从哪个位置开始请求binlog
2.在备库B上执行start slave命令备库会启动两个线程即io_thread和sql_thread其中io_thread负责与主库通信
3.主库A校验完信息后根据备库B转过来的位置本地读取binlog传递给B
4.备库拿到binlog后写到本地文件称为中转日志relay log
5.sql_thread读取中转日志解析出命令并执行
 

四. binlog的三种格式

binlog的格式实际上由两种格式一种是statement一种是row。此外还有一种mixed格式实际上是前两种的混合。

为了方便解释几种日志格式的区别我们创建一个表并写入些数据。

mysql> create table t(
    id int(11) not null,
    a int(11) default null,
    t_modified timestamp not null default current_timestamp,
    primary key (id),
    key a(a),
    key t_modified (t_modified)
)ENGINE=InnoDB;

insert into t values(1,1,'2018-11-13')
insert into t values(2,2,'2018-11-12')
insert into t values(3,3,'2018-11-11')
insert into t values(4,4,'2018-11-10')
insert into t values(5,5,'2018-11-09')

然后我们对于这个表执行delete语句 

注意下面这个语句包含注释如果你用 MySQL 客户端来做这个实验的话要记得加 -c 参数否则客户端会自动去掉注释。 

mysql>delete from t /*comment*/ where a>=4 and t_modified <='2018-11-10' limit 1;

我们可以使用下面的命令来查看binlog中的内容 

mysql> show binlog events in 'master.000001'

 可以看到当binlog_format=statement时binlog里面记录的就是sql原文

 

现在我们来看一下图 3 的输出结果。

第一行 SET @@SESSION.GTID_NEXT='ANONYMOUS’你可以先忽略后面文章我们会在介绍主备切换的时候再提到
第二行是一个 BEGIN跟第四行的 commit 对应表示中间是一个事务
第三行就是真实执行的语句了。可以看到在真实执行的 delete 命令之前还有一个“use ‘test’”命令。这条命令不是我们主动执行的而是 MySQL 根据当前要操作的表所在的数据库自行添加的。这样做可以保证日志传到备库去执行的时候不论当前的工作线程在哪个库里都能够正确地更新到 test 库的表 t。
use 'test’命令之后的 delete 语句就是我们输入的 SQL 原文了。可以看到binlog“忠实”地记录了 SQL 命令甚至连注释也一并记录了。
最后一行是一个 COMMIT。你可以看到里面写着 xid=61。
为了说明 statement 和 row 格式的区别我们来看一下这条 delete 命令的执行效果图

 

可以看到运行这条 delete 命令产生了一个 warning原因是当前 binlog 设置的是 statement 格式并且语句中有 limit所以这个命令可能是 unsafe 的。

为什么这么说呢这是因为 delete 带 limit很可能会出现主备数据不一致的情况。比如上面这个例子

如果 delete 语句使用的是索引 a那么会根据索引 a 找到第一个满足条件的行也就是说删除的是 a=4 这一行
但如果使用的是索引 t_modified那么删除的就是 t_modified='2018-11-09’也就是 a=5 这一行。
由于 statement 格式下记录到 binlog 里的是语句原文因此可能会出现这样一种情况在主库执行这条 SQL 语句的时候用的是索引 a而在备库执行这条 SQL 语句的时候却使用了索引 t_modified。因此MySQL 认为这样写是有风险的

那么如果我把 binlog 的格式改为 binlog_format=‘row’ 是不是就没有这个问题了呢我们先来看看这时候 binog 中的内容吧。 

可以看到与 statement 格式的 binlog 相比前后的 BEGIN 和 COMMIT 是一样的。但是row 格式的 binlog 里没有了 SQL 语句的原文而是替换成了两个 eventTable_map 和 Delete_rows。

Table_map event用于说明接下来要操作的表是 test 库的表 t;
Delete_rows event用于定义删除的行为。
其实我们通过图 5 是看不到详细信息的还需要借助 mysqlbinlog 工具用下面这个命令解析和查看 binlog 中的内容。因为图 5 中的信息显示这个事务的 binlog 是从 8900 这个位置开始的所以可以用 start-position 参数来指定从这个位置的日志开始解析。

mysqlbinlog  -vv data/master.000001 --start-position=8900; 

 

从这个图中我们可以看到以下几个信息

1.server id 1表示这个事务是在 server_id=1 的这个库上执行的。
2.每个 event 都有 CRC32 的值这是因为我把参数 binlog_checksum 设置成了 CRC32。
3.Table_map event 跟在图 5 中看到的相同显示了接下来要打开的表map 到数字 226。现在我们这条 SQL 语句只操作了一张表如果要操作多张表呢每个表都有一个对应的 Table_map event、都会 map 到一个单独的数字用于区分对不同表的操作。
我们在 mysqlbinlog 的命令中使用了 -vv 参数是为了把内容都解析出来所以从结果里面可以看到各个字段的值比如@1=4、 @2=4 这些值。
4.binlog_row_image 的默认配置是 FULL因此 Delete_event 里面包含了删掉的行的所有字段的值。如果把 binlog_row_image 设置为 MINIMAL则只会记录必要的信息在这个例子里就是只会记录 id=4 这个信息。
5.最后的 Xid event用于表示事务被正确地提交了。


你可以看到当 binlog_format 使用 row 格式的时候binlog 里面记录了真实删除行的主键 id这样 binlog 传到备库去的时候就肯定会删除 id=4 的行不会有主备删除不同行的问题。
 

 五. 为什么会有mixd格式的binlog

从上面的描述中我们可以很清楚地看到statement和row格式的优缺点

statement格式节省空间只需要记录sql语句。但是可能会出现主备不一致的情况
row不会出现主备不一致的情况。但是格式十分消耗空间需要记录所有修改的行。

mixed格式的意思是MySQL会自己判断这条SQL语句是否可能引起主备不一致如果有可能就用row格式否则就用statement格式。 

所以线上的场景设置为statement格式肯定是不合理的至少要设置成mixed格式。

实际上现在越来越多都是使用row格式其中一个好处就是恢复数据

当执行delete语句后发现误删了直接将binlog中的信息转换成insert语句插入即可
当执行insert语句后发现错误插入了直接将binlog中的信息转换成delete语句插入即可
如果执行的是update语句binlog会记录修改前后的信息方面恢复

 六 常见的两种主备切换流程

M-S结构

M-S结构两个节点一个当主库、一个当备库不允许两个节点互换角色。

 

在状态1中客户端的读写都直接访问节点A而节点B是A的备库只是将A的更新都同步过来到本地执行。这样可以保持节点B和A的数据是相同的。

当需要切换的时候就切成状态2。这时候客户端读写访问的都是节点B而节点A是B的备库。

 

双M结构

双M结构两个节点一个当主库一个当备库允许两个节点互换角色。

节点A和B之间总是互为主备关系。这样在切换的时候就不用再修改主备关系。

双M结构的循环复制问题


在实际生产使用中多数情况是使用双M结构的。但是双M结构还有一个问题需要解决。

业务逻辑在节点A执行更新会生成binlog并同步到节点B。节点B同步完成后也会生成binlog。log_slave_updates设置为on表示备库也会生成binlog。

当节点A同时也是节点B的备库时节点B的binlog也会发送给节点A造成循环复制。

解决办法

设置节点的server-id必须不同不然不允许设置为主备结构
备库在接到binlog后重放时会记录原记录相同的server-id即谁产生即为谁的。
每个节点在接受binlog时会判断server-id如果是自己的就丢掉。


解决后的流程

业务逻辑在节点A执行更新会生成带有节点A的server-id的binlog。
节点B接受到节点A发过来的binlog并执行完成后会生成带有节点A的server-id的binlog。
节点A接受到binlog后发现是自己的就丢掉。死循环就在这里断掉了。
 

 

 

 

 

 

 

 

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: mysql