关于 WriteSet 对复制延迟的改进

一、复制延迟的现象问题

   说到复制延迟。我曾经(现在也是)眼睁睁的看着每天好几封告警邮件,有一半是来自复制延迟,却又奈何不了。估计很多 MySQL DBA也是对他恨到牙痒痒。

二、MySQL官方给出的解决方案

 2.1  5.6 --> 基于库级别的并行复制    

   MySQL中可能会有多个库,不同的库之间可能没有什么关系,所以在slave那边为每一个库分配了一个线程。以此提高复制的效率。也有可能会出现跨库的情况,当出现这种情况,也就这能等待这个事务完成

 2.2  5.7 --> 基于组提交的并行复制

   组提交的思想是:所有处于 prepare 阶段的事务同属于一个组,一个组内的事务可以并行提交。

   基于此思想,同组事务到了 slave 端就可以并行处理。

   那么在 slave 端是怎么区分是同组事务呢?

   MySQL在binlog中加入了 last_committed,sequence_number

  #180915  2:59:19 server id 883306  end_log_pos 259 CRC32 0xad68c74f     GTID    last_committed=0        sequence_number=1       rbr_only=yes
  #180915  2:59:19 server id 883306  end_log_pos 582 CRC32 0x347b8079     GTID    last_committed=1        sequence_number=2       rbr_only=yes
  #180915  2:59:19 server id 883306  end_log_pos 905 CRC32 0x9728debf     GTID    last_committed=1        sequence_number=3       rbr_only=yes
  #180915  2:59:19 server id 883306  end_log_pos 1228 CRC32 0x5a8c2d37    GTID    last_committed=1        sequence_number=4       rbr_only=yes
  #180915  2:59:19 server id 883306  end_log_pos 1551 CRC32 0x79d0f774    GTID    last_committed=1        sequence_number=5       rbr_only=yes
  #180915  2:59:19 server id 883306  end_log_pos 1874 CRC32 0xc75b96fa    GTID    last_committed=1        sequence_number=6       rbr_only=yes
  #180915  2:59:19 server id 883306  end_log_pos 2197 CRC32 0x03bbc228    GTID    last_committed=1        sequence_number=7       rbr_only=yes
  #180915  2:59:19 server id 883306  end_log_pos 2520 CRC32 0x876377ab    GTID    last_committed=1        sequence_number=8       rbr_only=yes

   last_committed 相同的属于同一个组,例如:

   last_committed = 0 中只有 1 个事务

   last_committed = 1 中有 2-8 7个事务

   假设 slave 端的 parallel 是 10,那么就可以同时执行7个事务。虽然看着挺美好,但还是有个问题。介绍这个问题的时候,首先介绍两个参数:

   binlog_group_commit_sync_delay:等待多少时间后才进行组提交,单位 (ms),最大 1000000ms == 1s

   binlog_group_commit_sync_no_delay_count:等待一组里面有多少事物我才提交

   以上两个参数,默认都是0,意思是 MySQL 不等待就进行提交。所以当系统不繁忙是,last_committed 通常只有 1 个 sequence_number。也就是一组只有一个事务。而当系统比较繁忙时,last_commttied 中有可能有多个 sequence_number。这个由 MySQL 自己决定。但是MySQL本身决定是有问题的,假如 1 秒 中有 500 个事务(这个在我们的生产系统中最高达到1000,业务高峰300-500都是常见的)。但是一组中的事物并非都是 10 个以上的,基本上都是 1、2、3 …… 参差不齐的。所以我的从库本来是有 10 个并行线程的。但是最能同时处理 1、2、3个事务,你说这让人气不气。那么不是有 binlog_group_commit_sync_no_delay_count 这个参数吗?可以控制一组有多少事务才提交,我设置为 10,那么到从库就可以 10 个事务并行处理了啊。是这样没错,但是设置这个 binlog_group_commit_sync_no_delay_count 参数之前,需要打开 binlog_group_commit_sync_delay,否则不生效。

    

   那么既然需要打开,那就打开呗!设置多少好呢?假设设置到最大 1s,1s 最大 500个事务,开50个并行,每个并行每秒处理 10 个事务,每个事物 0.1 秒,0.1 秒从库处理的过来。从库没有延迟很开心有没有,但是这种设置有没有问题,有。看一下 binlog_group_commit_sync_delay 的解释,等待多少时间后才进行组提交。这个时间,不管你 1s 内有多少个事务,统统等待 1s。也就是我一个 insert 本来 0.01 可以完成的,我非要等待 1 秒后才提交。并发高的时候没有影响,并发低的时候问题就来了。非要阻塞 1s 才提交。还会有什么其他影响吗?有,没有提交是不是就持有锁,那么锁没释放,其他会话是不是就需要等待。所以,我曾经没有好好理解 等待多少时间后才进行组提交 这句话,导致了生产事故。具体看我写的另一篇博客:https://www.cnblogs.com/ziroro/p/9600359.html  

 2.3  5.7.22以上 --> 基于写集合的并行复制

   一不小心说了大多血泪史,回归正题。writeset的思想是:不同事物修改了不同行的数据,那么可以视为同一组。MySQL 会对这个提交的事务中的一行记录做一个 HASH值,这些 HASH 值称为 writeset。writeset会存入一张 HASH 表。其他事务提交时会检查这张 HASH 表中是否有相同的记录,如果不相同,则视为同组,如果有相同,则视为不同组。怎么判断是否同组,依然采用了last_committed,sequence_number。具体看实验:

  2.3.1 binlog_transaction_dependency_tracking 设置为 writeset   

  set global binlog_transaction_dependency_tracking = WRITESET

  2.3.2 插入不相同的数据

  insert into t1 select 1,'a';
  insert into t1 select 2,'b';

  2.3.3 解析binlog

  mysqlbinlog --base64-output=decode-rows -vv /data/mysql/mysql_3306/logs/bin.000004 > 4.sql

  内容如下

  ### INSERT INTO `vcyber`.`t1`
  ### SET
  ###   @1=1 /* INT meta=0 nullable=0 is_null=0 */
  ###   @2='a' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */  …………
  #180915  6:19:32 server id 883306  end_log_pos 572 CRC32 0x88c31fe8     GTID    last_committed=1        sequence_number=2       rbr_only=yes
  …………
  ### INSERT INTO `vcyber`.`t1`
  ### SET
  ###   @1=2 /* INT meta=0 nullable=0 is_null=0 */
  ###   @2='b' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */  …………
  #180915  6:19:35 server id 883306  end_log_pos 885 CRC32 0x6901dc68     GTID    last_committed=1        sequence_number=3       rbr_only=yes

   可以看到修改了不相同的数据,last_committed 都是相同的。为了比较不同,把 binlog_transaction_dependency_tracking 修改回 COMMIT_ORDER   

  2.3.4 binlog_transaction_dependency_tracking 设置为 COMMIT_ORDER

  set global binlog_transaction_dependency_tracking = COMMIT_ORDER

  2.3.5 插入不相同的数据

  insert into t1 select 11,'aa';
  insert into t1 select 12,'bb';

  2.3.6 解析binlog

  ### INSERT INTO `vcyber`.`t1`
  ### SET
  ###   @1=11 /* INT meta=0 nullable=0 is_null=0 */
  ###   @2='aa' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */
  …………
  #180915  6:30:30 server id 883306  end_log_pos 575 CRC32 0x24a2ace1     GTID    last_committed=1        sequence_number=2       rbr_only=yes
  …………
  ### INSERT INTO `vcyber`.`t1`
  ### SET
  ###   @1=15 /* INT meta=0 nullable=0 is_null=0 */
  ###   @2='ee' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */
  …………
  #180915  6:30:34 server id 883306  end_log_pos 891 CRC32 0xe3bb6418     GTID    last_committed=2        sequence_number=3       rbr_only=yes

   两个事物的 last_committed 不相同,到从库是没有办法并行复制的。

 

   那么 writeset 最多可以并行执行多少个事务呢?假设插入 30000 条不同记录,统计下 last_committed 有多少个,就可以证明可以并行执行多少个事务,为了少啰嗦一点,我就直接贴出我的结果

   12500 last_committed=1
   12501 last_committed=12501
    4997 last_committed=25002

   看来好像最多可以并发执行 12500 事务。12500 这个数字接近 binlog_transaction_dependency_history_size 这个参数的一半

   binlog_transaction_dependency_history_size:哈希表可以存储的最大大小

   为了证明猜想,将 binlog_transaction_dependency_history_size 设置为 50000 又会怎么样

   25000 last_committed=1
   25001 last_committed=25001
    9997 last_committed=50002

  看起来好像确实接近于 binlog_transaction_dependency_history_size 的一半。

 

posted @ 2018-09-15 18:46  Ziroro  阅读(2227)  评论(0编辑  收藏  举报