众所周知,MySQL5.7对于半同步增强的其中一个部分是对ack确认动作的改进。在5.6下的半同步的ack确认是在storage commit之后,这就带来了两个问题
首先,MySQL5.7增强半同步将ack确认放到了引擎层提交之前,这样在等待ack的过程中,主库上的其他会话对该数据是不可见的,这就避免了以上的第一个问题。其次,master必须等待接收到ack响应后引擎层提交,也就是说从库必须接收到binlog存入relay log以后,这个动作才算完成,这就保证了从库不会丢数据。
以上是我们的常规认识,但是细想一下,增强半同步是不是真的就完美了呢?
要解释以上问题,那么不得不先讲下binlog提交的三个阶段
知道了以上的知识以后,我们再来看第一个问题,这个问题实际上就变成了master产生binlog后是在落盘前通知dump thread还是在binlog落盘之后通知dump thread。
查看下binlog提交的源码(摘自重庆八怪)
flush阶段
flush_error= process_flush_stage_queue(&total_bytes, &do_rotate,&wait_queue); //进行binlog的从binlog buffer或者临时文件写入到binlog文件(注意是写到kernel buffer还没做fsync),同时触发innodb的组提交逻辑,innodb组提交的逻辑代码是阿里的印风兄写的,我请教过他。 update_binlog_end_pos_after_sync= (get_sync_period() == 1); //sync_binlog参数 如果为1则为真如果不为1则为假 if (!update_binlog_end_pos_after_sync) //如果sync_binlog=1则 这里不发信号给dump 如果不是1则发信号进行dump update_binlog_end_pos();
sync阶段
if (flush_error == 0 && total_bytes > 0) //这里进行sync binlog,
{
DEBUG_SYNC(thd, "before_sync_binlog_file");
std::pair<bool, bool> result= sync_binlog_file(false);
sync_error= result.first;
}
if (update_binlog_end_pos_after_sync) //如果sync_binlog = 1 这里才发送信号给dump线程通知进行发送binlog
{
THD *tmp_thd= final_queue;
while (tmp_thd->next_to_commit != NULL)
tmp_thd= tmp_thd->next_to_commit;
if (flush_error == 0 && sync_error == 0)
update_binlog_end_pos(tmp_thd->get_trans_pos());
}以上代码可以比较清楚的知道,只有当sync_binlog !=1的时候发信号给dump thread,而在sync_binlog=1的时候,那么通知dump thread的动作会发生在sync阶段,即binlog在master上落盘以后
实验过程如下(sync_binlog=1):
在slave上
stop slave io_thread;
在master上
(root:db1@xucl:22:49:17)[xucl]> begin;insert into t values(13);commit;Query OK, 0 rows affected (0.00 sec)Query OK, 1 row affected (0.00 sec)
此时session会卡在等待ACK响应上,再看master的binlog位置点
可以看到binlog位置点已经发生变化,并且已经落盘了,但是session还是处在等待ack上,也就验证了我们之前的说法。
针对问题2,我们先以实验开头
同样先停止slave上的io_thread且sync_binlog=1的情况下
session1(master)
session2(master)
接着kill掉session1
通过show processlist可以发现,虽然session1被kill了,但是session1并没有回滚,而是会继续等待超过rpl_semi_sync_master_timeout时间后,半同步会转成异步复制完成提交动作。在这个过程中,被kill的会话仍然持有commit mutex,并且会阻塞其他会话更新,不信我们可以尝试下。
可以看到的是thread 403这个会话的状态处于query end的状态,这个状态的意思就是卡在了2pc提交阶段,具体在哪个阶段呢?还是参考了八怪的文章,这里的线程id为403的会话其实是卡在了MYSQL_BIN_LOG::change_stage函数上,因为它无法获取到commit mutex,因此也无法完成2pc提交。
简单示意过程如下
| session1(master) | session2(slave) | session3(master) |
|---|---|---|
| - | stop slave io_thread; | |
insert into t values(16); | - | - |
| - | - | kill session1; |
Waiting for semi-sync ACK from slave状态为被killed | - | 此时16并不可见 |
超过rpl_semi_sync_master_timeout后(我这里设置200s) | - | 观察到session1已经完成提交,并且16已经可见 |
MySQL对于会话断开处理如下:
MySQL时刻去检查线程的状态,如果有埋点的情况下,会进入到
func_exit,进行相应的回滚处理。这也就解释了我们上面的实验过程,在到了commit阶段以后,没有这样的埋点处理,因此也就会继续等待ack响应,达到超时以后最终完成引擎层提交。
innodb_flush_log_at_trx_commit及sync_binlog都为1参考文章:https://yq.aliyun.com/articles/491719
最后,还要感谢重庆八怪和杨一老师的指导
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69917356/viewspace-3158631/ ,如需转载,请注明出处,否则将追究法律责任。