sync是同步还是非同步_MySQL半同步复制你可能没有注意的点

目录

  1. 问题
  2. 关于半同步复制
  3. 代码层面解释上面几个问题

1 问题

对于MySQL半同步复制,了解MySQL的人,都肯定能说出一二,比如和异步复制、同步复制的差别、超时退化为异步复制和无损复制等,但是这面有以下几个问题(基于MySQL版本为社区版5.7.21):

  • 主库在什么时候调用半同步插件?
  • 主库以什么为单位发送binlog日志?
  • 主库会对二进制日志做额外处理吗?
  • 从库以什么为单位来处理发送过来的日志信息
  • 从库什么时候发送ACK给到主库?
  • 当主库设置了多个ACK后,主库怎么判断收到的ACK的个数,即确认的位置点?

对于上面的几个问题,这面进行简单的回答,所以如果不想看其他部分的,直接看完这一部分就可以了。

  1. 主库在什么时候调用半同步插件?
    MySQL在server层引入了组提交后,为了提高并行度,将提交阶段分为了flush、sync和commit阶段,根据sync_binlog设置的不同,会在flush阶段(sync_binlog设置为非1)或sync阶段(sync_binlog设置为1)以更新binlog位置点的方式通知dump线程发送binlog,而在commit阶段等待从库的ACK应答情况,这面又分为两个不同的等待位置点,即rpl_semi_sync_master_wait_point参数是after_sync还是after_commit。
  2. 主库以什么为单位发送binlog日志?
    主库会以event为单位将binlog日志写入到net_buffer里面,但是并不会立即发送,而是达到条件后一起发送给从库。
  3. 主库会对二进制日志做额外处理吗?
    答案是肯定的,主库读取每个event后,在发送之前都会在event头部添加两个字节的内容,第一个字节是固定的0xef,第二个字节表示是否需要从库收到后发送ACK回包信息,所以对于从库的ACK的回包是以组提交里面一组事务为单位(之前有同事问我ACK确认包以什么为单位,我这面回答是event为单位,羞愧)。
  4. 从库以什么为单位来处理发送过来的日志信息?
    从库接收到主库发送过来的binlog日志后,也是通过event为单位进行读取、根据sync_relay_log设置写入到ralaylog文件里面。
  5. 从库什么时候发送ACK给到主库?
    问题3里面已经简单介绍了主库对event的处理,所以从库在读取发送过来的event后,根据前两个字节判断是否需要发送ACK包。
  6. 当主库设置了多个ACK后,主库怎么判断收到的ACK的个数?
    主库这面维护了一个ACK数组,数组的大小设置为rpl_semi_sync_master_wait_for_slave_count值N-1,当rpl_semi_sync_master_wait_for_slave_count为1时,用不到该数组。数组里面记录了以N-1个以从库的server_id为标识,确认的位置点,当收到一个新的ACK包时,通过server_id进行判断,如果不在数组里面,并且数组已经满了,说明收到了N个确认包,找到最小的确认位置点,等待在这个位置点或者之前的所有连接均可以返回成功。

2 关于半同步复制

下面简单介绍一下半同步复制,MySQL的半同步复制是在MGR出来之前的平衡性能和数据一致性的最好的解决方案,尤其在组提交、无损复制和并行复制出来之后。对于并行复制,writeset的判断方式的出现更是解决了主库并发不高的情况下,从库的并行回放的问题(MySQL在5.7.22和8.0.1里面添加了binlog_transaction_dependency_tracking参数用来标记主库通过什么方式来判断事务是否能在从库并行回放,同事测试发现对于设置成WRITESET会比COMMIT_ORDER TPS损耗10%左右,因为这面如果设置成WRITESET,系统会在原来的基础上多加一次通过hash表last_committed值的计算。而当binlog_transaction_dependency_history_size的值,即hash表的个数设置为默认25000和10时,测试TPS发现,设置为10的TPS却比25000的更高,查看代码发现hash表的实现时通过std::map实现,而std::map是通过红黑树实现,这面会需要对比更多的次数,导致性能差一些,后面估计会改成hash_map来实现,这面不具体说明)。MySQL的半同步插件、GTID等这些特性,使得实现一套高可用的方案更加简单,高可用软件只需要解决主节点可用性的检测、通过GTID的对比选择提升哪个从库为新主和最后一个事务的处理等问题即可。当然实现的过程中,需要注意很多细节,比如半同步插件rpl_semi_sync_master_wait_for_slave_count、rpl_semi_sync_master_timeout值的设置。对于数据一致性要求比较高的业务场景,都会避免半同步因为从库长时间没有回应ACK包退化为异步复制,所以将rpl_semi_sync_master_timeout设置为一个比较大的数字,使得半同步不退化,而对于rpl_semi_sync_master_wait_for_slave_count参数,则需要应场景设置,表示需要多少个从库收到事务的日志主库才返回给客户端成功。虽然半同步插件已经解决了一致性和性能大部分问题,但是相比较于MySQL的复制的最新的解决方案MGR,还是问题比较多,比如多写的问题,可以说MGR是复制的最终解决方案(现在MGR还是不够成熟,之前使用的时候就遇到过因为网络问题,导致某个节点上面的GTID对应的事务和别的节点上面的不同的问题,最终导致数据不一致的问题)。对于开源的高可用解决方案里面借助半同步来做高可用的也是很多,比如青云的分布式数据库RadonDB的下面的存储节点,GitHub上面介绍的图如下:

d461bffae8e29d8e3e83e43bd16e0bec.png

这面Raft算法是借助GTID来进行选主,而不是为了数据和同步日志,日志的同步这面还是用了半同步复制,将rpl_semi_sync_master_wait_for_slave_count设置为1,正常写入后,肯定会有一个从库收到了事务的日志信息,并且将rpl_semi_sync_master_timeout设置为999999,可以认为无限大,不会发生退化为异步的问题。

3 代码层面解释上面几个问题

下面根据MySQL代码解释上面的几个问题,再次强调参考的代码为MySQL社区版5.7.21。

我们先查看主库的处理逻辑,首先查看sql/http://binlog.cc里面的提交的逻辑,下面贴一下主要代码:

int 

可以发现上面对于sync_binlog不同的值,通知dump线程发送binlog的位置点也是不一样的:

  • sync_binlog设置为1: 在sync阶段通知dump 线程发送binlog,这时候binlog确定已经刷盘,因为在flush阶段只是将binlog刷新到了文件缓存,如果发生了宕机,有可能丢失,这个时候如果从库已经收到了,导致从库比主库数据多。
  • sync_binlog设置为非1: 在flush阶段通知dump线程发送binlog。

对于上面说的咋flush阶段调用的Binlog_storage_observer里面的repl_semi_report_binlog_update函数,里面的调用如下:

--

对于dump线程发送binlog日志的逻辑,调用的逻辑如下:

--

下面重点说明一下repl_semisync.updateSyncHeader函数,部分代码如下:

int 

对于从库的接收发送过来的二进制文件,以及处理逻辑,见http://rpl_slave.cc里面的handle_slave_io函数,里面的调用的逻辑为:

--

对于主库的ACK线程的处理逻辑如下:

--

对于上面一直通过文件名和位置点进行对比,会不会有问题呢?答案是否定的,因为binlog的文件名是递增的,如果没有执行reset master命令,这个文件名会一直增加,当然到了超过了最大值之后(2^31-1),会报错,需要重启数据库。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值