MySQL:半同步(三)从库端初始化和回调函数

2023年 8月 15日 60.8k 0

作者简介:高鹏,笔名八怪。《深入理解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线程。

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论