首先,要开启这个并行复制,需要设定slave_parallel_workers参数,这个参数如果设定成0的话代表不使用并行,relaylog由sql线程执行,表现和之前版本一致。当这个参数设置成n时,会有n个worker线程,由它来执行event,原来的sql变成coordinator线程,由它来读取relaylog,并按照一定规则将读到的event分配给worker线程执行,从这里可以看出,如果slave_parallel_workers被设置成1的话不仅不会增加效率,相反还会有所下降。
我们先来看简单的情况,即不涉及多库操作。对于某一个库来说,它会被绑定到第一个执行它的线程上,这里的绑定不是说以后该数据库的事件都会由该线程执行,还受制于另一个条件:coordinator线程分配事件时以事务为单位,一个事务会分配给该事务中第一个库所绑定worker线程,不会被拆分。如果遇到一个新的库,不能按照上面的规则决定执行的数据库的(即没有绑定线程,而且是该事务中第一个库)则会寻找绑定库最少的worker线程来执行它。
再来看涉及多库操作的语句,在分配这个语句时,coordinator线程会等待这些库的绑定线程都执行完毕,然后再分配这个语句。而如何涉及到的库太多(大于254)或者是一个ddl语句,则会触发一次同步操作,即等待所有线程执行完毕,然后将它分配给0号worker线程。
每一个worker线程都有一个任务队列,所谓的分配事件也就是coordinator线程将该事件加入某个worker的任务队列中,而所有worker的队列中事件的总长度是有一个上限的,这个上限由slave_pending_jobs_size_max决定,它默认为16M,也就是说最多有16M的relaylog在worker的执行队列中。
在这整个过程中,coordinator线程会保存一个位置,这个位置表示最后一个被连续执行的事务的结束位置,也就是说在这个点以前的所有relaylog都已经被执行了。这个点在代码中被称为lwm(Low-Water-Mark)或者checkpoint,coordinator会定期计算这个点的位置。那么它是如何算出这个位置的呢,首先,coordinator将上个checkpoint之后的所有事务的信息保存在gaq(global assigned queue)中,其次,worker保存了一个bitmap,标志着所有由它执行的事务,这样coordinator就可以通过遍历所有worker线程来计算出当前的lwm。这个点被更新以后,coordinator会给worker置一个标志,worker在执行完一个事务以后就会根据这个标志来右移那个bitmap。由于这些信息会被用来在程序意外退出以后的恢复,因此他们会被保存到磁盘上,此外,checkpoint之后的事务数是有上限的,这个值由slave_checkpoint_group(默认为512)来决定,超过这个值的事务不会被继续分发。
至于crash后的恢复,其实比较简单,恢复过程中由coordinator线程(或者叫它sql更合适)串行执行上个保存的checkpoint后面的事务,根据worker保存的记录,当一个事务被执行过了的话那么就跳过它,在这个恢复过程中,MySQL是可以正常提供服务的。当所有上次分配了但是没有被执行的事务全部执行结束以后就会恢复原来的流程。
参考:http://tencentdba.com/blog/mysql-5-6_multi_thread_slave_replication/