mysql默认是异步复制, 但是可以使用半同步插件(semisync_master.so和semisync_slave.so)来做半同步复制, 等待至少N个(rpl_semi_sync_master_wait_for_slave_count默认1)从库收到binlog后,才返回客户端提交成功. 当然超时(rpl_semi_sync_master_timeout默认10秒)后就变成异步了
半同步有两种模式 AFTER_SYNC(默认) 和 AFTER_COMMIT 其实从名字就可以看出来: 前者是在SYNC完成之后的阶段等待从库ACK, 后者是在commit阶段完成之后等待从库ACK
本文主要是用GDB验证半同步等待ACK的阶段. 不会详细看判断过程.
特殊情况也不在模拟了. 感兴趣的自己去模拟.
GDB的使用可以看前面的文章https://cloud.tencent.com/developer/article/2226040
主从搭建(略)完成之后, 主从执行如下SQL即可
install plugin rpl_semi_sync_master soname 'semisync_master.so'; -- 安装半同步插件
install plugin rpl_semi_sync_slave soname 'semisync_slave.so';
set global rpl_semi_sync_master_enabled=1; -- 启用半同步
set global rpl_semi_sync_slave_enabled=1;
set global rpl_semi_sync_master_wait_point=AFTER_SYNC; -- 设置半同步模式为AFTER_SYNC/0
stop slave; -- 从库执行即可. stop slave io_thread;start slave io_thread
start slave;
这一步是可选的, 有的话, 更容易观察
-i ens32 指定网卡为 ens32
not arp 不要arp包
那两IP就是我的主从库的IP
tcpdump -i ens32 'not arp and ((src host 192.168.101.21 and dst host 192.168.101.19) or (src host 192.168.101.19 and dst host 192.168.101.21))' -X
分为两种情况: AFTER_SYNC 和 AFTER_COMMIT
既然叫after_sync, 那就是在sync之后了. 那断点自然是打在 MYSQL_BIN_LOG::flush_cache_to_file
在gdb里执行如下
set substitute-path /var/lib/pb2/sb_1-6473437-1647886122.65/mysql-5.7.38 /root/mysql_source/mysql-5.7.38
break MYSQL_BIN_LOG::flush_cache_to_file
既然是after sync, 那sync阶段直接finish. 然后next往后走
当binlog写完之后, 从库就已经收到数据了, 并返回ACK了. 但我们还是要继续往下看
如果你的线程切换过去了, 你可以使用thread n 切换回来
然后到了commit阶段的call_after_sync_hook, 使用step进去瞧瞧
发现有个run_hook 还是使用step进去, 然后就到了Binlog_storage_delegate::after_sync
Binlog_storage_delegate::after_sync 里面有个FOREACH_OBSERVER(遍历观察者?), 还是step继续
然后就到了半同步插件的代码. repl_semi_report_binlog_sync
这个函数就判断当前是不是 AFTER_SYNC 是的话就返回repl_semisync.commitTrx.
等待的操作就是在repl_semisync.commitTrx里面的, 这里不详细看了. 直接finish吧, 然后continue, 后面的也不想看了
sql执行完成, Rpl_semi_sync_master_yes_tx+1
这个阶段在上一个阶段之后, 所以binlog肯定也写完, 并且也已经发送到从库了, 这里就不在看了.
设置主从的rpl_semi_sync_master_wait_point为AFTER_COMMIT, 然后重启从库的IO线程
set global rpl_semi_sync_master_wait_point = AFTER_COMMIT;
stop slave;
start slave;
既然叫after commit那么断点就打在commit之后 MYSQL_BIN_LOG::process_commit_stage_queue
gdb中执行如下
set substitute-path /var/lib/pb2/sb_1-6473437-1647886122.65/mysql-5.7.38 /root/mysql_source/mysql-5.7.38
break MYSQL_BIN_LOG::process_commit_stage_queue
continue
既然是commit之后, 那直接finish掉commit阶段吧, 然后next
直接就到了process_after_commit_stage_queue, 使用step进去瞧瞧
发现也是run_hook, step进去瞧瞧
然后就到了Trans_delegate::after_commit
应该就是和afer_sync的Binlog_storage_delegate::after_sync对应
Trans_delegate::after_commit里面也是有个FOREACH_OBSERVER, 一样的去瞧瞧
一路step下去也到了repl_semisync.commitTrx 还是不看了.直接continue吧
sql执行完成. Rpl_semi_sync_master_yes_tx+1
AFTER_SYNC 是在SYNC阶段执行完成之后, 等待从库的ACK (repl_semisync.commitTrx)
AFTER_COMMIT 是在COMMIT阶段完成之后, 等待从库的ACK.
binlog是在sync之后就发送到从库了, 从库收到之后就返回ACK给dump线程.
after_sync和after_commit只是等待收到ACK的时机不同.
AFTER_SYNC函数调用过程如下
MYSQL_BIN_LOG::flush_cache_to_file 完成后
call_after_sync_hook
RUN_HOOK Binlog_storage_delegate::after_sync
FOREACH_OBSERVER
repl_semisync.commitTrx (如果是AFTER_SYNC的话)
AFTER_COMMIT函数调用过程如下
MYSQL_BIN_LOG::flush_cache_to_file 完成后
MYSQL_BIN_LOG::process_commit_stage_queue 完成后
MYSQL_BIN_LOG::process_after_commit_stage_queue
RUN_HOOK Trans_delegate::after_commit
FOREACH_OBSERVER
repl_semisync.commitTrx (如果是AFTER_COMMIT的话)