菜鸟笔记
提升您的技术认知

MySQL如何保证高可用

正常情况下,只要主库执行更新生成的所有binlog,都可以传到备库并被正确执行,备库就能达到跟主库一样的状态,这就是最终一致性。

这里我们再放上MySQL主备切换的流程图:

1. 主备延迟

主备切换可能是一个主动的运维动作,如软件升级等。也可能是被动操作,如主库所在机器掉电等。

主备同步的过程通常有以下三步:

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

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

我们可以在备库上执行show slave status命令,会返回seconds_bebind_master,用于表示当前备库延迟了多少秒

实际上,在网络正常的时候,日志从主库传递给备库的时间是很短的。主要延迟的时间是在备库消费中转日志(relay log)速度,这个速度是要比主库生产binlog的速度要慢的。

2. 主备延迟的来源

第一种可能,有些部署条件下,备库所在机器的性能要比在主库所在的机器性能差

因为习惯性认为备库的请求不好,没必要用那么好的机器。但是在发生主备切换的时候就比较麻烦。

第二种可能,备库的压力比较大

因为大家比较重视主库,可能就会忽略备库的压力控制。备库上的查询可能耗费了大量的CPU资源,影响了同步速度,造成主备延迟。

解决办法可以采用一主多从的架构,让多个从库来分担压力。

第三种可能,大事务

大事务很容易理解,因为主库上必须等待事务执行完成才会写入binlog,再传给备库。所以,如果一个事务在主库上面执行10分钟,那这个也会导致从库上延迟10分钟。

解决办法就是尽量避免一个事务执行过长的时间。

3. 主备切换策略-可靠性优先策略

可靠性优先策略的主备切换流程主要有以下几步:

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

具体流程如下图所示(SBM代表seconds_bebind_master):

可以看到,这个切换流程中是有不可用时间的。因为在步骤2之后,主库A和备库B都处于readonly状态,也就是说系统处于不可写状态,直到步骤5才恢复。正是因为这个原因,所以在第一步菜肴确保主从延迟的时间尽可能短。

4. 主备切换策略-可用性优先策略

因为可靠性优先策略存在不可用时间,所以出现了可用性优先策略,这个方法几乎可以将不可用时间降为0。

可用性优先策略,就是把步骤4、5调整到最开始执行。也就是说不等主备数据同步,直接连接切到备库B,且让备库B可以读写。

但是这种可用性优先策略可能会存在主备不一致的情况,如下的例子,我们先建立下面这张表:

mysql> create table t(
    id int(11) unsigned not null auto_increment,
    c int(11) unsigned default null,
    primary key(id)
)engine=InnoDB;

insert into t(c) values(1),(2),(3)

接下来要进行的业务操作是要继续在这张表上插入两条数据:

insert into t(c) values(4);
insert into t(c) values(5);

假设,主库上其他的数据表有大量的更新,导致主备延迟5秒,在插入一条c=4的语句后,进行主备切换。

在使用可用性优先策略,且binlog_format=mixed是进行主备切换,过程如下图:

我们分析下上面的流程:

  1. 步骤1中,主备延迟5秒;
  2. 步骤2中,向库A插入记录(4,4);
  3. 步骤3中,发生主备切换,向库B插入记录(4,5)
  4. 步骤4中,因为库B发生更新,也要同步binlog到库A
  5. 步骤5中,库A和库B分别执行relay log。库A插入记录(5,5),库B插入记录(5,4)

从上面的步骤执行完的结果来看,库A和库B出现了不一致。这是因为可用性优先策略流程导致的。这样就就会有两行数据不一致。

那如果设置binlog_format=row,会发生什么情况呢?如下图所示:

我们分析下上面的流程:

  1. 步骤1中,主备延迟5秒;
  2. 步骤2中,向库A插入记录(4,4);
  3. 步骤3中,发生主备切换,向库B插入记录(4,5)
  4. 步骤4中,因为库B发生更新,也要同步binlog到库A
  5. 步骤5中,库A和库B分别执行relay log,A库试图插入(4,5),B库试图插入(4,4)。

因为binlog_format=row会记录插入行的具体值,所以最后进行主备同步的时候,会报错duplicate key error。这样就就会有一行数据不一致。

6. 结论

综上,我们可以得到以下结论:

  1. 使用row格式的binlog,数据不一致问题更容易被发现。而使用mixed或者statement的binlog,数据可能悄悄就不一致了
  2. 主备切换的可用性优先策略会导致数据不一致。因此,大多数情况下建议使用可靠性优先策略