MySQL 高可用基础教程之一主从复制方案介绍

大纲

主从复制概述

主从复制用途

  • 实时灾备:用于故障切换(高可用)
  • 数据备份:避免影响业务(高可用)
  • 读写分离:提供查询服务(读扩展)

主从复制原理

  • 主从复制的原理
    • (1) Master 节点将数据的改变都记录到二进制 Binlog 日志中,只要 Master 上的数据发生改变,则将其改变写入二进制日志。
    • (2) Salve 节点会在一定时间间隔内对 Master 二进制日志进行探测,判断其是否发生改变,如果发生改变,则启动一个 I/O 线程请求 Master 二进制事件。
    • (3) 同时 Master 节点会为每个 I/O 线程启动一个 Binlog Dump 线程,用于向其发送二进制事件,让 Slave 节点保存至本地的 Relay-Log (中继日志)中。
    • (4) Slave 节点启动 SQL 线程从 Relay-Log (中继日志)中读取二进制日志,并在本地重放,使得其数据和 Master 节点的保持一致。
    • (5) 最后 Slave 节点的 I/O 线程 和 SQL 线程将进入睡眠状态,等待下一次被唤醒。

  • 主从复制的重点
    • (1) Slave 节点会启动两个线程,分别是 I/O 线程和 SQL 线程。
    • (2) Slave 节点的 I/O 线程会去请求 Master 节点的 Binlog,并将得到的 Binlog 写入本地的 Relay-Log(中继日志)文件中。
    • (3) Master 节点会启动一个 Binlog Dump 线程,用来给 Slave 节点的 I/O 线程传 Binlog。
    • (4) Slave 节点的 SQL 线程会读取本地 Relay-Log(中继日志)文件中的日志,并解析成 SQL 语句逐一执行。

主从复制方式

  • 基于语句的复制: 在主服务器执行的 SQL 语句,在从服务器也执行同样语句,这是 MySQL 默认采用的复制方式。
  • 基于行的复制: 将改变的数据复制给从服务器,而不是让 SQL 语句在从服务器上执行一遍,从 MySQL 5.0 版本开始支持。
  • 混合类型的复制: 默认采用基于 SQL 语句的复制(效率较高),一旦发现基于语句的方式无法精确地复制时,就会采用基于行的复制。

主从复制类型

异步复制

异步复制是 MySQL 默认使用的复制类型。

特别注意

异步复制不能保证 Slave 节点一定能接收到 Binlog 日志,即无法保证数据的一致性,但执行效率是最高的。

整体流程

半同步复制

从 MySQL 5.5 版本开始,MySQL 可以让 Master 节点在某一个时间点等待 Slave 节点的 ACK 消息,接收到 ACK 消息后才进行事务提交,这就是半同步复制。半同步复制可以保证至少有一个 Slave 节点的 Relay Log(中继日志)是完整的数据,即对数据一致性有一定的保障,但执行效率较慢。

  • 半同步复制在事务提交过程中增加了一个延迟,即在提交事务时,在客户端接收到查询结束反馈前必须保证二进制日志已经传输到一台 Slave 节点上。
  • 半同步不会阻塞 Master 节点上的事务提交,只有通知客户端被延迟了。
  • Slave 节点在接收到事务后发送 ACK 消息,而不是完成事务后再发送。
  • 如果 Slave 节点一直没有回应,会超时自动切换为异步复制模式。

整体流程

半同步复制插件

MySQL 的半同步复制是以插件的形式提供的,因此在使用之前需要安装对应的插件,如下所示:

1
2
3
4
5
6
7
8
# 主库安装半同步复制插件
mysql> install plugin rpl_semi_sync_master soname 'semisync_master.so';

# 从库安装半同步复制插件
mysql> install plugin rpl_semi_sync_slave soname 'semisync_slave.so';

# 查看已安装的插件
mysql> show plugins;

半同步复制大坑

在极端情况下,半同步复制也无法保证数据的一致性(至少有一个 Slave 节点的数据是完整的),如下图所示:

为了避免出现上述图中数据不一致的问题,强烈建议使用 SET rpl_semi_sync_master_wait_point=AFTER_SYNC 开启了 MySQL 的增强半同步,从 5.7 版本开始默认就是开启的。特别注意,MySQL 5.7 之前的旧版本默认是使用 AFTER_COMMIT(传统半同步)。

MHA 与半同步复制是绝配

MHA 高可用架构非常适合搭配半同步复制一起使用,详细介绍请看 这里 的教程。

全同步复制

全同步复制属于主从强一致方案,对 Binlog 有一定的要求,且执行效率最慢。MySQL 5.7.17 版本开始引入了组复制技术,因此全同步复制可以配合 MGR 高可用架构 一起使用。MGR 中的组复制协议如下图所示:

主从复制进阶

并行复制

MySQL 从 5.6 版本开始引入了并行复制功能(即并行复制多个库),用于改善复制延迟的问题。这是因为 Slave 节点只有一个 SQL 线程,当主库写压力大时,复制很可能会延迟。

5.6 版本并行复制

MySQL 5.6 版本仅支持基于库的并行复制,也就是多个线程分别执行不同库的复制操作,互不干扰。值得一提的是,单库多表的复制效率并没有提升。

5.7 版本并行复制

MySQL 5.7 版本支持基于组提交的并行复制,不再有库的并行复制限制(即支持单库多表的并行复制)。当事务提交时,通过在主库上的二进制日志中添加组提交信息,并将在单个操作中写入到二进制日志中。如果多个事务能同时提交成功,那么它们意味着没有冲突,因此可以在 Slave 节点上并行执行。InnoDB 事务提交采用的是两阶段提交模式。一个阶段是 Prepare,另一个阶段是 Commit。MySQL 5.7 版本的并行复制基于一个前提,即所有已经处于 Prepare 阶段的事务,都是可以并行提交的。在 MySQL 5.7 版本中,其设计方式是将组提交的信息存放在 GTID 中。为了避免用户没有开启 GTID 功能,MySQL 5.7 又引入了称之为 Anonymous_Gtid 的二进制日志 Event 类型,即日志中具有相同的 last_committed,表示这些事务都在一组内。

8.0 版本并行复制

MySQL 8.0 版本支持基于写集(write-set)的并行复制。有一个集合变量来存储事务修改的记录信息(主键哈希值),所有已经提交的事务所修改的主键值经过 Hash 后都会与那个变量的集合进行对比,来判断改行是否与其冲突,并以此来确定依赖关系,没有冲突即可并行,Row 级别的粒度,类似于之前的表锁行锁差异,效率会更高。

各版本的并行复制总结

  • MySQL 不同版本的主从复制区别

    • 在 MySQL 5.5 版本及之前
      • 从库的 SQL 线程为单线程,只支持复制单个库,不支持并行复制多个库。
    • 从 MySQL 5.6 版本开始
      • 引入了多线程从库(Multi-Threaded Slave,MTS)功能(即并行复制),支持并行复制多个库,允许多个 SQL 线程并行应用 Relay Log 中来自不同数据库的事务。也就是说,如果数据库 A 和数据库 B 是互相独立的,那么它们的事务在从库就可以并行执行。
      • 其底层实现方式是:从库会为每个库(Schema)分配一个 SQL 执行队列,多个队列并行执行不同库的事务。同一库内的事务依然串行执行,不同库之间的事务可以并行执行,从而提升复制性能。
    • 从 MySQL 5.7 版本开始
      • 增强了多线程从库(MTS,并行复制)的功能,引入基于组提交(Group Commit)的并行复制机制。
      • 支持在同一个库内的不同表上进行并行复制,前提是这些事务在主库上是并行提交的。
      • 也就是说,只要这些事务在主库上是并行提交的,那么从库就可以并行回放(称为 Logical Clock 模式),突破了此前 “只能跨库并行复制” 的限制。
    • 从 MySQL 8.0 版本开始
      • 进一步优化多线程从库(MTS,并行复制)的功能,支持同一个库内同一张表的并行复制。
      • 默认启用基于写集合的复制(Write Set-Based Replication),可以检测事务是否访问相同的行,若不冲突即可并行回放,不再仅依赖库或组提交(Group Commit)。
  • MySQL 不同版本的主从复制总结

MySQL 版本 I/O 线程 SQL 线程并行能力主要特性 / 说明
5.5 及之前单线程单线程复制完全串行,延迟容易积累
5.6单线程多线程(Multi-Threaded Slave,MTS)跨库并行:不同库的事务可以并行执行,同库的事务仍然串行执行适合多库场景提升复制效率,通过 slave_parallel_workers 控制 SQL 线程数
5.7单线程多线程增强跨库并行 + 同库事务组并行(Group Commit / Logical Clock)单库并行复制能力提升,减少主从延迟,适用于高并发场景
8.0单线程多线程增强通过检测事务写集(Write Set)冲突决定是否并行执行,不再仅依赖库或组提交(Group Commit)并行度更高,充分利用多核 CPU,复制性能和一致性大幅提升

日志点与 GTID 复制

MySQL 主从复制支持基于日志点和基于 GTID 两种方式:

  • 基于日志点(binlog file & position)复制

    • Slave 通过指定 binlog 文件名和日志偏移量(log position)从 Master 拉取增量日志。
    • 配置主从复制时必须手动指定 MASTER_LOG_FILEMASTER_LOG_POS
    • 当发生主库切换(比如 MMM、MHA 高可用架构)时,需要人工或工具重新指定正确的 binlog 日志点。
    • 适用于未开启 GTID 的传统复制场景。
  • 基于 GTID(Global Transaction ID)复制

    • GTID = source_id:transaction_id,表示一次事务的全局唯一编号。
    • Slave 根据自己已经执行的 GTID 集合,自动从 Master 补齐缺失的事务,不再需要人工设置日志点。
    • 主从切换时,Slave 会自动找到正确的继续复制点,大幅简化高可用架构切换。
    • 主要用于 MHA、Orchestrator 等现代高可用方案,推荐在新项目中使用。

主从复制方式选择

如何是为了兼容旧版本的 MySQL 和 MMM,建议选择基于日志点复制,其他业务场景可以选择基于 GTID 复制。

主从复制架构

MySQL 常见的主从复制架构如下:

提示

在上述介绍的主从复制架构中,中小型企业使用最多的是 一主多从。其中的 双主环形多主 架构实现起来都比较复杂,早期一般是大型互联网公司才会使用。

主从复制常见问题

主从复制高延迟

MySQL 主从复制高延迟的常见原因:

  • 网络问题
    • 如主库或者从库的带宽打满,或主从之间的网络延迟很大,导致主库上的 Binlog 没有全量传输到从库,造成了延迟。
  • 机器性能
    • 从库的硬件性能较差,比如主库使用 SSD 硬盘,而从库使用 SATA 硬盘。
  • 从库高负载
    • 有很多业务会在从库上做统计(比如报表),把从库服务器搞成高负载,从而造成从库延迟很大的情况。
  • 大事务
    • 比如在 RBR 模式下,执行带有大量的 Delete 操作,这种情况可以通过查看 processlist 相关信息,以及使用 mysqlbinlog 查看 Binlog 中的 SQL 就能快速进行排查。
  • 锁冲突
    • 锁冲突问题也可能导致从库的 SQL 线程执行慢,比如从库上执行一些 select ... for update 的 SQL,或者使用了 MyISAM 存储引擎等。

MySQL 主从复制高延迟的优化方案:

  • 使用分库降压
    • 比如,单个主库的写并发约 2000 TPS,将单个主库水平拆成 4 个主库后,每个主库写并发约 500 TPS,单库写压力大幅下降,主从延迟通常会显著降低并可控(但并非绝对 “可忽略”,仍需监控与告警)。
  • 使用并行复制
    • 开启 MySQL 的并行复制(5.6 版本支持跨库并行、5.7 版本支持同库不同表并行复制)能够提升主从同步的吞吐,但对单库 / 单表热点写入(例如单库写并发 2000 QPS)收益有限;这符合 “二八法则”,少数热点订单表承担了绝大部分写入,复制并行度受到同库 / 同表事务冲突与顺序性的限制。
  • 代码层避免 “写后立查”
    • 重写关键业务代码,插入后直接依据返回的主键 / 受影响行数 / 版本号来执行业务更新,尽量避免为了确认插入而立刻查询(尤其是查从库),以减少对复制延迟的敏感性。
  • 必须 “写后可读” 的请求走主库
    • 若业务确实要求 “插入后立刻可查询并据此继续操作”,对这类查询强制直连主库(或做会话级主库粘性)。但不宜广泛使用,否则等于放弃读写分离的意义。
  • 优化硬件和网络环境
    • 采用性能更高的服务器,比如 4U 比 2U 性能明显要更好。数据库存储使用 SSD 硬盘、磁盘阵列或者 SAN 存储网络,提升随机写的性能。
    • 确保主从数据库之间的网络连接稳定,高带宽,低延迟。如果可能,考虑将主从数据库放置在同一个局域网内,并且是万兆环境,以减少网络传输延迟。

最佳实践

  • 全同步复制相对于半同步复制来说,数据的一致性更高,但性能代价也更高。具体选择哪种复制,取决于对数据一致性和性能的需求权衡。
  • 在关键业务场景下(对数据一致性的要求较高),可以更倾向于选择全同步复制,而在某些读写分离的场景下,可以考虑半同步复制。

参考资料