注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

网易杭研后台技术中心的博客

 
 
 
 
 

日志

 
 

MariaDB-10.0的并行复制实现方案  

来自Longhui   2013-11-29 14:51:06|  分类: MySQL |举报 |字号 订阅

  下载LOFTER 我的照片书  |

MariaDB-10.0版本开始支持Replication功能slave端的并行复制功能(如果你还不知道Replication是什么东西建议你先找些资料了解一下)。我们知道在这之前Replication中的slave一直是单线程同步master上的数据,所以一旦master上的写压力增大导致产生的日志量骤增通常都会导致slave端的数据落后于master。业界想了很多的办法来提供slave端的同步速度,比如最有名的属MySQL5.6开始支持的并行复制功能,不过它的并行复制方式只支持schema间数据更新的并行,这对于现有的很多应用来讲几乎是没什么用的,因为一个应用的数据通常是存放在一个schema中。其次比较有名的是淘宝实现的基于Row格式binlog的并行复制功能,这套方案曾经一度非常的热门,mariaDB差点就将它整合进去了(直到现有方案的出现)。这点个人还是很佩服淘宝在这方面的努力的,尽管它最终没被mariaDB整合但它给一度漆黑无望的MySQL同步问题带来了无限曙光,并且相比于mariaDB的并行复制方案它还是有一些优势的(本人在另一篇博文中有对淘宝该方案的介绍)。然后还有我们InnoSQL所做的slave端事务批量提交功能,一个相对很小的改动带来了slave端同步速度的成倍提高。说到这里你可能会问,既然有这么多的方案mariaDB为什么弄套新的出来?因为所提的这些方案都没有完美的解决slave的同步问题,而我要讲到的mariaDB的方案才是最终的方案完美的方案,用老美的原话讲叫: this challenge is now gone!!问题一去不复返了!而这套方案的基本原理非常的简单:一次group commit中提交的所有事务理所当然的可以并行执行。如果你还不了解什么是group commit你可能不会了解那种震撼,欣喜,又稍稍带点悔恨的错综复杂的感受,反正我第一次看到这话的时候顿时石化了两秒钟,这不正是我们苦苦寻找的并行复制的解决方案。。。竟然可以这么简单!基于这个原理MySQL5.7.2也迅速推出了她的另一套并行复制方式,在她的release Notes中给出了一个参数叫 slave-parallel-type,若将它设置成LOGIC_CLOCK就可以使用新的并行复制方式。再说回到MariaDB的并行复制方案,它的实现者名为Kristian Nielsen,这位正是group commit方案的实现者,是位名副其实的大神。

MariaDB-10.0支持全局事务ID,这个全局事务ID是一个结构变量,组成成员是:

{

     domain_id

     Server_id;

     seq_no;

     commit_id

}

MatiaDB-10.0中支持domain(域)的概念,而复制过程中不同domain中的数据修改是可以并行执行的。Domain是一个逻辑概念,由用户来保证不同domain中的数据更新互不影响。在slave端不同Domain中的数据更新可以并行执行,并且也不用保证提交的先后顺序。比较典型的一个应用场景是多主复制,不同的主指定不同的domain id这样它们在slave上的数据更新就可以并行执行了。Commit_id是事务在master提交阶段指定的提交id号,这是一个递增的值,每次提交都不一样,而在group commit中所有被group的事务所指定的commit id都是相同的。同一次group的所有事务在slave端可以并行的执行,不同group的事务则是不可以的。Slave在执行这些事务的更新的时候要保证事务的提交顺序与master上提交顺序相同。所以并行复制的原则是:

1)domain_id不同,事务可以并行执行,且不用考虑事务的提交顺序。

2)domain_id相同commit_id不同,事务不可以并行执行。

3)domain_is相同commit_id相同,事务可以并行执行,但是要保证提交的顺序。

  为了实现并行复制功能,MariaDB添加了如下几个变量。

1)添加了一个并行复制的整体控制对象rpl_parallel

2)添加了一个线程池的管理对象rpl_parallel_thread_pool

3)为每个执行线程添加了一个管理对象rpl_parallel_thread

4)为每个域的数据更新添加了一个管理对象rpl_parallel_entry

5)为每个事务添加了一个管理对象relay_group_info

6)为每个log event添加了一个管理对象queued_event

它们之间的关系是:线程池中包含了若干个执行线程;一个域(domain)中包含了很过个事务,一个事务中包含了很多个log event,每一个log event被分发到某个执行线程中去执行。由于一个domain只对应一个rpl_parallel_entry对象,所以在并行复制的总体管理结构rpl_parallel中有一个HASH表成员,表中记录了已经执行了的域domain_id值以及它的rpl_parallel_entry对象。

MatiaDB-10.0中对binlog的内容也做了改变,每个事务开始前都添加了一个gtid_event,该event中记录了事务的domain_idcommit_id

事务在slave端开始执行的时候都会被赋予一个sub_id,该值是递增的。它的作用主要是用来保证同一个域中的事务顺序执行和顺序提交。对于不同commit_id的事务需要顺序执行,而对于相同commit_id的事务需要顺序提交。以下图为例:

MariaDB-10.0的并行复制实现方案 - 网易杭研后台技术中心 - 网易杭研后台技术中心的博客

  每个块代表一个事务,块中的数值代表它们的sub id,事务1,2,3commit id相同都是1,而事务4,5,6commit id都是2。因此事务1,2,3可以并行执行,同理事务4,5,6也可以。但是事务4,5,6开始执行前需要得到事务3执行提交完毕。所以每个事务的管理结构rgi中都记录了以下几个变量:

struct rpl_group_info

{

  gtid_sub_id; //事务的sub id

  wait_start_sub_id; //开始执行前需要等待此事务提交完毕

  wait_commit_sub_id; //开始提交前需要等待此事务提交完毕

}

以事务5为例,它的gtid_sub_id5wait_start_sub_id3wait_commit_sub_id4。由于一个domain中的事务才需要维护这样的顺序,因此在对象rpl_parallel_entry中有以下几个变量:

struct rpl_parallel_entry {

  domain_id;

  last_commit_id;  //上一个事务的commit_id,用来判断是否可以并行执行

  last_committed_sub_id; //最新一个提交事务的sub_id

  current_sub_id;  //当前正在执行的事务的sub id

  uint64 prev_groupcommit_sub_id; //上一个group commit的最后一个事务的sub id

}

再以上图为例,若当前执行的事务是5,则它的last_commit_id2current_sub_id5prev_groupcommit_sub_id。事务5开始执行前必须等到last_committed_sub_id3,而它要提交前必须等到last_committed_sub_id

MariaDB-10.0SQL线程不再负责具体执行log event,而是将它丢到线程池中去执行。当SQL线程读到一个log event之后,分以下几种情况进行处理:

一,读到的是一个gtid_event,该event记录在每个事务开始的地方,标志着一个新的事务。处理的过程是:

   1)获取gtid_event中的domain id,根据domain idrpl_parallel对象的HASH表中查找对应的记录,如果能够查到则返回该domainrpl_parallel_entry对象,如果找不到则新建一个domainrpl_parallel_entry对象插入到HASH表中。

   2)如果gtid event中的commit idrpl_parallel_entry中的last_commit_id相同,则表示该事务与上一个事务是在同一次group commit中提交的,可以并行执行。从线程池中分配一个空闲的线程来给该事务执行。具体是将新分配的线程执行赋给rpl_parallel_entry对象的rpl_thread成员。

   3)否则,获得rpl_parallel_entry对象的rpl_thread成员,既该domain正在使用的线程指针。若rpl_threadNULL,则从线程此中重新分配一个线程使用;如果rpl_thread不为空,但是rpl_thread->current_entry不等于当前的rpl_parallel_entry对象,表示该线程已经重新分配给了其他域使用,则需要重新从线程池中分配新的线程;否则直接使用rpl_thread的线程执行。

当前的rpl_parallel_entry对象赋给rpl_parallel->current,表示并行复制当前正在处理的域。

二,如果读到的是以下类型的event

    case START_EVENT_V3:

    case STOP_EVENT:

    case ROTATE_EVENT:

    case SLAVE_EVENT:

    case FORMAT_DESCRIPTION_EVENT:

    case INCIDENT_EVENT:

    case HEARTBEAT_LOG_EVENT:

    case BINLOG_CHECKPOINT_EVENT:

case GTID_LIST_EVENT:

或者,rpl_parallel->current为空。则表示该log event不属于如何事务,直接在主SQL线程中执行即可。

三,不是上述两类的event,则表示是属于一个事务的event,那么它应该等待该事务之前的event都执行完了才能够执行。获得rpl_parallelcurrent变量,因为一个事务没执行完domain是不会变换的,所以可以直接使用current对象。获得该对象的rpl_thread值,如果该值不为空则将event交给rpl_thread的线程去执行,否则重新分配一个线程来执行该event

 

MariaDB线程池的管理:

并行复制中有一个包含若干个线程的线程池,线程池中的线程数可以动态的设置。一个线程管理结构包含了以下几个成员:

struct rpl_parallel_thread {

   current_entry; //当前正在执行的domain管理结构。

   *event_queue; //等待执行的event的链表。

... ...

}

SQL主线程在读取到一个log event之后就可以马上寻找一个线程来执行,而不用等到事务的所有线程都读取出来。

一个线程的大体执行过程是:

1)如果event链表是空的,则进入休眠,并等待主SQL线程唤醒。

2)获得event链表上的第一个event,如果它是一个gtid event,表示是一个新的事务要开始则需要进行如下操作:

   1while (wait_start_sub_id > entry->last_committed_sub_id) { mysql_cond_wait....}

      该事务等待wait_start_sub_id事务提交之后才开始执行。

   2if (wait_for_sub_id > entry->last_committed_sub_id)

      {

         wait_for_commit *waitee=

           &rgi->wait_commit_group_info->commit_orderer;

         rgi->commit_orderer.register_wait_for_prior_commit(waitee);

      }

      如果该事务A要等待提交的事务B还没有提交结束,则将A标记为需要等待B先提交,这样加入A先执行到了提交阶段它就会等待B先提交完毕。

3)如果是其他类型的event,则可以直接的执行。

  最后执行event,并更新rgi->rli中的执行位置。

 

后记:

  本文主要介绍了MariaDB并行复制的执行的实现过程,可以看到即使是一个事务中的不同log event该方案都能做到并行执行,并行的效率相当的高。当然并行过程中还有其他功能的实现,比如事务的顺序提交,这也是一个相当复制的实现过程,将在另一篇博文中介绍。

  评论这张
 
阅读(3214)| 评论(1)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017