转载部分

MySQL-18.高可用的基础-主备切换逻辑

2019-08-16  本文已影响43人  王侦

MySQL 要提供高可用能力,只有最终一致性是不够的。

双 M 结构的主备切换流程图:


1.主备延迟

主备切换可能是一个主动运维动作,比如软件升级、主库所在机器按计划下线等,也可能是被动操作,比如主库所在机器掉电。

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

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

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

seconds_behind_master 的计算方法是这样的:

其实 seconds_behind_master 这个参数计算的就是 T3-T1。所以,可以用seconds_behind_master 来作为主备延迟的值,这个值的时间精度是秒。

问题:

Ans:

主备延迟最直接的表现是,备库消费中转日志(relay log)的速度,比主库生产binlog 的速度要慢。

2.主备延迟的来源

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

更新请求对 IOPS 的压力,在主库和备库上是无差别的。所以,做这种部署时,一般都会将备库设置为“非双 1”的模式。

但实际上,更新过程中也会触发大量的读操作。所以,当备库主机上的多个备库都在争抢资源的时候,就可能会导致主备延迟了。

当然,这种部署现在比较少了。因为主备可能发生切换,备库随时可能变成主库,所以主备库选用相同规格的机器,并且做对称部署,是现在比较常见的情况。

2.2 备库的压力大

一般的想法是,主库既然提供了写能力,那么备库可以提供一些读能力。或者一些运营后台需要的分析语句,不能影响正常业务,所以只能在备库上跑。

由于主库直接影响业务,大家使用起来会比较克制,反而忽视了备库的压力控制。结果就是,备库上的查询耗费了大量的 CPU 资源,影响了同步速度,造成主备延迟。

一般可以这么处理:

其中,一主多从的方式大都会被采用。因为作为数据库系统,还必须保证有定期全量备份的能力。而从库,就很适合用来做备份。

2.3 大事务

因为主库上必须等事务执行完成才会写入 binlog,再传给备库。所以,如果一个主库上的语句执行 10 分钟,那这个事务很可能就会导致从库延迟 10 分钟。

不要一次性地用 delete 语句删除太多数据。其实,这就是一个典型的大事务场景。

比如,一些归档类的数据,平时没有注意删除历史数据,等到空间快满了,业务开发人员要一次性地删掉大量历史数据。同时,又因为要避免在高峰期操作会影响业务(至少有这个意识还是很不错的),所以会在晚上执行这些大量数据的删除操作。

结果,负责的 DBA 同学半夜就会收到延迟报警。然后,DBA 团队就要求你后续再删除数据的时候,要控制每个事务删除的数据量,分成多次删除。

2.3.1 另一种典型的大事务场景,就是大表 DDL

处理方案就是,计划内的 DDL,建议使用 gh-ost 方案

2.4 备库的并行复制能力

如果主库上也不做大事务了,还有什么原因会导致主备延迟吗?

Ans:

其实说到底,所有的多线程复制机制,都是要把一个线程的 sql_thread,拆成多个线程:



coordinator 就是原来的 sql_thread, 不过现在它不再直接更新数据了,只负责读取中
转日志和分发事务。真正更新日志的,变成了 worker 线程。而 work 线程的个数,就是由参数slave_parallel_workers 决定的。根据经验,把这个值设置为 8~16 之间最好(32 核物理机的情况),毕竟备库还有可能要提供读查询,不能把 CPU 都吃光了。

事务能不能按照轮询的方式分发给各个 worker,也就是第一个事务分给 worker_1,第二个事务发给 worker_2 呢?

Ans:

同一个事务的多个更新语句,能不能分给不同的worker 来执行呢?

Ans:

2.4.1 MySQL 5.5 版本的并行复制策略

官方 MySQL 5.5 版本是不支持并行复制的。作者自己写了两个版本的并行策略,即按表分发策略和按行分发策略。

1)按表分发策略

每个 worker 线程对应一个 hash 表,用于保存当前正在这个 worker 的“执行队列”里的事务所涉及的表。hash 表的 key 是“库名. 表名”,value 是一个数字,表示队列中有多少个事务修改这个表。在有事务分配给 worker 时,事务里面涉及的表会被加到对应的 hash 表中。worker 执行完成后,这个表会被从 hash 表中去掉。

假设在图中的情况下,coordinator 从中转日志中读入一个新事务 T,这个事务修改的行涉及到表 t1 和 t3。现在用事务 T 的分配流程,来看一下分配规则。

每个事务在分发的时候,跟所有 worker 的冲突关系包括以下三种情况:

这个按表分发的方案,在多个表负载均匀的场景里应用效果很好。但是,如果碰到热点表,比如所有的更新事务都会涉及到某一个表的时候,所有事务都会被分配到同一个 worker 中,就变成单线程复制了。

2)按行分发策略

CREATE TABLE `t1` (
 `id` int(11) NOT NULL,
 `a` int(11) DEFAULT NULL,
 `b` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `a` (`a`)
) ENGINE=InnoDB;

insert into t1 values(1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5);

假设,接下来要在主库执行这两个事务:



这两个事务要更新的行的主键值不同,但是如果它们被分到不同的 worker,就有可能 session B 的语句先执行。这时候 id=1 的行的 a 的值还是 1,就会报唯一键冲突。

因此,基于行的策略,事务 hash 表中还需要考虑唯一键,即 key 应该是“库名 + 表名 + 索引a 的名字 +a 的值”。

3)对比
相比于按表并行分发策略,按行并行策略在决定线程分发的时候,需要消耗更多的计算资源。这两个方案其实都有一些约束条件:

对比按表分发和按行分发这两个方案的话,按行分发策略的并行度更高。不过,如果是要操作很多行的大事务的话,按行分发的策略有两个问题:

所以,在实现这个策略的时候会设置一个阈值,单个事务如果超过设置的行数阈值(比如,如果单个事务更新的行数超过 10 万行),就暂时退化为单线程模式,退化过程的逻辑大概是这样的:

2.4.2 MySQL 5.6 版本的并行复制策略

官方 MySQL5.6 版本,支持了并行复制,只是支持的粒度是按库并行。用于决定分发策略的 hash 表里,key 就是数据库名。

这个策略的并行效果,取决于压力模型。如果在主库上有多个 DB,并且各个 DB 的压力均衡,使用这个策略的效果会很好。

相比于按表和按行分发,这个策略有两个优势:

但是,如果主库上的表都放在同一个 DB 里面,这个策略就没有效果了;或者如果不同 DB的热点不同,比如一个是业务逻辑库,一个是系统配置库,那也起不到并行的效果。

理论上可以创建不同的 DB,把相同热度的表均匀分到这些不同的 DB 中,强行使用这个策略。不过由于需要特地移动数据,这个策略用得并不多。

2.4.3 MariaDB 的并行复制策略

redo log 组提交 (group commit) 优化, 而 MariaDB 的并行复制策略利用的就是这个特性:

在实现上,MariaDB 是这么做的:

之前业界的思路都是在“分析 binlog,并拆分到 worker”上。而 MariaDB 的这个策略,目标是“模拟主库的并行模式”。但是,这个策略有一个问题,它并没有实现“真正的模拟主库并发度”这个目标。在主库上,一组事务在 commit 的时候,下一组事务是同时处于“执行中”状态的。

如下图所示,假设了三组事务在主库的执行情况,你可以看到在 trx1、trx2 和 trx3 提交的时候,trx4、trx5 和 trx6 是在执行的。这样,在第一组事务提交完成的时候,下一组事务很快就会进入 commit 状态。



而按照 MariaDB 的并行复制策略,备库上的执行效果如下图所示:


可以看到,在备库上执行的时候,要等第一组事务完全执行完成后,第二组事务才能开始执行,这样系统的吞吐量就不够。

另外,这个方案很容易被大事务拖后腿。假设 trx2 是一个超大事务,那么在备库应用的时候,trx1 和 trx3 执行完成后,就只能等 trx2 完全执行完成,下一组才能开始执行。这段时间,只有一个 worker 线程在工作,是对资源的浪费。

2.4.4 MySQL 5.7 的并行复制策略

在 MariaDB 并行复制实现之后,官方的 MySQL5.7 版本也提供了类似的功能,由参数 slave-parallel-type 来控制并行复制策略:

同时处于“执行状态”的所有事务,是不是可以并行?

Ans:

因此,MySQL 5.7 并行复制策略的思想是:

binlog 组提交的两个参数:

这两个参数是用于故意拉长 binlog 从 write 到 fsync 的时间,以此减少 binlog 的写盘次数。在 MySQL 5.7 的并行复制策略里,它们可以用来制造更多的“同时处于 prepare 阶段的事务”。这样就增加了备库复制的并行度。

也就是说,这两个参数,既可以“故意”让主库提交得慢些,又可以让备库执行得快些。在
MySQL 5.7 处理备库延迟的时候,可以考虑调整这两个参数值,来达到提升备库复制并发度的目的。

2.4.5 MySQL 5.7.22 的并行复制策略

在 2018 年 4 月份发布的 MySQL 5.7.22 版本里,MySQL 增加了一个新的并行复制策略,基于WRITESET 的并行复制。

相应地,新增了一个参数 binlog-transaction-dependency-tracking,用来控制是否启用这个新策略。这个参数的可选值有以下三种:

当然为了唯一标识,这个 hash 值是通过“库名 + 表名 + 索引名 + 值”计算出来的。如果一个表上除了有主键索引外,还有其他唯一索引,那么对于每个唯一索引,insert 语句对应的writeset 就要多增加一个 hash 值。

跟前面介绍的基于 MySQL 5.5 版本的按行分发的策略是差不多的。不过,MySQL 官方的这个实现还是有很大的优势:

因此,MySQL 5.7.22 的并行复制策略在通用性上还是有保证的。

当然,对于“表上没主键”和“外键约束”的场景,WRITESET 策略也是没法并行的,也会暂时退化为单线程模型。

2.4.6 总结

为什么要有多线程复制呢?

Ans:

大事务不仅会影响到主库,也是造成备库复制延迟的主要原因之一。因此,在平时的开发工作中,建议尽量减少大事务操作,把大事务拆成小事务。

官方 MySQL5.7 版本新增的备库并行策略,修改了 binlog 的内容,也就是说 binlog 协议并不是向上兼容的,在主备切换、版本升级的时候需要把这个因素也考虑进去。

问题:

Ans:

3.主备切换策略

3.1 可靠性优先策略


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

这个切换流程,一般是由专门的 HA 系统来完成的。


可以看到,这个切换流程中是有不可用时间的。因为在步骤 2 之后,主库 A 和备库 B 都处于readonly 状态,也就是说这时系统处于不可写状态,直到步骤 5 完成后才能恢复。

在这个不可用状态中,比较耗费时间的是步骤 3,可能需要耗费好几秒的时间。这也是为什么需要在步骤 1 先做判断,确保 seconds_behind_master 的值足够小。

3.2 可用性优先策略

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

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

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);

下图是可用性优先策略,且 binlog_format=mixed时的切换流程和数据结果。



切换流程:

最后的结果就是,主库 A 和备库 B 上出现了两行不一致的数据。可以看到,这个数据不一致,是由可用性优先流程导致的。

这里的关键原因是:这里的主键是自动生成的,然后主备切换时,后插入的先在B库上执行。

如果用可用性优先策略,但设置 binlog_format=row,情况又会怎样呢?

Ans:

一些结论:

但事无绝对,有没有哪种情况数据的可用性优先级更高呢?

Ans:

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

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

问题:

Ans:

begin; 
select * from t limit 1;

这时候主库对表 t 做了一个加字段操作,即使这个表很小,这个 DDL 在备库应用的时候也会被堵住。

上一篇 下一篇

猜你喜欢

热点阅读