MySQL8.0新特性--基于Write Set并行复制
复制简介
MySQL早期只有单线程复制,即IO线程接收master的binlog,并写入本地的relay log中,SQL线程负责从relay log中服务event并进行apply。当主库的写入压力较大时,备库的IO线程一般不会产生延迟,因为写relay log是顺序写;但SQL线程的重放速度经常跟不上主库的写入速度,造成主备延迟,延迟过大时,relay log会堆积造可能把磁盘占满,同时延迟大时,会给读写分离带来一定的困难。为缓解这种问题,MySQL从5.6开始引入了并行复制。
MySQL5.6并行复制
MySQL5.6版本时候,引入了基于schema级别的并行复制,同时引入参数slave_parallel_workers(并行复制线程数,默认0),当开启并行复制后,原SQL线程就变成coordinator线程,主要负责如下工作:
1、 判断relay log中的事务可以并行执行,并协调worker线程执行binlog中的事务。
2、 判断不可并行执行,如DDL操作、事务存在跨schema的操作,则等待其他worker线程执行完后,再执行当前无法并行执行的事务。
由于是基于不同的schema的并行复制,所以正常情况下各事务之间不存在锁冲突,即同一个事务内的EVENT都发给同一个worker进行回放。但是如果我们的数据库是一库一实例,那么基于schema的并行复制显然无能为力,或者单实例多库中某一个库的压力大时,复制延迟依旧存在。为了缓解这种问题,MySQL5.7引入了基于事务的并行复制。
MySQL5.7并行复制
5.7的并行复制是基于Group Commit(组提交)的,在引入Group Commit之前,因为binlog属于MySQL Server层,redo log属于innodb引擎层,为了保证Binlog和redo log顺序的一致性,引入了2pc(二阶段提交),binlog和redo log实际是串行提交,流程如下:
1、innodb prepare:sql语句已经成功执行并生成redo和undo
2、Write/Sync binlog:binary log写入文件并落盘
3、Innodb commit:在存储引擎内提交,redo落盘
在引入Group Commit之后,二进制日志提交过程分成三个阶段,整体的提交流程变成:
InnoDB Prepare---->Flush Stage---->Flush Stage---->Commit Stage
Flush stage:所有已经注册线程(InnoDB Prepare成功的事务)都将写入binary log缓存
Sync stage :binary log缓存的数据将会sync到磁盘,当sync_binlog=1时所有该队列事务的二进制日志缓存永久写入磁盘
Commit stage:leader根据顺序调用存储引擎提交事务。
当一组事务在进行Commit阶段时,其他新的事务可以进行Flush阶段,从而使group commit不断生效。
Group Commit的目的是:使高并发下的事务尽可能的在同一个时间点提交,然后用一次fsync()的操作将这一组的Binary log缓存的数据写入磁盘。当并发事务可以在同一个时间提交,说明每个线程所执行的事务之间没有锁冲突,那么意味着这一组并发提交的事务在slave机器上能并发重放主库提交的事务,所以我们只需要在master机器对二进制日志进行Group Commit的时候标记上组提交相关信息,slave机器就可以安全的并发执行主库提交的事务。
当设置slave_parallel_type=LOGICAL_CLOCK与slave_parallel_workers>0时,开启基于事务的并行复制。在binlog事务中会多出两个标签:
sequence_number:随每个事务递增的自增ID,每次新的binlog会从1开始
last_committed:当前事务所依赖的上次事务的sequence_number,每次新的binlog会从0开始
我们可以使用mysqlbinlog 解析对应的Binlog文件,只要last_committed相同的事务,可以并发回访。
1. #151223 15:11:28 server id 15102 end_log_pos 14623 CRC32 0x767a33fa GTID last_committed=18 sequence_number=26
3. #151223 15:11:28 server id 15102 end_log_pos 15199 CRC32 0x7dd1bf05 GTID last_committed=26 sequence_number=27
5. #151223 15:11:28 server id 15102 end_log_pos 15773 CRC32 0xb01dc76e GTID last_committed=26 sequence_number=28
7. #151223 15:11:28 server id 15102 end_log_pos 16347 CRC32 0x7a8e0ee8 GTID last_committed=26 sequence_number=29
9. #151223 15:11:28 server id 15102 end_log_pos 16921 CRC32 0x92516d17 GTID last_committed=26 sequence_number=30
11. #151223 15:11:28 server id 15102 end_log_pos 17495 CRC32 0xeb14a51e GTID last_committed=26 sequence_number=31
13. #151223 15:11:28 server id 15102 end_log_pos 18071 CRC32 0x750667d0 GTID last_committed=26 sequence_number=32
15. #151223 15:11:28 server id 15102 end_log_pos 18645 CRC32 0xcaed6159 GTID last_committed=26 sequence_number=33
17. #151223 15:11:28 server id 15102 end_log_pos 19219 CRC32 0x62408408 GTID last_committed=26 sequence_number=34
19. #151223 15:11:28 server id 15102 end_log_pos 19793 CRC32 0x5cf46239 GTID last_committed=33 sequence_number=35
显而易见的,基于事务的并行复制存在当master并发低或者一般的时候,并行回放效果并不好。
基于Write Set的并行复制
MySQL在8.0.1(5.7.22)版本中引入了基于Write Set的并行复制,即不同事务的不同记录不重叠,都可以在从库并行回访。实际上Write Set是一个集合,存放的是每条记录的Write Set值和sequence_number值。当我们对表中的数据进行增删改时,MySQL会根据算法结合主键和唯一索引生产一系列的hash值并放入Write Set中。当事务每次提交时,会计算修改的每个行记录的Write Set值,然后查找哈希表中是否已经存在有同样的Write Set,若无,Write Set插入到哈希表,写入二进制日志的last_committed值不变。若有,则更新哈希表中对应Write set的sequnce_number值,同时写入binlog中的last_committed值更新为sequnce_number。回放时和基于COMMIT_ORDER的并行复制一样,具有相同的last_committed值可以并行回放,同一条记录回放,last_committed值必然不同,必须等待之前的一条记录回放完成后才能执行。所以透过现象看本质基于Write Set的方式只是在以前的方式上对last_committed做了更进一步的处理,来达到最大的并发效果。
开启Write Set:
Binlog_format=ROW
slave_parallel_type=LOGICAL_CLOCK
slave_parallel_workers=n;n>0
binlog_transaction_dependency_tracking=WRITESET;默认COMMIT_ORDER(5.7的并行复制方式),可选【COMMIT_ORDER、WRITESET、WRITESET_SESSION】,WRITESET_SESSION是指同一个SESSION中的事务,不并发回放。
transaction_write_set_extraction=XXHASH64;计算WriteSet值的算法,默认OFF,可选【OFF、MURMUR32、XXHASH64】,推荐XXHASH64,有更好的散列
binlog_transaction_dependency_history_size=25000;WRITESET HASH表的记录数,最小1,最大100W,默认25000,值越大占的内存越大,建议根据内存大小调整
性能提升:
MySQL High Availability 对WRITESET性能做了测试,测试时通过Sysbench 先在主机上执行100W条事务,然后开启Slave的复制线程,测试环境在Xeon E5-2699-V3 16核主机上执行,以下是测试结果:
可以看到基于WRITESET的方式性能有明显提升,但当并发线程>64时,并没有比COMMIT_ORDER提升多少。
虽然基于WRITESET并行复制可以提高并发回放,但同样存在缺点和限制:
1、 表需要有主键或者非空唯一索引,否则会退化到COMMIT_ORDER方式。
2、 WRITESET需要额外的内存空间、WRITESET历史HASH表也需要额外的内存空间
3、 WRITESET的每个HASH值需要和HASH历史表进行比对。
所以如果主库负载较低,采用单线程复制即可;如果主库有一定的负载且从库没有延迟,采用默认的并行复制方式即可;如果从库有延迟或者有延迟需要追时,WRITESET毫无疑问是最佳选择。
引用: