MySQL逻辑并行复制的实现

来源:互联网 发布:西瓜影音播放网络文件 编辑:程序博客网 时间:2024/06/06 02:31
一、并行复制主库涉及到三个部分
1、binlog类保持两个值
/* Committed transactions timestamp */
Logical_clock max_committed_transaction;
/* "Prepared" transactions timestamp */
Logical_clock transaction_counter;

Logical_clock{offset state}
offset表示上一个binlog最后一个事务的序号
flush logs的时候,在MYSQL_BIN_LOG::open_binlog切换binlog生成新的binlog的时候,以下部分更新
max_committed_transaction和transaction_counter的offset
  /*    At every rotate memorize the last transaction counter state to use it as    offset at logging the transaction logical timestamps.  */  max_committed_transaction.update_offset(transaction_counter.get_timestamp());  transaction_counter.update_offset(transaction_counter.get_timestamp());
max_committed_transaction:表示binlog最近提交的事务,每次组提交完成以后在,finish_commit调用update_max_committed更新max_committed_transaction值
transaction_counter:表示一个序列号,保持binlog中最新的提交事务

2、每个事务保持两个值
int64 last_committed;
int64 sequence_number;
last commit:本次组提交之前上次commit的事务,在事务commit 的binlog prepare 阶段调用store_commit_parent,保存last_committed为binlog的max_committed_transaction
sequence_number:事务在binlog内的序列号,在组提交的flush阶段更新,和1中transaction_counter同步更新,

3、事务提交时,在order commit的flush 阶段,写gtid_log_event的时候会加一个last commit和 sequence_number,当last commit相同时,事务在从库可以并行回放。其中
last commit=事务->last_committed - max_committed_transaction.get_offset()
sequence_number=trn_ctx->sequence_number - clock.get_offset();

二、并行复制从库部分
并行复制是典型的生产者、消费者模式,Coordinator作为生产者,worker线程作为消费者。
sql线程(Coordinator线程)
SQL线程主函数 handle_slave_sql
主要函数:
slave_start_workers() 初始化coorinator和worker线程
exec_relay_log_event:读取relay log并执行
next_event: Log_event::read_log_event()读取event
如果是hot log在rli->relay_log.wait_for_update_relay_log阻塞等待更新
apply_event_and_update_pos:
调用Log_event::apply_event()分发并更新结果,调用en_queue函数
并行复制调用append_item_to_jobs()把event存放到worker线程的队列中,如果加入event后队列的大小超过queue的最大值(slave_pending_jobs_size_max)则要则塞等待,进入"Waiting for Slave Workers to free pending events"的状态。append_item_to_jobs()调用en_queue()入队时,进入Waiting for Slave Worker queue的状态
Log_event::apply_event()
应用需要sql线程执行的event
调用get_slave_worker()获得分配到的worker,同时保存到ev->worker中,get_slave_worker()是主要分发到worker的函数。
调用schedule_next_event(),调用 rli->current_mts_submode->schedule_next_event(rli, ev)判断是否可以并行复制(事务的last_commit下于正在执行的事务中最小的sequence_number),如果不能并行复制会调用Mts_submode_logical_clock::wait_for_last_committed_trx()阻塞进入“Waiting for dependent transaction to commit”的状态,如果可以并行复制马上返回。
事务中间的evenet,调用rli->current_mts_submode->get_least_occupied_worker()获得分配的worker,Mts_submode_logical_clock::get_least_occupied_worker()如果事务中间的event,rli->last_assigned_worker不为NULL,返回rli->last_assigned_worker,否则调用get_free_worker()返回一个空闲的worker,如果没有空闲的worker进入Waiting for slave workers to process their queues状态
get_slave_worker()中把event生成Slave_job_item,插入rli->curr_group_da,在apply_event_and_update_pos()取出来,调用append_item_to_jobs()最终插入相应的worker队列。这里对一个DML,GTID event,QUERY event先放入rli->curr_group_da,并不是马上取。而事务中的event,则每次get_slave_worker()存进去,后面马上就取出来了。
以上过程中,apply_event_and_update_pos()函数中,对于非XID event 在apply_event_and_update_pos中更新
ev->update_pos(rli);更新rli的位置。XID event在apply的时候更新位置信息。
worker线程:
主函数handle_slave_worker
工作函数:slave_worker_exec_job_group,每次执行完一个事务的event,步骤:
pop_jobs_item():从worker的取出item(封装的binlog event)
set_max_updated_index_on_stop()->head_queue()取item,如果为空在会阻塞等待进入stage_slave_waiting_event_from_coordinator状态
Slave_worker::slave_worker_exec_event()
调用Log_event::do_apply_event_worker()->Log_event::do_apply_event()至此真正的应用了主库的binlog
对于一个如图所示的完整事务
1、DML

Coordinator线程处理逻辑如下
一个函数栈
handle_slave_sql->
exec_relay_log_event->
apply_event_and_update_pos->
Log_event::apply_event->
Log_event::get_slave_worker->
1、GTID event,如果可以并行复制,get_slave_worker()中保存到rli->curr_group_da结构中,设置rli->curr_group_seen_gtid= true;
2、Query event,存入 rli->curr_group_da,设置rli->curr_group_seen_begin= true,这时rli->curr_group_da.size() =2
3、Rows_query event,rli->current_mts_submode->get_least_occupied_worker,分配一个空闲的worker,注意这里并没有把event存入 rli->curr_group_da,并设置rli->last_assigned_worker,在apply_event_and_update_pos(),apply_event执行完以后,首先取出rli->curr_group_da的event(共两个GTID Query ),存入分配的worker线程中,之后再把Rows_query 存入worker的队列中
4、Table_map event, 基本同Rows_query ,不过这时 rli->curr_group_da为空了,只需要在apply_event()执行完以后,把Table_map 存入worker的队列中
5、Write_rows 同上
6、Xid 同上
2、DDL

1、GTID event,如果可以并行复制,get_slave_worker()中保存到rli->curr_group_da结构中,设置rli->curr_group_seen_gtid= true;
2、Query event 因为starts_group()=false,所以直接调用rli->current_mts_submode->get_least_occupied_worker分配一个空闲的worker线程。get_slave_worker()返回,取出rli->curr_group_da中的事件,存入worker线程的队列中,之后把这个query event放到worker队列中。
relay-log 中的event有四种执行模式
EVENT_EXEC_PARALLEL:worker线程执行---------正常的事务中的event
EVENT_EXEC_ASYNC:coodinator线程异步执行---------从库的Format_desc event
EVENT_EXEC_SYNC:coodinator线程同步执行-----------Rotate event,主库的Format_desc event
EVENT_EXEC_CAN_NOT:worker线程和coordinator线程都不执行
relaylog中的从库自己的Format_desc由coordinator异步执行,Rotate event和主库的Format_desc even同步指行,要阻塞等待worker线程完成所有的事务以后再执行。从机自己的Rotate和previous_gtids不执行,跳过。
Format_desc apply的时候修改worker线程的fd_change_notified,以便后续执行event的时候利用





原创粉丝点击