作者简介:高鹏,笔名八怪。《深入理解MySQL主从原理》图书作者,同时运营个人公众号“MySQL学习”,持续分享遇到的有趣case以及代码解析!
源码版本5.7.29
一、全局变量
semisync_slave_plugin.cc
ReplSemiSyncSlave repl_semisync;
/*
indicate whether or not the slave should send a reply to the master.
This is set to true in repl_semi_slave_read_event if the current
event read is the last event of a transaction. And the value is
checked in repl_semi_slave_queue_event.
*/
bool semi_sync_need_reply= false;
semisync.cc
char rpl_semi_sync_slave_enabled;
char rpl_semi_sync_slave_status= 0;
unsigned long rpl_semi_sync_slave_trace_level;
二、相关信息
类ReplSemiSyncSlave
继承自ReplSemiSyncBase
/* True when initObject has been called */
bool init_done_;//插件是否初始化
bool slave_enabled_; /* semi-sycn is enabled on the slave */ //完全根据参数semi_sync_slave_enabled设置进行更改
MYSQL *mysql_reply; /* connection to send reply */
半同步hook 从库端
Binlog_relay_IO_observer relay_io_observer = {
sizeof(Binlog_relay_IO_observer), // len
repl_semi_slave_io_start, // start OK repl_semi_slave_io_end, // stop OK
repl_semi_slave_sql_start, // start sql thread 空
repl_semi_slave_sql_stop, // stop sql thread 空
repl_semi_slave_request_dump, // request_transmit OK
repl_semi_slave_read_event, // after_read_event OK
repl_semi_slave_queue_event, // after_queue_event OK
repl_semi_reset_slave, // reset 空
};
反馈ack的信息
/* The layout of a semisync slave reply packet:
1 byte for the magic num
8 bytes for the binlog positon
n bytes for the binlog filename, terminated with a '\0'
*/
#define REPLY_MAGIC_NUM_LEN 1
#define REPLY_BINLOG_POS_LEN 8
#define REPLY_BINLOG_NAME_LEN (FN_REFLEN + 1)
#define REPLY_MESSAGE_MAX_LENGTH \
(REPLY_MAGIC_NUM_LEN + REPLY_BINLOG_POS_LEN + REPLY_BINLOG_NAME_LEN)
#define REPLY_MAGIC_NUM_OFFSET 0
#define REPLY_BINLOG_POS_OFFSET (REPLY_MAGIC_NUM_OFFSET + REPLY_MAGIC_NUM_LEN)
#define REPLY_BINLOG_NAME_OFFSET (REPLY_BINLOG_POS_OFFSET + REPLY_BINLOG_POS_LEN)
三、初始化
执行语句 INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
#0 semi_sync_slave_plugin_init (p=0x354bd40) at /home/mysql/soft/percona-server-5.7.29-32/plugin/semisync/semisync_slave_plugin.cc:230
#1 0x00000000014e7cf0 in plugin_initialize (plugin=0x354bd40) at /home/mysql/soft/percona-server-5.7.29-32/sql/sql_plugin.cc:1279
#2 0x00000000014ea354 in mysql_install_plugin (thd=0x7ffddc000c00, name=0x7ffddc0066e8, dl=0x7ffddc0066f8) at /home/mysql/soft/percona-server-5.7.29-32/sql/sql_plugin.cc:2279
#3 0x00000000014f0547 in Sql_cmd_install_plugin::execute (this=0x7ffddc0066e0, thd=0x7ffddc000c00) at /home/mysql/soft/percona-server-5.7.29-32/sql/sql_plugin.cc:4664
#4 0x00000000014c0054 in mysql_execute_command (thd=0x7ffddc000c00, first_level=true) at /home/mysql/soft/percona-server-5.7.29-32/sql/sql_parse.cc:5154
#5 0x00000000014c2025 in mysql_parse (thd=0x7ffddc000c00, parser_state=0x7fffe81d64a0, update_userstat=false) at /home/mysql/soft/percona-server-5.7.29-32/sql/sql_parse.cc:5927
#6 0x00000000014b6c5f in dispatch_command (thd=0x7ffddc000c00, com_data=0x7fffe81d6c90, command=COM_QUERY) at /home/mysql/soft/percona-server-5.7.29-32/sql/sql_parse.cc:1539
#7 0x00000000014b5a94 in do_command (thd=0x7ffddc000c00) at /home/mysql/soft/percona-server-5.7.29-32/sql/sql_parse.cc:1060
#8 0x00000000015e9d32 in handle_connection (arg=0x36a8a20) at /home/mysql/soft/percona-server-5.7.29-32/sql/conn_handler/connection_handler_per_thread.cc:325
#9 0x00000000018b97f2 in pfs_spawn_thread (arg=0x36d4480) at /home/mysql/soft/percona-server-5.7.29-32/storage/perfschema/pfs.cc:2198
#10 0x00007ffff7bc6ea5 in start_thread () from /lib64/libpthread.so.0
#11 0x00007ffff5f2b8dd in clone () from /lib64/libc.so.6
相关调用过程
semi_sync_slave_plugin_init
->ReplSemiSyncSlave::initObject
->if (init_done_) 如果已经初始化
打印警告
sql_print_warning("%s called twice", kWho);
init_done_ = true; //设置为已经初始化
setSlaveEnabled(rpl_semi_sync_slave_enabled); //根据参数semi_sync_slave_enabled设置插件状态,默认初始化为flase
->slave_enabled_ = enabled;
setTraceLevel(rpl_semi_sync_slave_trace_level);//根据参数semi_sync_slave_trace_level设置日志级别
->trace_level_ = trace_level;
-> (register_binlog_relay_io_observer(&relay_io_observer, p))
进行插件回调函数注册
四、回调函数repl_semi_slave_io_start
调用者:IO线程
(gdb) bt
#0 ReplSemiSyncSlave::slaveStart (this=0x7ffdb2c953b0 , param=0x7ffdb2a8fb20) at /home/mysql/soft/percona-server-5.7.29-32/plugin/semisync/semisync_slave.cc:82
#1 0x00007ffdb2a93889 in repl_semi_slave_io_start (param=0x7ffdb2a8fb20) at /home/mysql/soft/percona-server-5.7.29-32/plugin/semisync/semisync_slave_plugin.cc:146
#2 0x00000000013dd878 in Binlog_relay_IO_delegate::thread_start (this=0x2d1ca40 , thd=0x7ffdc8002bd0, mi=0x7ffddd16eaa0)
at /home/mysql/soft/percona-server-5.7.29-32/sql/rpl_handler.cc:868
#3 0x0000000001836320 in handle_slave_io (arg=0x7ffddd16eaa0) at /home/mysql/soft/percona-server-5.7.29-32/sql/rpl_slave.cc:5687
#4 0x00000000018b97f2 in pfs_spawn_thread (arg=0x7ffddcd20ea0) at /home/mysql/soft/percona-server-5.7.29-32/storage/perfschema/pfs.cc:2198
#5 0x00007ffff7bc6ea5 in start_thread () from /lib64/libpthread.so.0
#6 0x00007ffff5f2b8dd in clone () from /lib64/libc.so.6
(gdb) p param->server_id
$1 = 623306
(gdb) p param->thread_id
$2 = 154(gdb)
回调时机
handle_slave_io
->初始化IO线程属性 init_slave_thread
->(RUN_HOOK(binlog_relay_io, thread_start, (thd, mi))
->safe_connect 进行连接主库
->request_dump 发送gtid,获取主库信息等语句
具体过程
Binlog_relay_IO_delegate::thread_start(THD *thd, Master_info *mi)
输入参数为IO线程的线程的processlist id
->repl_semi_slave_io_start
->semi_sync= getSlaveEnabled();//判断是否开启了半同步 slave_enabled_,由参数semi_sync_slave_enabled设置进行更改控制
->输出日志级别信息
"Slave I/O thread: Start %s replication to master '%s@%s:%d' in log '%s' at position %lu"
->if (semi_sync && !rpl_semi_sync_slave_status)
如果是半同步,但是状态不对则进行修改
rpl_semi_sync_slave_status= 1;
这个回调函数比较简单,只是设置了从库是否使用为半同步方式,但是随后还会修改,参考repl_semi_slave_request_dump回调函数。
五、回调函数repl_semi_slave_io_end
调用者:IO线程
#0 ReplSemiSyncSlave::slaveStop (this=0x7ffdb2c953b0 , param=0x7ffdb2a8fb20) at /home/mysql/soft/percona-server-5.7.29-32/plugin/semisync/semisync_slave.cc:98
#1 0x00007ffdb2a938ad in repl_semi_slave_io_end (param=0x7ffdb2a8fb20) at /home/mysql/soft/percona-server-5.7.29-32/plugin/semisync/semisync_slave_plugin.cc:151
#2 0x00000000013ddaa4 in Binlog_relay_IO_delegate::thread_stop (this=0x2d1ca40 , thd=0x7ffdc8002bd0, mi=0x7ffddd16eaa0)
at /home/mysql/soft/percona-server-5.7.29-32/sql/rpl_handler.cc:882
#3 0x0000000001837906 in handle_slave_io (arg=0x7ffddd16eaa0) at /home/mysql/soft/percona-server-5.7.29-32/sql/rpl_slave.cc:6076
#4 0x00000000018b97f2 in pfs_spawn_thread (arg=0x7ffddcd20ea0) at /home/mysql/soft/percona-server-5.7.29-32/storage/perfschema/pfs.cc:2198
#5 0x00007ffff7bc6ea5 in start_thread () from /lib64/libpthread.so.0
#6 0x00007ffff5f2b8dd in clone () from /lib64/libc.so.6
回调时机
handle_slave_io
->io线程循环退出
->RUN_HOOK(binlog_relay_io, thread_stop, (thd, mi)); //回调stop
具体过程
Binlog_relay_IO_delegate::thread_stop
->repl_semi_slave_io_end
->ReplSemiSyncSlave::slaveStop
if (rpl_semi_sync_slave_status)
rpl_semi_sync_slave_status= 0;
这个回调函数也很简单只是将 rpl_semi_sync_slave_status状态设置为OFF
六、回调函数repl_semi_slave_request_dump
调用者:IO线程
#0 repl_semi_slave_request_dump (param=0x7ffdb2a8f830, flags=0) at /home/mysql/soft/percona-server-5.7.29-32/plugin/semisync/semisync_slave_plugin.cc:52
#1 0x00000000013de14a in Binlog_relay_IO_delegate::before_request_transmit (this=0x2d1ca40 , thd=0x7ffdd0000a90, mi=0x7ffddd16eaa0, flags=0)
at /home/mysql/soft/percona-server-5.7.29-32/sql/rpl_handler.cc:922
#2 0x0000000001832dd9 in request_dump (thd=0x7ffdd0000a90, mysql=0x7ffdd000e7e0, mi=0x7ffddd16eaa0, suppress_warnings=0x7ffdb2a8fccb)
at /home/mysql/soft/percona-server-5.7.29-32/sql/rpl_slave.cc:4426
#3 0x0000000001836a71 in handle_slave_io (arg=0x7ffddd16eaa0) at /home/mysql/soft/percona-server-5.7.29-32/sql/rpl_slave.cc:5826
#4 0x00000000018b97f2 in pfs_spawn_thread (arg=0x7ffddc042190) at /home/mysql/soft/percona-server-5.7.29-32/storage/perfschema/pfs.cc:2198
#5 0x00007ffff7bc6ea5 in start_thread () from /lib64/lib
回调方式:
handle_slave_io
->初始化IO线程属性 init_slave_thread
->(RUN_HOOK(binlog_relay_io, thread_start, (thd, mi))
->safe_connect 进行连接主库
->request_dump 发送gtid,获取主库信息等语句
->首先回调RUN_HOOK(binlog_relay_io,before_request_transmit,(thd, mi, binlog_flags))
->其他参考https://www.jianshu.com/p/a81d62bf6b31
具体过程
Binlog_relay_IO_delegate::before_request_transmit
param.server_id= thd->server_id;//获取server_id
param.thread_id= thd->thread_id(); //获取dump线程processlist id
->repl_semi_slave_request_dump
->(!repl_semisync.getSlaveEnabled())
根据slave_enabled_是否设置为1,如果没有则说明半同步没开启,
直接返回
->SELECT @@global.rpl_semi_sync_master_enabled
首先探测主库是否安装半同步插件,如果没有安装报错,也就是报错ER_UNKNOWN_SYSTEM_VARIABLE
Master server does not support semi-sync, fallback to asynchronous replication
并且关闭从库办同步
rpl_semi_sync_slave_status=0
->然后告诉主库,从库使用的是半同步
"SET @rpl_semi_sync_slave= 1"
设置rpl_semi_sync_slave_status= 1
我们可以看到这里在和主库进行交互,实际探测主库是否安装了半同步插件,没有的话rpl_semi_sync_slave_status也会在io线程启动时刻设置为OFF。
七、回调函数repl_semi_slave_read_event
调用者:IO线程
(gdb) bt
#0 ReplSemiSyncSlave::slaveReadSyncHeader (this=0x7fffe82193b0 , header=0x7ffdb802f691 , total_len=67,
need_reply=0x7fffe82193c8 , payload=0x7ffe00844cc0, payload_len=0x7ffe00844cb8) at /home/mysql/soft/percona-server-5.7.29-32/plugin/semisync/semisync_slave.cc:57
#1 0x00007fffe80177e4 in repl_semi_slave_read_event (param=0x7ffe00844b20, packet=0x7ffdb802f691 , len=67, event_buf=0x7ffe00844cc0, event_len=0x7ffe00844cb8)
at /home/mysql/soft/percona-server-5.7.29-32/plugin/semisync/semisync_slave_plugin.cc:119
#2 0x00000000013de3a1 in Binlog_relay_IO_delegate::after_read_event (this=0x2d1ca40 , thd=0x7ffdb801b630, mi=0x6f1f8d0,
packet=0x7ffdb802f691 , len=67, event_buf=0x7ffe00844cc0, event_len=0x7ffe00844cb8) at /home/mysql/soft/percona-server-5.7.29-32/sql/rpl_handler.cc:937
#3 0x00000000018370ae in handle_slave_io (arg=0x6f1f8d0) at /home/mysql/soft/percona-server-5.7.29-32/sql/rpl_slave.cc:5929
#4 0x00000000018b97f2 in pfs_spawn_thread (arg=0x7ffdec0239b0) at /home/mysql/soft/percona-server-5.7.29-32/storage/perfschema/pfs.cc:2198
#5 0x00007ffff7bc6ea5 in start_thread () from /lib64/libpthread.so.0
#6 0x00007ffff5f2b8dd in clone () from /lib64/libc.so.6
回调方式
handle_slave_io
->读取event read_event
->RUN_HOOK(binlog_relay_io, after_read_event,(thd, mi,(const char*)mysql->net.read_pos + 1,event_len, &event_buf, &event_len))
->写入relay log queue_event
具体过程
Binlog_relay_IO_delegate::after_read_event
->repl_semi_slave_read_event
->ReplSemiSyncSlave::slaveReadSyncHeader(const char *header,unsigned long total_len,bool *need_reply,const char **payload,unsigned long *payload_len)
->((unsigned char)(header[0]) == kPacketMagicNum) //判断是否为半同步信息
*need_reply = (header[1] & kPacketFlagSync); //本event是否需要进行ACK
*payload_len = total_len - 2;//长度总数减去2
*payload = header + 2;//偏移量+2
如果trace_level_设置为16则对于需要回复的event的会打印日志如下
sql_print_information("%s: reply - %d", kWho, *need_reply);
可以看到这个回调函数主要通过读取event,然后通过kPacketFlagSync去判断是否本event需要进行ack反馈。注意是否需要反馈是放在semi_sync_need_reply里面的,但是这是全局变量(多源复制如何处理?多源复制不支持半同步)
八、回调函数repl_semi_slave_queue_event
调用者:IO线程 注意本函数内部会做replay的判断,根据前面的semi_sync_need_reply进行判断如果不是事务的最后一个event则不需要反馈ack
(gdb) bt
#0 ReplSemiSyncSlave::slaveReply (this=0x7fffe82193b0 , mysql=0x7ffdb8022200, binlog_filename=0x6f21988 "log_bin.000002", binlog_filepos=1748)
at /home/mysql/soft/percona-server-5.7.29-32/plugin/semisync/semisync_slave.cc:110
#1 0x00007fffe8017860 in repl_semi_slave_queue_event (param=0x7ffe00844b20, event_buf=0x7ffdb802f693 "\341\244 `\020w\002", event_len=31, flags=0)
at /home/mysql/soft/percona-server-5.7.29-32/plugin/semisync/semisync_slave_plugin.cc:139
#2 0x00000000013de605 in Binlog_relay_IO_delegate::after_queue_event (this=0x2d1ca40 , thd=0x7ffdb801b630, mi=0x6f1f8d0,
event_buf=0x7ffdb802f693 "\341\244 `\020w\002", event_len=31, synced=false) at /home/mysql/soft/percona-server-5.7.29-32/sql/rpl_handler.cc:957
#3 0x00000000018371c1 in handle_slave_io (arg=0x6f1f8d0) at /home/mysql/soft/percona-server-5.7.29-32/sql/rpl_slave.cc:5949
#4 0x00000000018b97f2 in pfs_spawn_thread (arg=0x7ffdec0239b0) at /home/mysql/soft/percona-server-5.7.29-32/storage/perfschema/pfs.cc:2198
#5 0x00007ffff7bc6ea5 in start_thread () from /lib64/libpthread.so.0
#6 0x00007ffff5f2b8dd in clone () from /lib64/libc.so.6
回调方式
handle_slave_io
->读取event read_event
->RUN_HOOK(binlog_relay_io, after_read_event,(thd, mi,(const char*)mysql->net.read_pos + 1,event_len, &event_buf, &event_len))
->写入relay log queue_event
->(RUN_HOOK(binlog_relay_io, after_queue_event,(thd, mi, event_buf, event_len, synced)))
具体过程:
Binlog_relay_IO_delegate::after_queue_event(THD *thd, Master_info *mi,const char *event_buf,ulong event_len,bool synced)
此处传入的是event和是否写到了磁盘synced,但是synced并未使用
->repl_semi_slave_queue_event
->(rpl_semi_sync_slave_status && semi_sync_need_reply)
判断是否开启的半同步,同时判断本event是否需要反馈ACK
->ReplSemiSyncSlave::slaveReply((MYSQL *mysql,const char *binlog_filename,my_off_t binlog_filepos))
函数传入了反馈的主库binlog位点
->reply_buffer[REPLY_MAGIC_NUM_LEN + REPLY_BINLOG_POS_LEN + REPLY_BINLOG_NAME_LEN];
构建反馈ACK的信息 1字节魔术数+8字节位点+最大(512+1)字节binlog文件名
->如果trace_level_设置为16输出日志
"%s: reply (%s, %lu)", kWho,binlog_filename, (ulong)binlog_filepos)
->进行发送
可以看到这个回调函数,实际的构建了需要反馈的信息给主库的Ack_recevier线程。