从机Relay log处理
在5.6之前,数据回放只使用了一个线程,SQL thread SQL线程做了以下一些事情 :
1.next_event(读event):除了正常读事件,它还做了以下事情
A.CRC校验
B.如果一个非当前relay_log 文件读完,就把它删掉
C.如果读到当前relay log的结尾,就睡眠等待IO线程唤醒
2.apply_event apply_event中调用了名为do_apply_event的虚函数来实现多态 具体实现可以看到,如果复制是STAEMENT_BASE 的,那么是在slave上提交语句。如果是ROW_BASED ,其实是交给存储引擎来完成的。另外事务结束时候会在这里就提前更新已完成的relay_log 点位。
3. update_pos 更新已完成的relay_log 点位信息。
性能问题
当master压力很大时候,slave只有单线程在做回放,可以预计的是slave将会落后master很多。解决方法就是让回放也变成多线程。
前提条件:假设我们有办法根据某种规则判定当前事务是否可以和已经在执行的事务并发. 我们就可以把它分发到不同的线程里去做,那样的话我们重放的结构如上,重放步骤:
A.可以并发,并且有worker在空闲,将这个事务分给空闲的worker。
B.如果可以并发,但没有空闲的,就把它扔给任务最少的worker或者等待有worker空出来。
C.只有选择某个worker1才不会和worker2,3冲突.选择2,3都会和1冲突,将任务扔给这个woker1。
D.如果选择任何一个worker都会导致和多个worker冲突,这种情况较为复杂,要看和规则设置关系具体分析
E.如果知道某个事务跟当前执行的所有事务都冲突,就等待当前这批任务全执行完,再进行worker分配。
MySQL5.6 的是按照DB来并发的,在按照DB分配worker之前,需要了解:1.我们读取relay log是以event为粒度的,所以我们事先并不知道一个事务涉及到多少个DB.一个事务的event可能如下图。注意:这里只有TableMap中包含DB信息,每个TableMap中的DB 可能不同。
我们需要有一个维护全局的db-worker的map,还要记录它同时被几个事务使用,所以建立如下图的hash,称之为APH, 这里假设每次每个entry的usage=0就会被移出haah(实际情况稍复杂)
并行回放的结构图变更如下:
C(Coordinator)线程开始读取relay log,遇到tableMap1,获取db信息 db_1,检查APH中是否有对应的db_1,如果没有,从worker中选取任务最少的worker,记为当前事务选取的workerX,将映射关系记录到APH中;如果找到了,将找到的work记为workerX。如果这个事务只涉及到一个db,没有遇到其他tablemap,那么就将事务分配给worker了。如果继续读event读到tablemap2,获取db_2.检查APH. 1.如果在APH中找不到,那么本事务仍然是WorkerX,将建立的关系记入APH. 2.如果在APH找到了db_2 对应的关系为workerY。A.判断,如果workerY和workerX是同一个worker,没有冲突,计数器+1 B. workerY和workerX是不同一个worker,那么读取event等待,知道对应的workerY的所有使用 db_2-> workerY的所有任务全部执行完,才能继续。
简言之,一个事务即使有多个db,也只能分给同一个线程去做
基于DB的并行回放粒度太粗了,而且如果只有一个DB的话,性能没有任何提高,反而会有所下降。
MySQL 5.7 新增了基于logic clock的回放,基本上能满足性能需求。所谓logic clock ,可以认为给事务打了一组标签,标签相同的事务就可以并发,标签不同的事务需要等上一批事务全部执行完毕。下图是GTID中logic clock 真实样子。标签是用64位数字来表示的。
给同一组group commit 事务打上统一的标签,回放的时候可并行回放了 ,last_committed是事务提交时,上次事务提交的编号,如果具备相同的last_committed,说明它们在一个组内,可以并发回放执行
Logic clock
Logic clock实现跟两阶段提交有关。具体打标签实现:MySQL_BIN_LOG 作为两阶段协议的协调者,管理了2个logic clock,也就是两个64位的整数,我们可以称它们max_lc和curr_lc, mysql启动时都为0,其中curr_lc 不能被赋值。
1 prepare:Binlog的prepare(在线程中记录max_lc)Innodb的prepare:略
2 group commit: FLUSH阶段:循环对队列中每个线程的binlog刷盘之前 curr_lc 自增,然后将max_lc 和curr_lc 写入gtid。在进入commit stage之后,将最后的curr_lc赋给max_lc. 这样每一组提交的Gtid中的max_lc都是上一组的事务中最后一个事务的curr_lc,即:binlog里的内容: last_committed 就是max_lc, sequence_number就是curr_lc
网友评论