欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

5.7新特性之多线程复制

程序员文章站 2022-03-09 09:28:30
...

5.7新特性之多线程复制

一、背景

1.1传统主从复制存在的问题

  众所周知,MySQL的从库可以做业务的线性扩展、实现读写分离、数据备份等功能。但是当主库压力比较大的时候,就会产生一个让人头疼的问题,那就是主从复制延迟。主从复制延迟会导致从库数据落后于主库,产生诸多问题。

1.2降低复制延迟的方法

·增大从库innodb_buffer_pool_size的值,使从库可以缓存更多数据,降低IO压力。
·增大innodb_log_file_size、innodb_log_files_in_group的值,降低刷盘IO,提升写入性能。
·将innodb_flush_method设置为O_DIRECT,提升写入性能。
·如果没有特殊需求,关闭从库binlog。
·将参数master_info_repository和relay_log_info_repository设置为TABLE,降低IO压力。
  上文这些方法虽然可以降低复制延迟,但是这始终无法真正解决复制延迟问题。


二、MySQL5.6的多线程复制

  除了上文的优化方法,其实MySQL还有一种自带的手段,就是开启MySQL的多线程复制。

2.1MySQL5.6多线程复制的实现

  对于某一个库来说,它会被绑定到第一个执行它的线程上,这里的绑定不是说以后该数据库的事件都会由该线程执行,还受制于另一个条件:coordinator线程分配事件时以事务为单位,一个事务会分配给该事务中第一个库所绑定worker线程,不会被拆分。如果遇到一个新的库,不能按照上面的规则决定执行的数据库的(即没有绑定线程,而且是该事务中第一个库)则会寻找绑定库最少的worker线程来执行它。
  涉及多库操作的语句,在分配这个语句时,coordinator线程会等待这些库的绑定线程都执行完毕,然后再分配这个语句。而如何涉及到的库太多(大于254)或者是一个ddl语句,则会触发一次同步操作,即等待所有线程执行完毕,然后将它分配给0号worker线程。

2.2MySQL5.6的多线程复制的缺点

  MySQL5.6的多线程复制要求数据库数量比较多,并且各个库的数据要分布均匀。换句话说,MySQL5.6的多线程复制是基于库级别的,如果binlog row event操作的是不同的schema的对象,在没有DDL的情况下,就可以实现多线程复制。
  但是在实际业务场景中,一库多表很常见,多库少表却很少见。这个原因导致MySQL5.6的多线程复制并不适合在实际环境应用,但是不得不承认这的确一个解决复制延迟问题很好的手段。


三、MySQL5.7的多线程复制

  MySQL5.6的多线程复制在实际应用起到的作用却很小。直到MySQL5.7版本,MySQL才“真正”支持多线程复制功能,官方称为为enhanced multi-threaded slave(简称MTS)。

3.1MySQL的组提交

  在介绍MySQL5.7的多线程复制前,我要先介绍一下组提交。MySQL5.7相对于MySQL5.6在GTID中增加了两个事件(last_committed和sequence_number),根据这两个事件就可以明白什么是组提交:

#190528  9:50:18 server id 330601  end_log_pos 971 CRC32 0x0d6b7893 	GTID	last_committed=2	sequence_number=3	rbr_only=yes
#190528  9:50:23 server id 330601  end_log_pos 1288 CRC32 0x02f70237 	GTID	last_committed=3	sequence_number=4	rbr_only=yes
#190528  9:50:24 server id 330601  end_log_pos 1605 CRC32 0xd613b4bf 	GTID	last_committed=4	sequence_number=5	rbr_only=yes		

  可以看到以往binlog中下一个事务的last_committed永远都和上一个事务的sequence_number是相等的。

#190629 21:21:07 server id 330601  end_log_pos 1420595 CRC32 0xee9fca87 	GTID	last_committed=3460	sequence_number=3506	rbr_only=yes
#190629 21:21:07 server id 330601  end_log_pos 1420998 CRC32 0x64a67854 	GTID	last_committed=3460	sequence_number=3507	rbr_only=yes
#190629 21:21:07 server id 330601  end_log_pos 1421401 CRC32 0x89930386 	GTID	last_committed=3460	sequence_number=3508	rbr_only=yes

  在MySQL5.7开启组提交的binlog中各事务的last_committed是相同的,这意味着多个事务是作为一个组提交的,这些事务在perpare阶段获取相同的last_committed而且相互不影响,最终是会作为一个组进行提交。这就是所谓的组提交。
  MySQL5.7的组提交通过参数group_commit设置。

mysql> show variables like '%group_commit%';
+-----------------------------------------+-------+
| Variable_name                           | Value |
+-----------------------------------------+-------+
| binlog_group_commit_sync_delay          | 0     |
| binlog_group_commit_sync_no_delay_count | 0     |
+-----------------------------------------+-------+
2 rows in set (0.00 sec)
binlog_group_commit_sync_delay控制着日志在刷盘前日志提交要等待的时间,0代表提交后立即刷盘,当设置为N(大于0)的时候,就允许多个事务的日志同时间一起提交刷盘,也就是我们说的组提交。组提交是多线程复制的基础。
binlog_group_commit_sync_no_delay_count ,这个参数优先于binlog_group_commit_sync_delay,在等待时间内,如果事务数达到binlog_group_commit_sync_no_delay_count的值,就会触动一次组提交。

3.2MySQL5.7多线程复制的实现

  MySQL期望最大化的还原主库的并行度,实现方式是在binlog event中增加必要的信息(last_committed和sequence_number),以便slave节点根据这些信息实现多线程复制。
  MySQL5.7使用参数slave_parallel_type来兼容5.6的多线程复制;通过参数slave_parallel_workers指定多线程复制的线程数。

mysql> show variables like '%slave_parallel_type%';
+---------------------+---------------+
| Variable_name       | Value         |
+---------------------+---------------+
| slave_parallel_type | LOGICAL_CLOCK |
+---------------------+---------------+
1 row in set (0.01 sec)
mysql> show variables like '%slave_parallel_workers%';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| slave_parallel_workers | 8     |
+------------------------+-------+
1 row in set (0.01 sec)

  MySQL5.7的多线程复制建立在group commit的基础上,所有在主库上能够完成prepared的语句表示没有数据冲突,就可以在slave节点多线程复制。


四、MySQL事务提交方式及多线程复制分发

4.1MySQL5.7中事务提交方式

  binlog的组提交是通过 Stage_manager 管理,其中比较核心内容如下:

class Stage_manager {
  public:
    enum StageID {         // binlog的组提交包括了三个阶段
      FLUSH_STAGE,
      SYNC_STAGE,
      COMMIT_STAGE,
      STAGE_COUNTER
    };
  private:
    Mutex_queue m_queue[STAGE_COUNTER];
};

  binlog的组提交的三个阶段主要执行的工作内容为:

InnoDB, Prepare
    SQL已经成功执行并生成了相应的redo和undo内存日志;
Binlog, Flush Stage
    所有已经注册线程都将写入binlog缓存;
Binlog, Sync Stage
    binlog缓存将sync到磁盘,sync_binlog=1时该队列中所有事务的binlog将永久写入磁盘;
InnoDB, Commit stage
    leader根据顺序调用存储引擎提交事务;

  每个阶段都有各自的队列,从而使每个会话的事务进行排队,提高并发性能。如果当一个线程注册到一个空队列时,该线程就做为该队列的leader,后注册到该队列的线程均为follower,后续的操作都由leader控制队列中follower行为。
  leader同时会带领当前队列的所有follower到下一个阶段 去执行,当遇到下一个阶段为非空队列时,leader会变成follower注册到此队列中。

4.2多线程复制分发原理

  当slave_parallel_workers参数设置成n时,会有n个worker线程,由它来执行event,原来的sql线程变成coordinator线程,由它来读取relay log,并按照一定规则将读到的event分配给worker线程执行。从库是以事务为单位进行APPLY的,每一个事务有一个GTID事件,都有一个last_committed和sequence_number,多线程复制分发原理如下:

1、从库SQL线程读取一个新事务,拿出last_committed和sequence_number的值。
2、判断当前last_committed是否大于当前已经执行的sequence_number的最小值。如果大于,则说明上一组事务还没有完成,需要等待last_committed等于sequence_number后继续;否则说明当前事务和正在执行在同一组,直接继续。
3、SQL线程寻找worker线程,将当前事务交给worker,worker线程去APPLY这个事务。


五、总结

  多线程复制从MySQL5.6到MySQL5.7的发展,真正地实现多线程复制,解决了复制延迟问题!