官方文档: https://mariadb.com/kb/en/mariadb/parallel-replication


从10.0.5版本开始,MariaDB开始支持并行复制

MariaDB10.0的从服务器能并行的执行查询和复制操作,这篇文章将会解释是如何实现的和你可以做的调优。


注意:主从服务器上的 MariaDB 的版本必须是10.0.5和10.0.5的以后的版本,才能启用并行复制


Parallel replication overview -- 并行复制概述


MariaDB 的复制通过三步完成:

1.从服务的IO线程去主库上读取变更事件,并把读取的事件顺序放到relay log中

2.从服务器的SQL线程一次读取relay log中的一个事件

3.SQL线程依次应用relay log中的事件


MariaDB 10之前的版本中,第三步是通过SQL线程来执行的,这意味着,一次只能执行一个事件,复制本质上是单线程的。

MariaDB 10之后的版本中,第三步可以通过一组相互独立的复制线程,因为可以一次并行复制多个事件,提升了复制性能。


How to enable parallel slave -- 如何开启并行复制

在 my.cnf 指定 slave-parallel-threads = n 作为参数传递给MySQL.

并行复制也可以用于多源复制连接设置 "@@connection_name.slave-parallel-mode" 设置为 none, 即 set @@connection_name.slave-parallel-mode = none


slave_parallel_threads 的值决定了池中会有多少个工作线程会被创建用来在从服务器上执行并行复制事件

如果值为0,默认采用旧的复制方式(即slave的SQL线程来重放event事件)

通常来讲,如果值为非0,应该设置为多源主服务器连接数的至少两倍。一个连接用一个工作线程的意义不大,因为这不但会增加内部线程通讯(即SQL线程和工作线程之间的通讯)的负担,而且一个连接使用一个工作线程的时候,事件不能被并行复制。

slave_parallel_threads 的值可进行动态调整,即可在不重启MySQL的情况下,进行更改,但是更改的时候,所有的从库连接都要被停止。


What can be run in parallel  -- 如何运行并行复制


并行复制的方式有哪些?

in-order 有序 和 out-of-order 无序两种方式

有序方式

并行执行事务,但是对commit顺序进行排序,以确保从库上事务提交和主库上事务提交顺序一致。只有那些被自动确认为不会引起冲突的事务才会被并行执行,也就是说,并行复制对应用来说,是完全透明的。


无序方式

无序意味着在从库上执行和提交事务的顺序跟主库可能不一致,也就是说,应用必须要具备容忍主从服务器上数据更新顺序不一致的问题,同时这种方式要求应用来确保事务之间的没有冲突。无序方式只在GTID模式和应用明确指定使用无序的时候才会被使用, 复制域属于gtid的一部分。


Conservative mode of in-order parallel -- 乐观并行复制模式(optimistic)

Optimistic模式,从MariaDB 10.1.3版本开始支持。


这种模式提供了大量的并行应用slave,同时从应用程序的角度来看,仍然保留精确的事务语义。

开启使用配置选项 --slave-parallel-mode=optimistic.


任何事务DML(插入/更新/删除)可以并行运行,达到限制@@slave_domain_parallel_threads。这可能在slave导致冲突,如果两个事务试图修改同一行,检测到任何这样的冲突,并且两个事务的后者被回滚,允许前者继续, 一旦前者已经完成,后者的事务重新尝试。


Optimistic模式适用于这种方式,因为服务器会发生一些冲突乐观的假定,这额外的工作花了回滚后重新事务冲突时从运行大部分事务获得合理的并行。


有一些启发,试图避免不必要的冲突,如果在master上一个事务执行了一个行锁等待。它不会再并行运行slave,

事务也可以明确在master上标识为潜在的冲突,通过设置变量@@skip_parallel_replication。这种启发式可能在以后加入MariaDB版本(现在还不支持)

这是接下来的选项--slave-parallel-mode 叫 侵略性"aggressive"模式。当这些启发式呗禁用,允许更多的事务将被应用在并行。


非事务DML和DDL不安全的乐观应用并行,因为它不能再冲突的情况下,回滚。因此,在乐观的模式下,非事务性的(如MyISAM)更新不适用于并行或者早期的事件。(不过,可能适用于并联一个MyISAM更新后会更新),DDL语句不适用于任何其他事务,或早或晚。


不同的事务类型可以在mysqlbinlog输出确定,例如:

#150324 13:06:26 server id 1  end_log_pos 6881 GTID 0-1-42 ddl

...

#150324 13:06:26 server id 1  end_log_pos 7816 GTID 0-1-47

...

#150324 13:06:26 server id 1  end_log_pos 8177  GTID 0-1-49 trans

/*!100101 SET @@session.skip_parallel_replication=1*//*!*/;

...

#150324 13:06:26 server id 1  end_log_pos 9836 GTID 0-1-59 trans waited


gtid 0-1-42标明DDL。gtid 0-1-47被标记为非事务DML。而gtid 0-1-49是事务性的DML("trans" 关键字),另外,gtid 0-1-49在master上运行@@ skip_parallel_replication,gtid 0-1-59是事务型的,在master上有一排等待DML锁。("waited" 关键字)。


Conservative mode of in-order parallel -- 保守并行复制(conservative)模式


默认是conservative模式,在10.0版本中唯一可用的模式,可以使用 --slave-parallel-mode=conservative 开启。

在conservative模式中,并行复制在master上使用"组提交“,以发现潜在的并行应用事件的slave,

在mater上如果两个事物提交在一组,他们都写进同一个commit id,此类事件的binlog一定不会互相冲突,他们可以计划安排由复制到运行在不同的线程中。


两个在主上单独提交的事务可能会发生冲突(如,修改同一个表的一行)。因此,应用第二个事务的worker不会立即开始。

但是等到第一个事务开始提交步骤之后,在这一点上,启动第二个事务是安全的,因为它不能再扰乱第一个的执行。


这是mysqlbinlog输出的一个例子,显示gtid events都标有commit id,gtid 0-1-47 没有提交ID,不能并行运行,

gtid 0-1-48和0-1-49有相同的commit id 630,可以在另一个slave上并行复制。


binlog信息如下:

#150324 12:54:24 server id 1  end_log_pos 20052 GTID 0-1-47 trans

...

#150324 12:54:24 server id 1  end_log_pos 20212 GTID 0-1-48 cid=630 trans

...

#150324 12:54:24 server id 1  end_log_pos 20372 GTID 0-1-49 cid=630 trans


在任何一种情况下,当两个事务到达低级别提交的点,并确定提交顺序时,这两个提交以相同的顺序发生在mater上,这样操作对应用程序是透明的。

如果在一个组提交中提交了更多的事务,则可以高度地增加对slave的并行复制的机会。

这个可以调整使用binlog_commit_wait_count和binlog_commit_wait_usec变量,例如,如果应用程序在master可以容忍额外延迟50毫秒,你可以设置binlog_commit_wait_usec = 50000和binlog_commit_wait_count = 20,在一个可用的时间,并行复制可获得高达20的事务处理。

不过小心不要设置binlog_commit_wait_usec太高,因为这可能导致应用程序会放缓,运行大量的串行小事务一个接一个的。


请注意:在master上即使没有组提交的并行性,仍然有个机会,提升并行速度。

由于不同事务的实际提交步骤可以运行。在slave上特别有效,binlog开启(log_slave_updates=1)

甚至如果slave配置崩溃修复安全(sync_binlog=1 and innodb_flush_log_at_trx_commit=1),这种组提交可能在slave上


在--slave-parallel-mode=minimal模式下,仅仅事务提交步骤被并行应用。所有其他事务复制都是连续发生的。


Out-of-order parallel -- 无序并行复制

无序并行复制只使用在GTID模式下,当使用不同的复制域GTID时,复制域是有DBA/应用程序中使用的变量gtid_domain_id.

在并行复制中有两个事务gtids,不同domain_id定于不同的线程,并允许执行完全独立于此,它是应用程序的责任,只为那些真正独立的事务设置不同的domain_ids,

并保证不会相互冲突,应用程序还必须正常工作,即使事务中有不同的domain_id,被视为不同的顺序呢,在slave和master之间,或者在不同的slave中。


由于应用程序可以显式地提供更多并行运行事务的机会。而不是服务器可以自动确定的,出于事务并行复制的顺序并行复制可能自动自己确定自己


一个简单但有效的用法是运行单独的复制域中的长时间运行的语句,如“更改表”。这允许复制其他事务继续进行不间断:

SET SESSION gtid_domain_id=1

ALTER TABLE t ADD INDEX myidx(b)

SET SESSION gtid_domain_id=0


通常,一个长时间运行的ALTER TABLE语句或者 其他查询,将会停止所有后面的事务。会引发slave落后于master至少需要很长的时间去执行这个运行时间很长的查询。

通过无序并行复制,设置复制domain id。是可以避免的。当运行ALTER TABLE时,DBA/应用程序必须确保没有冲突的事务将被复制。


出于无序并行复制的另一个常见的条件,是于多源复制有关的。假设我们有两个不同的master M1 M2,我们利用多源复制S1作为一个M1和M2的slave,S1将收到从M2事件并行M1接收事件。

如果我们现在有三分之一级的奴隶,从S1 S2复制master,我们希望S2也能够应用事件起源于M1与M2平行事件起源于。这可以从顺序并行复制来实现,通过设置在M1和M2 gtid_domain_id不同。


请注意,没有什么特殊限制,可以使用无序的并行复制,这样的操作可以在同一个database/schema,甚至在同一个table中,唯一的限制是,操作必须不冲突,这是他们必须能够以任何顺序应用,并最终获得相同的结果。


当使用了无序并行复制时,

当前slave position就是 master 位置

在master binlog在任何一个时间,成为多维 - 每个复制域可以达到一个不同的点,

当前的position可以在gtid_slave_pos变量看到。当slave被停止、重启、或者切换复制从不同master 使用CHANGE MASTER时,MariaDB自动处理重启每个复制域在binlog适当点。


当--slave-parallel-mode=minimal(或none)模式,无序并行复制是关闭的。


Checking worker thread status in SHOW PROCESSLIST -- 检查显示列表的线程状态

在show processlist中,工作线程将会列出"system user",他们的状态将显示他们目前正在进行的查询,或者它可以显示其中一个:


"Waiting for work from main SQL threads". "等待工作从主要的SQL线程"。

这意味着工作线程空闲,没有工作可用于它的时刻。

"Waiting for prior transaction to start commit before starting next transaction". "等待之前的事务开始下一次事务开始之前提交"。

这意味着,前一批提交的事务在主必须完成第一个。这个工作线程在等待它发生之前,它可以开始工作在下面的一批。

This worker thread is waiting for that to happen before it can start working on the following batch.

"Waiting for prior transaction to commit". ”等待优先事务提交“。

这意味着事务已被工作线程执行了,为了确保订单提交,工作线程等待提交,知道上一次事务准备在它之前提交为止。



Expected performance gain -- 性能预期测试的文章


这里有一篇关于使用并行复制时,改进后高达10倍的性能文章

文章地址: http://kristiannielsen.livejournal.com/18435.html。


slave-parallel-max-queued配置参数:

变量slave_parallel_max_queued是唯一有意义的,前提并行复制技术被使用(slave_parallel_threads参数大于0时),当并行复制被使用时, SQL线程会预读relay-log中的event,队列时间放在内存中,在并行复制中,同时寻找并行执行事件的机会。@@slave_parallel_max_queued变量设置为SQL 线程将在预读多少内存限制的日志,寻找这样的机会。每个线程是有限制的,因此,这次预读的值是设置@@slave_parallel_threads变量值得来的


如果这个值设置的太高,并且slave落后于master太远(如千兆字节的binlog),然后SQL线程可以快速读,填补大量内存的binlog事件比工作线程消耗的更快


另一方面,如果值定的太低,SQL线程可能没有足够的空间来保持足够的事件队列忙的线程,这可能降低性能。


注意,@@slave_parallel_max_queued不是硬性限制,因为binlog事件当前执行总是需要保存在内存中,

例如,每个工作者线程至少有两个事件总是可以在内存中排队,不管slave_parallel_threads是多


通常,slave_parallel_threads应该设置的足够大,SQL线程利用所有可能的并行性可以预读的足够远的binlog,在正常操作中,slave将不希望落后太远,因此不会将大量数据队列有必要的放在内存中的。所以slave_parallel_threads可能设置的相当高(如几百KB)不限制吞吐量。它应该被设置的足够低,slave_parallel_threads * slave_parallel_max_queued 不会造成服务器内存不足。


slave_domain_parallel_threads配置参数:

在所有多源主连接中共享复制工作线程池,在所有可以在并行使用顺序复制的复制域中。

如果一个主连接或者一个复制域 当前正在执行处理一个长时间的查询,可能它将分配在池中的所有工作线程,只能等待让他们长时间运行的查询完成。

拖延任何master主库连接或者复制域,这将不得不等待复制域工作线程变得空闲。


可以通过slave_domain_parallel_threads变量设置的值避免低于slave_parallel_threads设置的值,

当设置不等于0时,一个主连接中的每个复制域都可以保留至多一个时间段的多个线程,

其余的,达到slave_parallel_threads参数的值,在并行复制中无其他主连接或者复制域去使用。


slave_domain_parallel_threads变量是可以动态修改的,可以在不重启服务的情况下更改。所有的slave必须停止掉更改。