用户线程
事务提交
MYSQL_BIN_LOG::ordered_commit
一阶段 flush binlog 后,执行semi插件 调用repl_semi_report_binlog_update
file_name_ptr: binlog 文件名
flush_end_pos: flush binlog时的大小
-> if (RUN_HOOK(binlog_storage, after_flush,(thd, file_name_ptr, flush_end_pos)))
->repl_semi_report_binlog_update
->lock() LOCK_binlog_
记录当前的binlog 的 文件名 和 大小
->repl_semisync.writeTranxInBinlog(log_file,log_pos);
设置为半同步后如果是第一次写,则不比较大小,commit_file_name_inited_ 更新为true
仅在次数更新为ttrue,其他修改为false的地方,都是关闭或者开启
比较存储信息是否比当前的小,如果小,则更新存储的值commit_file_pos_
->int cmp = ActiveTranx::compare(log_file_name, log_file_pos,commit_file_name_, commit_file_pos_);
存储当前binlog 的文件 和大小
->active_tranxs_->insert_tranx_node(log_file_name, log_file_pos)
分配一个节点
->ins_node = allocator_.allocate_node();
当前的binlog 的节点放到链表的尾部
-> trx_rear_->next_ = ins_node;
trx_rear_= ins_node;
trx_front_ 为链表头
根据binlog的文件和文件大小生成hash值,放到hash数组中
->hash_val = get_hash_value(ins_node->log_name_, ins_node->log_pos_);
ins_node->hash_next_ = trx_htb_[hash_val];
trx_htb_[hash_val] = ins_node;
sync binlog文件
->std::pair<bool, bool> result= sync_binlog_file(false);
判断当前binlog的内容是否已经发送到从库且返回应答,如果没有从库返回应答则阻塞
->sync_error= call_after_sync_hook(commit_queue);
->repl_semi_report_binlog_sync
->repl_semisync.commitTrx(log_file, log_pos)
根据当前的文件与大小,找到链表中存储的节点
-> entry= active_tranxs_->find_active_tranx_node(trx_wait_binlog_name,trx_wait_binlog_pos);
->while(1) 循环
判断已返回文件的大小与当前文件的大小,主要是判断是否需要wait 等待
int cmp = ActiveTranx::compare(reply_file_name_, reply_file_pos_,
trx_wait_binlog_name, trx_wait_binlog_pos);
如果已经返回应答(cmp>0),则退出循环,把binlog的文件名与大小从等待节点链表中移除
如果没有返回,则继续下面的逻辑 如果当前等待的文件偏移还大于当前文件则偏移,再更新为最小的,即使用当前的
int cmp = ActiveTranx::compare(trx_wait_binlog_name, trx_wait_binlog_pos,
wait_file_name_, wait_file_pos_);
wait_file_name_inited_更新为 为true
等待ack线程信号触发(即收到了从库的应答)
wait_result= mysql_cond_timedwait(&entry->cond, &LOCK_binlog_, &abstime);
如果有异常或等待超时则自动关闭半同步
switch_off();
当流程执行完毕后,清理当前节点
active_tranxs_->clear_active_tranx_nodes(trx_wait_binlog_name,trx_wait_binlog_pos);
半同步完成,继续第三阶段的提交
主库ack应答线程
主库执行set global rpl_semi_sync_master_enabled=1;
设置为1时,启动ack线程
->fix_rpl_semi_sync_master_enabled
初始化一些类
->repl_semisync.enableMaster()
创建ActiveTranx,主要是等待链表的一些操作
->active_tranxs_ = new ActiveTranx(&LOCK_binlog_, trace_level_);
初始化一些变量
commit_file_name_inited_ = false;
reply_file_name_inited_ = false;
wait_file_name_inited_ = false;
修改状态
set_master_enabled(true);
开启ack线程
->ack_receiver.start()
修改状态
m_status= ST_UP;
创建线程
mysql_thread_create(key_ss_thread_Ack_receiver_thread, &m_pid,
&attr, ack_receive_handler, this))
->ack_receive_handler
->Ack_receiver::run()
如果是第一个从库,则等待从库加入
wait_for_slave_connection();
如果已有从库,则把从库添加到m_fds数组中
if (!listener.init_slave_sockets(m_slaves))
把从库的fd 添加到poll中
ret= listener.listen_on_sockets();
遍历从库的socket ,读取所有从库发来的信息
while (i < listener.number_of_slave_sockets() && m_status == ST_UP)
net_clear(&net, 0);
读取socket中的数据
len= my_net_read(&net);
处理收到数据的逻辑
repl_semisync.reportReplyPacket(slave_obj.server_id,net.read_pos, len);
解析收到数据的报文
log_file_pos = uint8korr(packet + REPLY_BINLOG_POS_OFFSET); 文件大小
log_file_len = packet_len - REPLY_BINLOG_NAME_OFFSET;文件名长度
strncpy(log_file_name, (const char*)packet + REPLY_BINLOG_NAME_OFFSET, log_file_len);文件名
对当前的文件名与文件大小做触发激活等待的线程的相关逻辑
handleAck(server_id, log_file_name, log_file_pos);
lock();
如果有一个从库,log_file_pos 从库应答已经确认收到的文件的大小
reportReplyBinlog(log_file_name, log_file_pos);
如果半同步是关闭了,试图根据收到的文件大小重新开始半同步
try_switch_on(log_file_name, log_file_pos);
如果收到的文件大小 大于提交的文件的大小 则开始半同步的状态
int cmp = ActiveTranx::compare(log_file_name, log_file_pos,
commit_file_name_, commit_file_pos_);
更新reply_file_pos_,把从库发来的报文解析的log_file_pos 更新到reply_file_pos_
cmp = ActiveTranx::compare(log_file_name, log_file_pos,
reply_file_name_, reply_file_pos_);
从库发送已经确认的reply_file_pos_ 是否已经大于 当前线程等待的binlog 的 wait_file_pos_ 大小
如果是第一次,则设置reply_file_name_inited_为true
cmp = ActiveTranx::compare(reply_file_name_, reply_file_pos_,
wait_file_name_, wait_file_pos_);
如果已经响应的binlog大小reply_file_pos_ 大于 等待的binlog大小wait_file_pos_
则需要更新为wait_file_name_inited_为false
如果大于则激活等待的线程
active_tranxs_->signal_waiting_sessions_up_to(reply_file_name_, reply_file_pos_);
int cmp= ActiveTranx::compare(entry->log_name_, entry->log_pos_, log_file_name, log_file_pos) ;
遍历所有的等待的线程,凡是等待的binlog 偏移 小于从库应答的binlog的小,则依次激活等待的线程
while (entry && cmp <= 0)
mysql_cond_broadcast(&entry->cond);
entry= entry->next_;
if (entry)
cmp= ActiveTranx::compare(entry->log_name_, entry->log_pos_, log_file_name, log_file_pos) ;
如果有多个从库,log_file_pos 从库应答已经确认收到的文件的大小找一个最小的
ackinfo= ack_container_.insert(server_id, log_file_name, log_file_pos);
reportReplyBinlog(ackinfo->binlog_name, ackinfo->binlog_pos);
主库dump线程调用semi的接口为
主要接口函数
repl_semi_binlog_dump_start, // start
repl_semi_binlog_dump_end, // stop
repl_semi_reserve_header, // reserve_header
repl_semi_before_send_event, // before_send_event
repl_semi_after_send_event, // after_send_event
repl_semi_reset_master, // reset
dump 线程
com_binlog_dump
mysql_binlog_send
Binlog_sender::run()
Binlog_sender::init
调用semi 插件,m_start_file 从库发送的binlog文件名 (char*)thd->get_protocol_classic()->get_raw_packet()
m_start_pos 从库发送的binlog文件大小即从库收到主库的binlog大小 thd->get_protocol_classic()->get_packet_length()
if (RUN_HOOK(binlog_transmit, transmit_start,(thd, m_flag, m_start_file, m_start_pos,
&m_observe_transmission)))
repl_semi_binlog_dump_start
判断当前线程是否半同步,有从库设置见 repl_semi_slave_request_dump SET @rpl_semi_sync_slave= 1 session级别的参数
if (semi_sync_slave != 0)
把从库建立的链接信息添加到ack线程中
ack_receiver.add_slave(current_thd)
连接信息
slave.vio= *thd->get_protocol_classic()->get_vio();
与ack线程相对应listener.init_slave_sockets(m_slaves)
m_slaves.push_back(slave);
mysql_cond_broadcast(&m_cond); 激活ack线程 与 wait_for_slave_connection对应
/* 从库个数计数器 One more semi-sync slave */
repl_semisync.add_slave();
第一次建立链接后,与从库确认信息
repl_semisync.handleAck(param->server_id, log_file, log_pos);
至此 与从库semi 关系建立完成
每次新打开一个binlog文件之前,需要往从库发送rotate信号,从库relay文件需要新建一个文件
fake_rotate_event(log_file, start_pos)
reset_transmit_packet
生成一个的header 用户semi 发送消息时的校验信息
m_packet[0]='\0'
RUN_HOOK(binlog_transmit, reserve_header, (m_thd, flags, &m_packet))
Binlog_transmit_delegate::reserve_header
repl_semi_reserve_header
repl_semisync.reserveSyncHeader(header, size)
memcpy(header, kSyncHeader, sizeof(kSyncHeader)); kSyncHeader={0xef,0} ReplSemiSyncBase::kPacketMagicNum = 0xef
hlen= sizeof(kSyncHeader); hlen = 2
packet->append((char *)header, hlen) hlen = 2 header={0xef,0} packet为 {0,0xef,0} len=3
即repl_semi_reserve_header函数作用为异步复制发送的消息添加了3字节的报文头
Binlog_sender::read_event
Binlog_sender::reset_transmit_packet
RUN_HOOK(binlog_transmit, reserve_header, (m_thd, flags, &m_packet))
异步复制发送数据之前,判断当前消息是否需要从库发送消息应答 packet为 {0,0xef,1} 此消息需要应答 packet为 {0,0xef,0} 此消息不需要应答
repl_semi_before_send_event
repl_semisync.updateSyncHeader
判断从库需要发送应答的依次条件
当前的发送的文件大小 大于 已保存reply_file_pos_的大小 则 需要应答 reply_file_pos_在此函数中更新reportReplyBinlog
cmp = ActiveTranx::compare(log_file_name, log_file_pos,
reply_file_name_, reply_file_pos_);
且 当前的发送的文件大小大于已保存wait_file_pos__的大小 或wait_file_name_inited_为false 则需要应答,
wait_file_pos_ 在此函数中更新 commitTrx
cmp = ActiveTranx::compare(log_file_name, log_file_pos,
wait_file_name_, wait_file_pos_);
且 当前当前的发送的文件大小大于已保存 等待链表中 在MYSQL_BIN_LOG::ordered_commit中flush binlog之后 repl_semi_report_binlog_update
sync = active_tranxs_->is_tranx_end_pos(log_file_name,
log_file_pos);
或者 当前的发送的文件大小 大于 已保存 commit_file_pos_ 的大小 或 wait_file_name_inited_ 为false 则需要应答
commit_file_pos_ 在此函中ReplSemiSyncMaster::writeTranxInBinlog 更新
int cmp = ActiveTranx::compare(log_file_name, log_file_pos,
commit_file_name_, commit_file_pos_);
(packet)[2] = kPacketFlagSync;
异步复制发送数据后
Binlog_sender::send_events
repl_semi_after_send_event
ReplSemiSyncMaster::readSlaveReply
net_flush 把报文发送到从库
从库semi 接口函数
repl_semi_slave_io_start, // start
repl_semi_slave_io_end, // stop
repl_semi_slave_request_dump, // request_transmit
repl_semi_slave_read_event, // after_read_event
repl_semi_slave_queue_event, // after_queue_event
start slave; 启动从库
repl_semi_slave_io_start
ReplSemiSyncSlave::slaveStart
设置半同步
rpl_semi_sync_slave_status= 1;
repl_semi_slave_request_dump
读取主库是否开始半同步
query= "SELECT @@global.rpl_semi_sync_master_enabled";
设置连接主库的session 此连接执行半同步状态
query= "SET @rpl_semi_sync_slave= 1";
从库读取数据的线程
handle_slave_io
RUN_HOOK(binlog_relay_io, after_read_event,
(thd, mi,(const char*)mysql->net.read_pos + 1,
event_len, &event_buf, &event_len))
Binlog_relay_IO_delegate::after_read_event
repl_semi_slave_read_event
repl_semisync.slaveReadSyncHeader
*need_reply = (header[1] & kPacketFlagSync); 判断是否需要回复 1 则回复
*payload_len = total_len - 2; 把报文前两个字节占用的长度剔除
Binlog_relay_IO_delegate::after_queue_event
repl_semi_slave_queue_event
if (rpl_semi_sync_slave_status && semi_sync_need_reply) 从库开启半同步且需要回复
ReplSemiSyncSlave::slaveReply
生成需要应答的信息
reply_buffer[REPLY_MAGIC_NUM_OFFSET] = kPacketMagicNum; 校验码
int8store(reply_buffer + REPLY_BINLOG_POS_OFFSET, binlog_filepos); 文件pos
memcpy(reply_buffer + REPLY_BINLOG_NAME_OFFSET,binlog_filename,name_len + 1 /* including trailing '\0' */); 文件名
my_net_write
net_flush
主库的ack线程负责收应答报文