image.png

图 1 MySQL 主备切换流程 — 双 M 结构

主备延迟

与数据同步有关的时间点主要包括以下三个:

  1. 主库 A 执行完成一个事务,写入 binlog,我们把这个时刻记为 T1;
  2. 之后传给备库 B,我们把备库 B 接收完这个 binlog 的时刻记为 T2;
  3. 备库 B 执行完成这个事务,我们把这个时刻记为 T3。

主备延迟,就是同一个事务,在备库执行完成的时间和主库执行完成的时间之间的差值,也就是 T3-T1。

你可以在备库上执行 show slave status 命令,它的返回结果里面会显示 seconds_behind_master,用于表示当前备库延迟了多少秒。

备库连接到主库的时候,会通过执行 SELECT UNIX_TIMESTAMP() 函数来获得当前主库的系统时间。

需要说明的是,在网络正常的时候,日志从主库传给备库所需的时间是很短的,即 T2-T1 的值是非常小的。也就是说,网络正常情况下,主备延迟的主要来源是备库接收完 binlog 和执行完这个事务之间的时间差。

主备延迟的来源

  • 首先,有些部署条件下,备库所在机器的性能要比主库所在的机器性能差。
  • 备库的压力大
  • 大事务
    • delete 太多数据
    • 大表 DDL
  • 备库的并行复制能力

可靠性优先策略

在图 1 的双 M 结构下,从状态 1 到状态 2 切换的详细过程是这样的:

  1. 判断备库 B 现在的 seconds_behind_master,如果小于某个值(比如 5 秒)继续下一步,否则持续重试这一步;
  2. 把主库 A 改成只读状态,即把 readonly 设置为 true;
  3. 判断备库 B 的 seconds_behind_master 的值,直到这个值变成 0 为止;
  4. 把备库 B 改成可读写状态,也就是把 readonly 设置为 false;
  5. 把业务请求切到备库 B。

image.png

图 2 MySQL 可靠性优先主备切换流程

可用性优先策略

如果我强行把步骤 4、5 调整到最开始执行,也就是说不等主备数据同步,直接把连接切到备库 B,并且让备库 B 可以读写,那么系统几乎就没有不可用时间了.

  1. mysql> CREATE TABLE `t` (
  2. `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  3. `c` int(11) unsigned DEFAULT NULL,
  4. PRIMARY KEY (`id`)
  5. ) ENGINE=InnoDB;
  6. insert into t(c) values(1),(2),(3);

这个切换流程的代价,就是可能出现数据不一致的情况。

image.png

图 3 可用性优先策略,且 binlog_format=mixed

如果我还是用可用性优先策略,但设置 binlog_format=row:

  • 因为 row 格式在记录 binlog 的时候,会记录新插入的行的所有字段值,所以最后只会有一行不一致。而且,两边的主备同步的应用线程会报错 duplicate key error 并停止。

image.png

图 4 可用性优先策略,且 binlog_format=row

按照可靠性优先的思路,异常切换会是什么效果?

假设,主库 A 和备库 B 间的主备延迟是 30 分钟,这时候主库 A 掉电了,HA 系统要切换 B 作为主库。我们在主动切换的时候,可以等到主备延迟小于 5 秒的时候再启动切换,但这时候已经别无选择了。

image.png

图 5 可靠性优先策略,主库不可用

  • 采用可靠性优先策略的话,你就必须得等到备库 B 的 seconds_behind_master=0 之后,才能切换。
  • 系统处于完全不可用, B 不能启用只读, 因为数据可能还没有同步到 B

在满足数据可靠性的前提下,MySQL 高可用系统的可用性,是依赖于主备延迟的。延迟的时间越小,在主库故障的时候,服务恢复需要的时间就越短,可用性就越高