前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >半同步复制after_sync模式下的一则客户端断开问题分析

半同步复制after_sync模式下的一则客户端断开问题分析

作者头像
用户1278550
发布2019-08-16 16:45:56
1.2K1
发布2019-08-16 16:45:56
举报
文章被收录于专栏:idbaidba

作者徐晨亮,MySQL DBA,知数堂学员。热衷于数据库优化,自动化运维及数据库周边工具开发,对MySQL源码有一定的兴趣

一、背景

众所周知,MySQL5.7对于半同步增强的其中一个部分是对ack确认动作的改进。在5.6下的半同步的ack确认是在storage commit之后,这就带来了两个问题

  1. master上先做了引擎层提交,再去确认ack,那么master上的其他会话对于改数据是可见的,这就可能引起业务问题
  2. 引擎层已经做了提交,在等待ack响应的间隙,并不能保证binlog已经传输到了slave,这就可能造成slave丢数据的风险

二、那MySQL5.7的增强半同步是如何解决这些问题呢?

首先,MySQL5.7增强半同步将ack确认放到了引擎层提交之前,这样在等待ack的过程中,主库上的其他会话对该数据是不可见的,这就避免了以上的第一个问题。其次,master必须等待接收到ack响应后引擎层提交,也就是说从库必须接收到binlog存入relay log以后,这个动作才算完成,这就保证了从库不会丢数据。

以上是我们的常规认识,但是细想一下,增强半同步是不是真的就完美了呢?

  1. 万一主库binlog未落盘,而slave上已经接收到了binlog,此时master宕机,是不是会造成slave比master多数据呢?
  2. 万一在等待ack响应或者接收到ack后引擎层提交之前,客户端断开了连接又会发生什么呢?

要解释以上问题,那么不得不先讲下binlog提交的三个阶段

  • flush阶段(将redo刷入redo log并刷盘<由参数innodb_flush_logs_at_trx_commit决定>),写入binlog文件<只是写入到os的缓存中>
  • sync阶段(调用fsync,将binlog刷入文件落盘)
  • commit阶段(引擎层完成数据提交,并将binlog信息写入redo log)

问题1:

知道了以上的知识以后,我们再来看第一个问题,这个问题实际上就变成了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:

针对问题2,我们先以实验开头 同样先停止slave上的iothread且sync_binlog=1的情况下 session1(master)

session2(master)

接着kill掉session1

通过show processlist可以发现,虽然session1被kill了,但是session1并没有回滚,而是会继续等待超过rpl_semi_sync_master_timeout时间后,半同步会转成异步复制完成提交动作。在这个过程中,被kill的会话仍然持有commit mutex(LOCK commit),并且会阻塞其他会话更新,不信我们可以尝试下。

可以看到的是thread 403这个会话的状态处于query end的状态,这个状态的意思就是卡在了2pc提交阶段,具体在哪个阶段呢?还是参考了八怪的文章,这里的线程id为403的会话其实是卡在了 MYSQL_BIN_LOG::change_stage函数上,因为它无法获取到commit mutex(LOCK commit),因此也无法完成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已经可见

三、那为什么会话被kill以后并不会回滚呢?

MySQL对于会话断开处理如下:

/* Check thd->killed every 1,000 scanned rows */
    if (--cnt == 0) {
        if (trx_is_interrupted(prebuilt->trx)) {
            ret = DB_INTERRUPTED;
            goto func_exit;
        }
        cnt = 1000;
    }

MySQL时刻去检查线程的状态,如果有埋点的情况下,会进入到 func_exit,进行相应的回滚处理。这也就解释了我们上面的实验过程,在到了commit阶段以后,没有这样的埋点处理,因此也就会继续等待ack响应,达到超时以后最终完成引擎层提交。

四、结论

  1. 为了数据安全性,还是建议设置 innodb_flush_log_at_trx_commitsync_binlog都为1
  2. 采用MySQL5.7的增强半同步(没有理由不选择)
  3. 如果出现增强半同步下并且不允许退化为异步复制的情况下,可以让数据库还是处在等待状态,尽快恢复slave与master的通信,否则master将处于不可用状态

PS:有同学比较关心从库是在什么时候进行ack回复的问题,这里简单给出一下结论:

-> relay_log.append_buffer
    -> after_append_to_relay_log
        ->flush_and_sync(取决于sync_relay_log_period)

-> RUN_HOOK(binlog_relay_io, after_queue_event,
                   (thd, mi, event_buf, event_len, synced))
    这里进入ack的逻辑

从上面看出,半同步下确实需要sync后再进行ack回复,但是落盘还是需要看syncrelaylog

最后放一张八怪的事务提交流程图,可以说写得非常清楚了(图片出自八怪专栏《深入理解MySQL主从原理32讲》15节)


参考文章:https://yq.aliyun.com/articles/491719

-The End-

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 yangyidba 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景
  • 二、那MySQL5.7的增强半同步是如何解决这些问题呢?
  • 问题1:
  • 问题2:
  • 三、那为什么会话被kill以后并不会回滚呢?
  • 四、结论
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档