共计 4471 个字符,预计需要花费 12 分钟才能阅读完成。
导读 | 为了保证高可用,之前在测试环境部署了一套 MySQL 双主模式,当一个主库服务出现异常,可以将流量切到另外一个主库,两个主库之间相互同步数据。 |
本文主要内容如下:
为了保证高可用,之前在测试环境部署了一套 MySQL 双主模式,当一个主库服务出现异常,可以将流量切到另外一个主库,两个主库之间相互同步数据。
双主模式的原理图如下:
但是经常出现数据冲突的问题,于是我们又把双主模式改为了主从读写分离模式。主库作为读写库,再加上一个从库用来做 I/O 密集型的任务(如大量的数据统计操作)。如下图所示:
另外从库复制的模式采用位点的方式:指定 binlog 文件和 binlog 位置,这样从库就知道了复制的起始位置。(下文会讲解这种方式)
虽然改为了主从模式,但依旧遇到了些问题:
问题 1:从库 B 复制数据时,出现了主键冲突问题,导致同步失败,从库停止复制。猜测因主库配置的 binlog 日志的格式为 mixed,从库同步时出现不一致的情况。
问题 2:从库 B 停止复制后,导致很多数据未同步到从库,出现主从大量数据不一致的情况。
问题 3:从库 B 想要恢复复制,必须先解决同步失败的问题才能恢复。排查难度较大,耗时。
问题 4:从库 B 恢复时,必须知道同步位点,也就是从哪个 binlog 文件和 binlog 位置断开复制的,且即使找到了位点,也不是精确的。
问题 5:从库 B 因同步异常导致停止复制到恢复复制这段期间,主库 A 自动清理了几天前的 binlog 日志,而这些日志从库 B 还未来得及同步,进而导致再次同步失败。
问题 6:主从存在同步延迟。
这篇我们来探讨下问题 4 和问题 6。
其中问题 4 是一个比较头疼的问题,我们一般是通过查看从库 B 当前的同步状态拿到同步位点,然后设置同步位点后。但是重新启动同步的时候又会出现同步异常,比如从库 B 可能会出现 Duplicate entry‘id_of_R’for key‘PRIMARY’错误,提示出现了主键冲突,然后停止同步。
为了减少位点同步引入的复杂度,我们切换成了 GTID 模式。
对于问题 6,本篇也仅限于探讨如何观察延迟,对于如何减少延迟不在本篇探讨范围之内。
接下来我们来展开看下位点同步的痛点。
为了更清晰地理解主从采用位点同步的原理,这里有一个原理图:
当我们使用位点同步的方式时,两种场景下的操作步骤比较复杂。
痛点 1:首次开启主从复制的步骤复杂
第一次开启主从同步时,要求从库和主库是一致的。
找到主库的 binlog 位点。
设置从库的 binlog 位点。
开启从库的复制线程。
痛点 2:恢复主从复制的步骤复杂
找到从库复制线程停止时的位点。
解决复制异常的事务。无法解决时就需要手动跳过指定类型的错误,比如通过设置 slave_skip_errors=1032,1062。当然这个前提条件是跳过这类错误是无损的。(1062 错误是插入数据时唯一键冲突;1032 错误是删除数据时找不到行)
不论是首次开启同步时需要找位点和设置位点,还是恢复主从复制时,设置位点和忽略错误,这些步骤都显得过于复杂,而且容易出错。所以 MySQL 5.6 版本引入了 GTID,彻底解决了这个困难。
GTID 的全称是 Global Transaction Identifier,全局事务 ID,当一个事务提交时,就会生成一个 GTID,相当于事务的唯一标识。
GTID 长这样:
c5d74746-d7ec-11ec-bf8f-0242ac110002:1
结构:
GTID=server_uuid:gno
server_uuid 是一个实例第一次启动时自动生成的,是一个全局唯一的值;
gno 是一个整数,初始值是 1,每次提交事务的时候分配给这个事务,并加 1。
每个 MySQL 实例都维护了一个 GTID 集合,用来对应“这个实例执行过的所有事务”。
修改主库和从库的配置文件:
#GTID:
gtid_mode=on
enforce_gtid_cnotallow=on
从库配置同步的参数:
CHANGE MASTER TO
MASTER_HOST=$host_name
MASTER_PORT=$port
MASTER_USER=$user_name
MASTER_PASSWORD=$password
master_auto_positinotallow=1
其中 master_auto_position 标识主从关系使用的 GTID 协议。
相比之前的配置,MASTER_LOG_FILE 和 MASTER_LOG_POS 参数已经不需要了。
GTID 同步的原理图。
GTID 方案:主库计算主库 GTID 集合和从库 GTID 的集合的差集,主库推送差集 binlog 给从库。
当从库设置完同步参数后,主库 A 的 GTID 集合记为集合 x,从库 B 的 GTID 集合记为 y。从库同步的逻辑如下:
GTID 同步方案和位点同步的方案区别是:
上面提到的问题 6 是主从读写分离后,从库复制存在延迟,接下来我们来探讨下如何观察主从延迟多少的问题。
方案一:判断从库的同步状态参数 seconds_behind_master 是否为 0。(不准确)
方案二:对比位点确保主备无延迟。
方案三:对比 GTID 集合确保主备无延迟。
可以在从库上执行 slow slave status 命令来看执行结果里面的 seconds_behind_master 参数的值,如下图所示,Seconds_Behind_Master 等于 0
Seconds_Behind_Master 的单位是秒,所以精度不准确。
所以为了保证查询的数据是和主库一致的,就需要先判断 seconds_behind_master 是否已经等于 0,如果不等于 0,就必须等到这个参数变为 0 才能执行查询请求。
可以通过查看从库当前的同步位点来确认从库同步是否有延迟。下图是在从库上执行 show slave status \G命令后的结果:
Master_Log_File 和 Read_Master_Log_Pos 这两个参数合起来表示的是读到的主库的最新位点,第一参数是代表读取到了哪个文件,第二个是读取到的文件的位置。
Relay_Master_Log_File 和 Exec_Master_Log_Pos,这两个参数合起来表示的是从库执行的最新位点。
如果红色框起来的两个参数:Master_Log_File 和 Relay_Master_Log_File 相等,则说明从库读到的最新文件和主库上生成的文件相同,这里都是 mysql-bin.000934。
如果蓝色框起来的两个参数 Read_Master_Log_Pos 和 Exec_Master_Log_Pos 相等,则说明从库读到的日志文件的位置和从库上执行日志文件的位置相同,这里都是 59521082。
当上面两组参数都相等时,则说明没有延迟。
方案三是对比 GTID 集合。首先我们在从库上执行 show slave status \G 来查看 GTID 集合。
如下图所示:
Master_UUID 表示当前连接的主库的 ID。
Auto_Position: 1 表示主备使用了 GTID 协议。
Retrieved_Gtid_Set 表示从库收到的所有日志的 GTID 集合。
Executed_Gtid_Set 表示从库已经执行完成的 GTID 集合。
如果 Executed_Gtid_Set 集合是包含 Retrieved_Gtid_Set,则表示从库接收到的日志已经同步完成。
比如上图中 Retrieved_Gtid_Set 值为
c5d74746-d7ec-11ec-bf8f-0242ac110002:1
-87323
前面一段是主库 id,后面一段 1-87383 是 GTID 范围。而 Executed_Gtid_Set 的值有两个集合
7083ae1f-d7ef-11ec-a329-0242ac110002:1-2,
c5d74746-d7ec-11ec-bf8f-0242ac110002:1
-87323
Executed_Gtid_Set 的第二个集合和第一个集合完全一致,第一个集合 id 和 集合范围是上次同步另外一个主库的记录。这里说明从库已经和当前主库同步完成了。
方案二对比位点和方案三的 GTID 比对都要比方案一的 seconds_behind_master 更准确。但是还是没有达到精确的程度,需要配合半同步复制(semi-sync replication)才能达到。
小结:本篇通过 GTID 的方式更好地实现了主从节点的同步,以及如何观察主从同步的延迟。
www.passjava.cn
https://time.geekbang.org/column/article/77636
高性能 MySQL 第四版
千金良方:MySQL 性能优化金字塔法则
8 年互联网开发经验,擅长微服务、分布式、架构设计。目前在一家大型上市公司从事基础架构和性能优化工作。
InfoQ 签约作者、蓝桥签约作者、阿里云专家博主、51CTO 红人。