5.7新特性之多线程复制

  1. 云栖社区>
  2. 袋鼠云技术团队>
  3. 博客>
  4. 正文

5.7新特性之多线程复制

梓杰 2019-07-08 09:43:12 浏览2485
展开阅读全文

一、背景

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的发展,真正地实现多线程复制,解决了复制延迟问题!

网友评论

登录后评论
0/500
评论
梓杰
+ 关注
所属云栖号: 袋鼠云技术团队