正常情况下,只要主库执行更新生成的所有binlog,都可以被传到备库并被正确地执行,备库就能达到跟主库一致的状态,这就是最终一致性,而MySQL要提供的高可用能力,只有最终一致性是不够的。
主备延迟
主备切换可能是一个主动运维动作,比如软件升级、主库所在机器按计划下线等,也可能是被动操作,比如主库所在机器掉电。
先看主动切换的场景。首先介绍同步延迟的概念,与数据同步有关的时间点主要包括三个:
-
主库A执行完成一个事务写入binlog,将这个时刻记为T1;
-
之后传给备库B,把备库B接收完这个binlog的时刻记为T2;
-
备库B执行完这个事务,将这个时刻记为T3。
主备延迟指的是,同一个事务主备库执行完成的时间差,即T3-T1。
可以在备库上执行show slave status
命令,其返回结果里会显示seconds_behind_master,这个值计算的就是T3-T1。
如果主备库机器的系统时间设置不一致,会不会导致主备延迟的值不准呢?答案是不会的,备库连接到主库的时候,会通过执行SELECT UXIN_TIMESTAMP()函数来获得当前主库的系统时间,如果此时发现两库系统时间不一致,备库执行seconds_behind_master计算时会扣除该差值。
网络正常时,T2-T1是很短的,主备延迟的主要来源是备库接收完binlog和执行完这个事务之间的时间差。所以,主备延迟最直接的表现是,备库消费中转日志(relay log)的速度,比主库生产binlog的速度要慢。
主备延迟的来源
首先,有些部署条件下备库所在机器性能要比主库所在的机器性能差。
有些人部署时想着反正备库没有请求,可以用差一点的机器,但实际上更新过程也会触发大量读操作,当备库主机上的多个备库都在争抢资源时,就可能导致主备延迟。
现在这种部署比较少了,因为主备可能随时切换,所以主备库一般选用相同规格的机器,并且做对称部署。
第二,备库的压力大。
备库一般用来提供一些读能力,如果大量查询,备库上的查询可能耗费大量CPU资源,影响同步速度,造成主备延迟。
这种情况一般的处理方法:
-
一主多从,让多个从库分担读的压力;
-
通过binlog输出到外部系统,比如Hadoop,让外部系统提供统计类查询能力。
第三是大事务。
主库上必须等事务执行完才会写入binlog再传给备库。那么,如果一个主库上的语句执行10分钟,意味着这个事务可能会导致从库延迟10分钟。常见的大事务有一次性用delete删除大量数据、大表DDL等。
第四是备库的并行复制能力,这个下篇文章再介绍。
由于主备延迟的存在,所以在主备切换的时候,就相应的有不同的策略。
可靠性优先策略
上篇文章提到的双M结构:
双M结构下,状态1切换到状态2的详细过程:
-
判断备库B现在的seconds_behind_master,如果小于某个值(如5秒)继续下一步,否则持续重试这一步;
-
将主库A改成只读状态;
-
判断备库B的seconds_behind_master,直到这个值变成0;
-
将备库B改成可读写状态;
-
把业务请求切换到备库B。
我们暂时把这个切换流程称为可靠性优先流程。
可以发现,这个切换流程中,在步骤2之后,主备库都处于readonly状态,即系统处于不可写状态,直到步骤5完成后才能恢复。在这个不可用状态中,步骤3比较耗时,这也是为什么在步骤1先确保seconds_behind_master足够小,不然不可用时间可能会很长。
系统的不可用时间,是由数据可靠性优先的策略决定的。也可以选择可用性优先的策略,把这个不可用的时间几乎降为0。
可用性优先策略
如果强行把步骤45调整到最开始执行,即不等主备数据同步,直接把连接切到备库B且让备库B可以读写,那么系统几乎就没有不可用时间了。
暂时把这个切换流程称为可用性优先流程,该切换流程的代价是可能出现数据不一致的情况。
假设有一个表t:
sql
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);
初始化数据后,主备库上都是3行数据,继续插入:
sql
insert into t(c) values(4);
insert into t(c) values(5);
假设现在主库上其他的数据表有大量的更新,导致主备延迟达到5秒,在插入一条c=4
的语句后,发起了主备切换。
当使用可用性优先策略,且binlog_format=mixed
时切换流程如下:
图中的SBM,是seconds_behind_master参数的简写。
-
step 2中,主库A执行完insert插入(4,4),之后开始进行主备切换;
-
step 3中,由于主备之间有5秒延迟,所以备库B还没来得及应用插入
c=4
中转日志,就开始接收客户端插入c=5
的命令; -
step 4中,备库B插入一行数据(4,5),并把这个binlog发给主库A;
-
step 5中,备库B执行插入
c=4
的日志,插入一行数据(5,4),而前面的语句传到主库A,会在主库插入(5,5)。
因此最后出现主备不一致的情况。
当使用可用性优先策略,且binlog_format=row
时切换流程如下:
由于row格式在记录binlog时会记录新插入的行的所有字段值,所以最后只会有一行不一致,且两边的主备同步的应用线程会报错duplicate key error并停止。即备库B的(5,4)和主库A的(5,5)两行数据都不会被对方执行。
从上面的分析中,可以看到一些结论:
-
使用row格式的binlog时,数据不一致问题更容易被发现,而使用mixed或者statement格式的binlog,可能数据不一致而没有发现;
-
主备切换的可用性优先策略会导致数据不一致,因此大多数情况都建议使用可靠性优先策略。
但也有情况数据的可用性优先级更高。比如一个库的作用是记录操作日志,这时如果数据不一致可以通过binlog来修补,短暂的不一致不会引发业务问题,而业务系统又依赖这个日志,如果这个库不可写会导致线上的业务操作无法执行。这时就需要先强行切换,事后再补数据的策略。
当然也能改进,比如让业务逻辑不要依赖于这类日志的写入,那就又可以使用可靠性优先策略。
最后总结:在满足数据可靠性的前提下,MySQL高可用系统的可用性,是依赖于主备延迟的。延迟的时间越小,在主库故障的时候,服务恢复需要的时间就越短,可用性就越高。