Jquery中文网 www.jquerycn.cn
Jquery中文网 >  数据库  >  mysql  >  正文 各版本MySQL并行复制的实现及优缺点

各版本MySQL并行复制的实现及优缺点

发布时间:2017-12-10   编辑:www.jquerycn.cn
jquery中文网为您提供各版本MySQL并行复制的实现及优缺点等资源,欢迎您收藏本站,我们将为您提供最新的各版本MySQL并行复制的实现及优缺点资源
mysql复制我们用到的不多特别是并行复制了今天看到阿里云一篇关于并行复制的教程,这里小编也来为各位整理一下,具体的操作细节如下所示.

MySQL并行复制已经是老生常谈,笔者从2010年开始就着手处理线上这个问题,刚开始两三年也乐此不疲分享,现在再提这个话题本来是难免“炒冷饭”嫌疑。
 
 最近触发再谈这个话题,是因为有些同学觉得“5.7的并行复制终于彻底解决了复制并发性问题”, 感觉还是有必要分析一下。大家都说没有银弹,但是又期待银弹。。
 
既然要说5.7的并行复制,干脆顺手把各个版本的并行复制都说明一下,也好有个对比。便是本次分享的初衷。
 
【背景】
一句话说完,因为这几年太多这样文章了, 就是MySQL一直以来的备库复制都是单线程apply。
 
【解决基本思路】
改成多线程复制。
 
备库有两个线程与复制相关:io_thread 负责从主库拿binlog并写到relaylog, sql_thread 负责读relaylog并执行。


 
多线程的思路就是把sql_thread 变成分发线程,然后由一组worker_thread来负责执行。
几乎所有的并行复制都是这个思路,有不同的,便是sql_thread 的分发策略。


 
而这些策略里面又分成两类:利用传统binlog格式、修改binlog。
 
使用传统的binlog格式的几类,由于binlog里面的信息就那些,因此只能按照粒度来分,也就是:按库、按表、按行
 
另外有两个策略是修改了binlog格式的,在binlog里面增加了别的信息,用于体现提交分组。
 
下面我们分别介绍几个并行复制的实现。
 
【5.5】
 
MySQL官方5.5是不支持并行复制的。但是在阿里的业务需要并行复制的年份,还没有官方版本支持,只好自己实现。而且从兼容性角度说,不修改binlog格式,所以采用的是利用传统binlog格式的改造。
 
阿里的版本支持两种分发策略:按表和按行。
 
前情说明,由于MySQLbinlog日志还有用于别的系统的要求,因此阿里的binlog格式都是row----这也给并行复制的实现减少了难度。
 
按表分发策略:row格式的binlog,每个DML前面都是有Table_map event的。因此很容易拿到库名/表名。一个简单的思路是,不同表的更新之间是不需要严格按照顺序的。
因此按照表名hash,hash key是 库名 表名,相同的表的更新放到同一个worker上。这样就保证同一个表的更新顺序,跟主库上是一样的。
 
应用场景:对于多表更新的场景效果特别好。缺点是反之的,若是热点表更新,则本策略无效。而且由于hash表的维护,性能反而下降。
 
按行分发策略:row格式的binlog中,也不难拿到主键ID.  有同学说如果没有主键怎么办,答案是"起开,现在谁还没主键:)"。好吧,正经答案是没有主键就不支持这个策略。
同样的,我们认为不同行的更新,可以无序并发的。只要保证同一行的数据更新,在备库上的顺序与主库上的相同即可。
因此按照主键id hash,所以这个hash key更长,必须是 库名 表名 主键id。相同行的更新放到同一个worker上。
 
需要注意的是,上面的描述看上去都是对单个event的操作,实际上并不能!因为备库可能接受读,因此事务的原子性是要保证的,也就是说,对于涉及多个更新操作的事务,每次用于决策的不是一个hash key,而是一组。
应用场景:热点表更新。缺点,hash key计算冲突的代价大。尤其是大事务,计算hash key的cpu消耗大,而且耗内存。这需要业务DBA做判断得失。
 
【5.6】
官方的5.6支持的是按库分发。有了上面的背景,大家就知道,这个feature出来以后,在中国并没有什么反响。
 
但是这个策略也要说也是有优点的:
 
1、对于可以按表分发的场景,可以通过将表迁到不同的库,来应用此策略,有可操作性
 
2、速度更快,因为hash key就一个库名
 
3、不要求binlog格式,大家知道不论是row还是statement格式,都是能够轻松获取库名的。
所以并不是完全没有用的。还是习惯问题。
 
 
【MariaDB】
 
MariaDB的并行复制策略看上去有好几个选项,然而生产上可用的也就是默认值的 CONSERVATIVE。
 
由于maraiaDB支持多主复制,一个domain_id字段是用来标示事务来源的。如果来自于不同的主,自然可以并行(这个其实也是通用概念,还得业务DBA自己判断)。
 
对于同一个主库来的binlog,用commit_id 来决定分组。
 
想法是这样的:在主库上同时提交的事务设置成相同的commit_id。在备库上apply时,相同的commit_id可以并行执行,因为这意味着这些事务之间是没有行冲突的(否则不可能同时提交)。
 
这个思路跟最初从单线程改成多线程一样,个人认为是划时代的。
 
但是也并没有解决了所有的问题。这个策略最怕的是,拖后腿事务。
 
设想一下这个场景,假设某个DB里面正在作大量小更新事务(比如每个事务更新一行),这样在备库就并行得很欢乐。
 
然后突然,在同一个实例,另外一个库下,或者同一个库的另外一个跟目前的更新无关的表,突然有一个delte操作删除了10w行。
 
delete事务在提交的时候,跟当时一起提交的事务都算同一个commit_id。假设为N.
 
之后的小事务更新提交组commit_id为N 1。
 
到备库apply时,就会发现N这个组里面,其他小事务都执行完了,线程进入空闲状态,但是不能继续执行N 1这个commit_id的事务,因为N里面还有一个大事务没有执行完成,这个我们认为是拖后腿的。
 
而基于传统binlog格式的上面三个策略,反而没有这个问题。只要是策略上能够判断不冲突,大事务自己有个线程跑,其他事务继续并行。
 
【5.7】
 
MySQL官方5.7版本也是及时跟进,先引入了上述MariaDB的策略。当然从版权安全上,oracle是不会允许直接port代码的。
 
实际上按组直接分段这个策略略显粗暴。实际上事务提交并不是一个点,而是一个阶段。至少我们可以分成:准备提交、提交中、提交完成。
 
这三个阶段都是在事务已经完成了主要操作逻辑,进入commit状态了。
 
同时进入“提交中”状态的算同一个commit_id. 但是实际上,在任意时刻,处于”准备提交”的事务,与“提交中”的事务,也是可以并行的。但是明显他们会被分成两个不同的commit_id。
 
这意味着这个策略还有提升并发度的空间。
 
我们来看一下两种策略的对比差别。
 
假设主库有如下面示意图的事务序列。每个事务提交过程看成两个阶段,prepare ... commit. 分别给不同的编号。其中commit对应的数字是自然数递增,sequence_no。而prepare是对应的数字是X 1,这个X表示的是当前已经提交完成的sequence_no。
trx1 1…..2
trx2 1………….3
trx3 1…………………….4
trx4        2………………………….5
trx5               3………………………………..6
trx6               3………………………………………………7
trx7                                                       6……………………..8
 
分析:
在MariaDB的策略里面,并发执行序列如下:
trx1, trx2, trx3 ----group 1
trx4 -----group 2
trx 5, trx6 ----group 3
trx 7 ----group 4
每个group 执行完成后,下一个group 才可以开始。
完全执行完成的时间是每个group的最大事务时间之和,即 trx3 trx4 trx6 trx7。
因此,如果某个group里面有一个很大的事务,则整个序列的执行时间就会被拖久。
 
再来看5.7的改进策略:
虽然也是group1先启动,但是在trx1完成后, trx4就可以开始执行;
同样的,trx7可以在trx4执行完成后就开始执行,与trx5和trx6并发。
因此可以说上面这个例子中,备库apply过程完全达到了主库执行的并发度。
但是对于大事务,比如trx2 commit 非常久的情况,仍然存在拖后腿的问题。

补充:


MySQL 5.7并行复制原理
MySQL 5.7基于组提交的并行复制
MySQL 5.7才可称为真正的并行复制,这其中最为主要的原因就是slave服务器的回放与主机是一致的即master服务器上是怎么并行执行的slave上就怎样进行并行回放。不再有库的并行复制限制,对于二进制日志格式也无特殊的要求(基于库的并行复制也没有要求)。
从MySQL官方来看,其并行复制的原本计划是支持表级的并行复制和行级的并行复制,行级的并行复制通过解析ROW格式的二进制日志的方式来完成,WL#4648。但是最终出现给小伙伴的确是在开发计划中称为:MTS: Prepared transactions slave parallel applier,可见:WL#6314。该并行复制的思想最早是由MariaDB的Kristain提出,并已在MariaDB 10中出现,相信很多选择MariaDB的小伙伴最为看重的功能之一就是并行复制。
MySQL 5.7并行复制的思想简单易懂,一言以蔽之:一个组提交的事务都是可以并行回放,因为这些事务都已进入到事务的prepare阶段,则说明事务之间没有任何冲突(否则就不可能提交)。
为了兼容MySQL 5.6基于库的并行复制,5.7引入了新的变量slave-parallel-type,其可以配置的值有:
DATABASE:默认值,基于库的并行复制方式
LOGICAL_CLOCK:基于组提交的并行复制方式
支持并行复制的GTID
如何知道事务是否在一组中,又是一个问题,因为原版的MySQL并没有提供这样的信息。在MySQL 5.7版本中,其设计方式是将组提交的信息存放在GTID中。那么如果用户没有开启GTID功能,即将参数gtid_mode设置为OFF呢?故MySQL 5.7又引入了称之为Anonymous_Gtid的二进制日志event类型,如:

mysql> SHOW BINLOG EVENTS in 'mysql-bin.000006';
  ------------------ ----- ---------------- ----------- ------------- -----------------------------------------------
 | Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
  ------------------ ----- ---------------- ----------- ------------- -----------------------------------------------
 | mysql-bin.000006 | 4 | Format_desc | 88 | 123 | Server ver: 5.7.7-rc-debug-log, Binlog ver: 4 |
 | mysql-bin.000006 | 123 | Previous_gtids | 88 | 194 | f11232f7-ff07-11e4-8fbb-00ff55e152c6:1-2 |
 | mysql-bin.000006 | 194 | Anonymous_Gtid | 88 | 259 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
 | mysql-bin.000006 | 259 | Query | 88 | 330 | BEGIN |
 | mysql-bin.000006 | 330 | Table_map | 88 | 373 | table_id: 108 (aaa.t) |
 | mysql-bin.000006 | 373 | Write_rows | 88 | 413 | table_id: 108 flags: STMT_END_F |
 .....
这意味着在MySQL 5.7版本中即使不开启GTID,每个事务开始前也是会存在一个Anonymous_Gtid,而这GTID中就存在着组提交的信息。
LOGICAL_CLOCK
然而,通过上述的SHOW BINLOG EVENTS,我们并没有发现有关组提交的任何信息。但是通过mysqlbinlog工具,用户就能发现组提交的内部信息:

 

root@localhost:~# mysqlbinlog mysql-bin.0000006 | grep last_committed
 #150520 14:23:11 server id 88 end_log_pos 259 CRC32 0x4ead9ad6 GTID last_committed=0 sequence_number=1
 #150520 14:23:11 server id 88 end_log_pos 1483 CRC32 0xdf94bc85 GTID last_committed=0 sequence_number=2
 #150520 14:23:11 server id 88 end_log_pos 2708 CRC32 0x0914697b GTID last_committed=0 sequence_number=3
 #150520 14:23:11 server id 88 end_log_pos 3934 CRC32 0xd9cb4a43 GTID last_committed=0 sequence_number=4
 #150520 14:23:11 server id 88 end_log_pos 5159 CRC32 0x06a6f531 GTID last_committed=0 sequence_number=5
 #150520 14:23:11 server id 88 end_log_pos 6386 CRC32 0xd6cae930 GTID last_committed=0 sequence_number=6
 #150520 14:23:11 server id 88 end_log_pos 7610 CRC32 0xa1ea531c GTID last_committed=6 sequence_number=7
 ...
可以发现较之原来的二进制日志内容多了last_committed和sequence_number,last_committed表示事务提交的时候,上次事务提交的编号,如果事务具有相同的last_committed,表示这些事务都在一组内,可以进行并行的回放。例如上述last_committed为0的事务有6个,表示组提交时提交了6个事务,而这6个事务在从机是可以进行并行回放的。
上述的last_committed和sequence_number代表的就是所谓的LOGICAL_CLOCK。先来看源码中对于LOGICAL_CLOCK的定义:

 

class Logical_clock
 {
 private:
 int64 state;
 /*
 Offset is subtracted from the actual "absolute time" value at
 logging a replication event. That is the event holds logical
 timestamps in the "relative" format. They are meaningful only in
 the context of the current binlog.
 The member is updated (incremented) per binary log rotation.
 */
 int64 offset;
 ......
state是一个自增的值,offset在每次二进制日志发生rotate时更新,记录发生rotate时的state值。其实state和offset记录的是全局的计数值,而存在二进制日志中的仅是当前文件的相对值。使用LOGICAL_CLOCK的场景如下:

 

class MYSQL_BIN_LOG: public TC_LOG
 {
 ...
 public:
 /* Committed transactions timestamp */
 Logical_clock max_committed_transaction;
 /* "Prepared" transactions timestamp */
 Logical_clock transaction_counter;
 ...
可以看到在类MYSQL_BIN_LOG中定义了两个Logical_clock的变量:
max_c ommitted_transaction:记录上次组提交时的logical_clock,代表上述mysqlbinlog中的last_committed
transaction_counter:记录当前组提交中各事务的logcial_clock,代表上述mysqlbinlog中的sequence_number
并行复制测试
下图显示了开启MTS后,slave服务器的QPS。测试的工具是sysbench的单表全update测试,测试结果显示在16个线程下的性能最好,从机的QPS可以达到25000以上,进一步增加并行执行的线程至32并没有带来更高的提升。而原单线程回放的QPS仅在4000左右,可见MySQL 5.7 MTS带来的性能提升,而由于测试的是单表,所以MySQL 5.6的MTS机制则完全无能为力了。
mysql

mysql 5.7 并行复制

并行复制配置与调优
master_info_repository
开启MTS功能后,务必将参数master_info_repostitory设置为TABLE,这样性能可以有50%~80%的提升。这是因为并行复制开启后对于元master.info这个文件的更新将会大幅提升,资源的竞争也会变大。在之前InnoSQL的版本中,添加了参数来控制刷新master.info这个文件的频率,甚至可以不刷新这个文件。因为刷新这个文件是没有必要的,即根据master-info.log这个文件恢复本身就是不可靠的。在MySQL 5.7中,Inside君推荐将master_info_repository设置为TABLE,来减小这部分的开销。
slave_parallel_workers
若将slave_parallel_workers设置为0,则MySQL 5.7退化为原单线程复制,但将slave_parallel_workers设置为1,则SQL线程功能转化为coordinator线程,但是只有1个worker线程进行回放,也是单线程复制。然而,这两种性能却又有一些的区别,因为多了一次coordinator线程的转发,因此slave_parallel_workers=1的性能反而比0还要差,在Inside君的测试下还有20%左右的性能下降,如下图所示:
mysql
mysql 5.7 并行复制
这里其中引入了另一个问题,如果主机上的负载不大,那么组提交的效率就不高,很有可能发生每组提交的事务数量仅有1个,那么在从机的回放时,虽然开启了并行复制,但会出现性能反而比原先的单线程还要差的现象,即延迟反而增大了。聪明的小伙伴们,有想过对这个进行优化吗?
Enhanced Multi-Threaded Slave配置
说了这么多,要开启enhanced multi-threaded slave其实很简单,只需根据如下设置:

 

# slave
 slave-parallel-type=LOGICAL_CLOCK
 slave-parallel-workers=16
 master_info_repository=TABLE
 relay_log_info_repository=TABLE
 relay_log_recovery=ON
并行复制监控
复制的监控依旧可以通过SHOW SLAVE STATUS\G,但是MySQL 5.7在performance_schema架构下多了以下这些元数据表,用户可以更细力度的进行监控:


mysql> show tables like 'replication%';
  ---------------------------------------------
 | Tables_in_performance_schema (replication%) |
  ---------------------------------------------
 | replication_applier_configuration |
 | replication_applier_status |
 | replication_applier_status_by_coordinator |
 | replication_applier_status_by_worker |
 | replication_connection_configuration |
 | replication_connection_status |
 | replication_group_member_stats |
 | replication_group_members |
  ---------------------------------------------
 8 rows in set (0.00 sec)
 
 【小结】

按粒度区分的三个策略,粒度从粗到细是按库、按表、按行。
这三个的对比中,并行度越来越大,额外损耗也是。无关大事务不会影响并发度。
 
按照commit_id 的两个策略,适用范围更广,额外消耗也低。
5.7的改进策略并发性更优。但出现大事务会拖后腿。
 
另外,很重要的一点,5.7的策略目的是“模拟主库并发”,所以对于主库单线程更新是无加速作用的。而基于冲突的前三个策略,若满足并发条件,会出现备库比主库执行速度快的情况。这种需求在搭备库或者延迟复制的场景中可能触发。
 
实际上策略的选择取决于应用场景,这是架构师的工作之一。
 
PS:具体5.7的实现原理可参考我们团队的@印风 同学的博客  http://mysqllover.com/?p=1370 (最后一个例子的case也从此摘录)

您可能感兴趣的文章:
mysql设置自动清理binlog日志的方法
理解Mysql binlog 日志的三种模式
mysql如何锁定单个表
各版本MySQL并行复制的实现及优缺点
css 全方位清理浮动总结
几种css 清除浮动的方法
mysql Replication(复制)基本原理介绍
mysql索引的建立原则
有关 mysql 5.6 Replication 参数的学习笔记
mysql数据库性能优化技巧

[关闭]