关于mysql:Mysql源码分析基于行的复制实现之主从复制

35次阅读

共计 12504 个字符,预计需要花费 32 分钟才能阅读完成。

前言

  通过对《【Mysql 源码剖析】基于行的复制实现之“主从关系建设”》理解了主从复制的一些原理,本章内容会深刻对 binlog、relaylog 做解说。并对流程做深刻理解。

  在开始交接之前,咱们带着几个问题切入:

  1. 如何查看 binlog 和 relaylog 事件?
  2. relaylog 是如何写入?
  3. binlog 是如何同步?
  4. binlog 格局如何解析?

1. 如何查看 binlog 和 relaylog

   在 mysql 中能够通过如下命令能够查看都有哪些 binlog 日志,如图 1 -1:

mysql> show binary logs; 


<center> 图 1 -1 查看 binlog 日志 </center>

  • Log_name 日志文件名称。
  • File_size 文件大小。
  • Encrypted 是否加密“No”代表未加密。

  在查看 binlog 时、主有本人的 binlog、从也有本人的 binlog。

  如果要查看最新的 binlog,能够通过如下命令查看,如图 1 -2

mysql> show master status; 


<center> 图 1 -2 查看最新 binlog</center>

  通过如下命令能够查看以后 binlog 事件,如图 1 -3:

mysql> show binlog events;


<center> 图 1 -3 查看 binlog 事件 </center>

  • Log_name 日志文件名称。
  • Pos 代表文件开始的地位。
  • Event_type 代表事件的类型。
  • Server_id 是创立事件的服务器 ID。
  • End_log_pos 代表事件在文件中的完结地位,以下面为例,第一次查问的完结地位是 125,第二次 insert 之后文件的开始地位就是从 125 开始。
  • Info 代表事件信息,是一段可读的文本内容。

  除了查看 binlog 事件以外,咱们还能够查看 relaylog 的事件如图 1 - 4 所示,查看事件命令如下:

mysql> show relaylog events;


<center> 图 1 -4 查看 relaylog 事件 </center>
relaylog 事件的参数含意和 binlog 的统一,可参考 binlog。

  除了查看 relaylog 事件外,还能够查看 relaylog 参数,如图 1 - 5 所示。通过如下命令能够查看:

mysql> show variables like '%relay%';


<center> 图 1 -5 relaylog 参数 </center>

  • max_relay_log_size:标记 relay log 容许的最大值,如果该值为 0,则默认值为 max_binlog_size(1G);如果不为 0,则 max_relay_log_size 则为最大的 relay_log 文件大小;
  • relay_log:定义 relay_log 的地位和名称,如果值为空,则默认地位在数据文件的目录,文件名为 host_name-relay-bin.nnnnnn;
  • relay_log_index:同 relay_log,定义 relay_log 的地位和名称;
  • relay_log_info_file:设置 relay-log.info 的地位和名称(relay-log.info 记录 MASTER 的 binary_log 的复原地位和 relay_log 的地位)
  • relay_log_purge:是否主动清空不再须要中继日志时。默认值为 1(启用)。
  • relay_log_recovery:当 slave 从库宕机后,如果 relay-log 损坏了,导致一部分中继日志没有解决,则主动放弃所有未执行的 relay-log,并且从新从 master 上获取日志,这样就保障了 relay-log 的完整性。默认状况下该性能是敞开的,将 relay_log_recovery 的值设置为 1 时,可在 slave 从库上开启该性能,倡议开启。
  • relay_log_space_limit:避免中继日志写满磁盘,这里设置中继日志最大限额。但此设置存在主库解体,从库中继日志不全的状况,不到万不得已,不举荐应用;
  • sync_relay_log:这个参数和 sync_binlog 是一样的,当设置为 1 时,slave 的 I / O 线程每次接管到 master 发送过去的 binlog 日志都要写入零碎缓冲区,而后刷入 relay log 中继日志里,这样是最平安的,因为在解体的时候,你最多会失落一个事务,但会造成磁盘的大量 I /O。当设置为 0 时,并不是马上就刷入中继日志里,而是由操作系统决定何时来写入,尽管安全性升高了,但缩小了大量的磁盘 I / O 操作。这个值默认是 0,可动静批改,倡议采纳默认值。
  • sync_relay_log_info:这个参数和 sync_relay_log 参数一样,当设置为 1 时,slave 的 I / O 线程每次接管到 master 发送过去的 binlog 日志都要写入零碎缓冲区,而后刷入 relay-log.info 里,这样是最平安的,因为在解体的时候,你最多会失落一个事务,但会造成磁盘的大量 I /O。当设置为 0 时,并不是马上就刷入 relay-log.info 里,而是由操作系统决定何时来写入,尽管安全性升高了,但缩小了大量的磁盘 I / O 操作。这个值默认是 0,可动静批改,倡议采纳默认值。

2.slave_relay_log_info 与 slave_master_info

  slave_relay_log_info 和 slave_master_info 别离为 relaylog-info 和 master-info 信息。relaylog-info 和 master-info 信息能够应用 FILE 或这 TABLE 存储。relaylog-info 有三种模式,分为为 FILE、TABLE、DUMMY。


<center> 图 2 -1 查看 relaylog-info 和 master-info </center>
  通过 relay_log_info_repository 和 master_info_repository 能够得悉 relaylog-info 和 master-info 是用什么模式存储,如图 2 - 1 所示。

3.binlog 文件格式解析

  想理解 binlog 的格局,能够通过十六进制的模式去查看, 如图 3 - 1 所示。

<center> 图 3 -1 binlog 十六进制格局 </center>
  查看十六进制格局能够应用 hexdump 命令,命令格局如下:

#hexdump -C  mysql-bin.000024 

  通过 hexdump 失去如下内容:

00000000  fe 62 69 6e 41 93 92 5f  0f 0a 00 00 00 79 00 00  |.binA.._.....y..|
00000010  00 7d 00 00 00 01 00 04  00 38 2e 30 2e 32 30 2d  |.}.......8.0.20-|
00000020  64 65 62 75 67 00 00 00  00 00 00 00 00 00 00 00  |debug...........|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  00 00 00 00 00 00 00 00  00 00 00 41 93 92 5f 13  |...........A.._.|
00000050  00 0d 00 08 00 00 00 00  04 00 04 00 00 00 61 00  |..............a.|
00000060  04 1a 08 00 00 00 08 08  08 02 00 00 00 0a 0a 0a  |................|
00000070  2a 2a 00 12 34 00 0a 28  01 3a 50 74 ce 41 93 92  |**..4..(.:Pt.A..|
00000080  5f 23 0a 00 00 00 1f 00  00 00 9c 00 00 00 80 00  |_#..............|
00000090  00 00 00 00 00 00 00 00  56 4b 92 11              |........VK..|

  也能够 mysqlbinlog 命令失去 binlog 信息,如图 3 -2。命令格局如下:

#mysqlbinlog   --base64-output='decode-rows' mysql-bin.000024 


<center> 图 3 -2 binlog 信息 </center>
  能够通过一张图解析下图 3 - 2 中内容,从十六进制格局中解析一下 binlog 格局,如图 3 - 3 所示。

<center> 图 3 -3 binlog 格局解析 </center>
  能够关注下如下内容和图 3 - 3 还有图 3 - 2 中关系:

fe 62 69 6e 对应魔术头 0xFE 'bin'

41 93 92 5f timestamp 工夫戳 1603441473,对应工夫 "2020/10/23 16:24:33"
0f          event_type 对应十进制 16 对应 XID_EVENT
0a 00 00 00 server_id 0x0a 对应十进制 10
79 00 00 00 event_size 对应十进制 121
7d 00 00 00 log_pos 对应十进制 125
01 00       flags 状态

04 00       binlog version 对应版本 4
38 2e 30 2e 32 30 2d 64 65 62 75 67 00 00 00  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  00 00 00 
mysql-server version 对应版本号 8.0.20-debug
41 93 92 5f create-timestamp 创立工夫戳,对应 1603441473,对应工夫 2020/10/23 16:24:33 
13          Event-header-length 对应 19
00 0d 00 08 00 00 00 00  04 00 04 00 00 00 61 00  
04 1a 08 00 00 00 08 08  08 02 00 00 00 0a 0a 0a  
2a 2a 00 12 34 00 0a 28  01   Event-type-header-length 对应 41 个 event 类型
3a 50 74 ce  CRC32 对应 0xce74503a  

41 93 92 5f  timestamp 对应工夫 2020/10/23 16:24:33
23           event_type 对应 35 对应 PREVIOUS_GTIDS_LOG_EVENT
0a 00 00 00  server_id 对应服务 ID 为 10
1f 00 00 00  event_size 对应大小 31
9c 00 00 00  log_pos 对应 156
80 00        flags 状态

00 00 00 00 00 00 00 00  Next pos

56 4b 92 11 CRC 0x11924b56      

  Event-type-header-length 对 41 为什么是 41?能够看一下源码, 对应 libbinlogevents/include/binlog_event.h

enum Log_event_type {
  /**
    Every time you add a type, you have to
    - Assign it a number explicitly. Otherwise it will cause trouble
      if a event type before is deprecated and removed directly from
      the enum.
    - Fix Format_description_event::Format_description_event().
  */
  UNKNOWN_EVENT = 0,
  /*
    自 MySQL8.0.2 以来已弃用。它只是一个占位符,不应该在其余中央应用。*/
  START_EVENT_V3 = 1,  // 起始事件是二进制日志版本 1 至 3 的二进制日志的第一个事件。QUERY_EVENT = 2,     // 查问事件用于向 binlog 发送文本查问。STOP_EVENT = 3,      // 进行事件
  ROTATE_EVENT = 4,    // 将 Rotate 事件作为最初一个事件增加到 binlog 中,以通知读者接下来要申请的 binlog。INTVAR_EVENT = 5,   // 基于整数的会话变量


  SLAVE_EVENT = 7,   // 从事件

  APPEND_BLOCK_EVENT = 9,  // 将块数据追加到文件
  DELETE_FILE_EVENT = 11,  // 删除文件事件

  RAND_EVENT = 13,         //RAND() 函数的 外部状态。USER_VAR_EVENT = 14,     // 用户变量
  FORMAT_DESCRIPTION_EVENT = 15, // 格局形容事件是 binlog 版本 4 的 binlog 的第一个事件。它形容了其余事件的布局形式。XID_EVENT = 16,          //2PC 的事务处理 ID,在须要时写入 COMMIT。BEGIN_LOAD_QUERY_EVENT = 17,  // 截断文件并设置块数据
  EXECUTE_LOAD_QUERY_EVENT = 18,

  TABLE_MAP_EVENT = 19,        // 基于行的复制中 应用的第一个事件 申明如何定义将要更改的表。/**
    V1 事件号从 5.1.16 始终应用到 mysql-5.6。*/
  WRITE_ROWS_EVENT_V1 = 23,
  UPDATE_ROWS_EVENT_V1 = 24,
  DELETE_ROWS_EVENT_V1 = 25,

  /**
    主产生了不寻常的事
   */
  INCIDENT_EVENT = 26,

  /**
    主机在闲暇工夫发送的心跳事件确保主机联机状态为从机
  */
  HEARTBEAT_LOG_EVENT = 27,

  /**
    在某些状况下,有必要把不可漠视的货色送过来数据到从机:在这种状况下,从机能够解决的数据是用于解决它的代码,但如果不是,则能够疏忽它辨识。*/    
  IGNORABLE_LOG_EVENT = 28,
  ROWS_QUERY_LOG_EVENT = 29, //ROWS_EVENT 的查问

  /** 行事件的版本 2 */
  WRITE_ROWS_EVENT = 30,
  UPDATE_ROWS_EVENT = 31,
  DELETE_ROWS_EVENT = 32,

  GTID_LOG_EVENT = 33,    //gtid 
  ANONYMOUS_GTID_LOG_EVENT = 34, // 匿名 gtid

  PREVIOUS_GTIDS_LOG_EVENT = 35, // 上一个 gtid

  TRANSACTION_CONTEXT_EVENT = 36, // 事务上下文事件

  VIEW_CHANGE_EVENT = 37,

  /* 筹备了相似于 Xid 的 XA 事务终端事件 */
  XA_PREPARE_LOG_EVENT = 38,

  /**
    UPDATE_ROWS_事件的扩大,容许依据到 binlog_row_value_options 配置中。*/
  PARTIAL_UPDATE_ROWS_EVENT = 39,

  TRANSACTION_PAYLOAD_EVENT = 40,


  ENUM_END_EVENT /* 起点标记 */
};

  在 mysql5 中只有 39 个 event-type。

  基于行复制实现的的事件为 TABLE_MAP_EVENT、ROWS_EVENT、ROWS_QUERY_EVENT 三大类。
能够看官网:https://dev.mysql.com/doc/internals/en/row-based-replication.html

4.Mysql 主从同步源码解析

<center> 图 4 -1 主从同步 </center>
  在主从同步过程中、咱们能够把同步过程分为 10 个阶段,如图 4 -1。

  通过对之前的《【Mysql 源码剖析】基于行的复制实现之“主从关系建设”》的学习,得悉 dump 线程会接管到 COM_BINLOG_DUMP 指令后触发。然而从服务器申请二进制日志流中分为几个参数:

 1 字节              [12] COM_BINLOG_DUMP
4 字节              binlog-pos  binlog 对应到地位
2 字节              flags       状态
4 字节              server-id   服务 ID
string[EOF]    binlog-filename 文件名称   

  当接管到 COM_BINLOG_DUMP 指令后会触发调用 com_binlog_dump 办法,com_binlog_dump 办法中会调用 mysql_binlog_send 函数用于发送 binlog,如图图 4 - 2 所示。

<center> 图 4 -2 发送 binlog </center>

void mysql_binlog_send(THD *thd, char *log_ident, my_off_t pos,
                       Gtid_set *slave_gtid_executed, uint32 flags) {
  //log_ident 发送 binlog 名称
  //pos 以后行号,后续会作为开始行号
  Binlog_sender sender(thd, log_ident, pos, slave_gtid_executed, flags);

  sender.run();}

  在调用 mysql_binlog_send 办法后,会调用 Binlog_sender::send_events 办法发送事件,在发送事件时会调用 Binlog_sender::check_event_type 办法对事件进行检测, 如图 4 -3。

<center> 图 4 -3 事件检测办法 </center>
  用 Binlog_sender::check_event_type 检测办法原型如下:

bool Binlog_sender::check_event_type(Log_event_type type, const char *log_file,
                                     my_off_t log_pos) {if (type == binary_log::ANONYMOUS_GTID_LOG_EVENT) {
    /*
     通常状况下,当主动地位被启用,因为主设施和从设施
     如果主机未应用 GTID_MODE=ON,则回绝连贯。然而,如果主机在连贯后更改了 GTID_模式初始化,或者如果隶属申请复制呈现在最初一个匿名事件之前
     的事务,则这可能产生。而后生成此谬误以阻止发送
     到隶属服务器的匿名事务。*/
    if (m_using_gtid_protocol) { // 判断是否应用 gtid
      DBUG_EXECUTE_IF("skip_sender_anon_autoposition_error",
                      {return false;};);
      char buf[MYSQL_ERRMSG_SIZE];
      snprintf(buf, MYSQL_ERRMSG_SIZE,
               ER_THD(m_thd, ER_CANT_REPLICATE_ANONYMOUS_WITH_AUTO_POSITION),
               log_file, log_pos);
      set_fatal_error(buf);
      return true;
    }
    /*
      通常状况下,当 master 有 GTID_MODE=ON,因为当 GTID_MODE=ON。然而,如果主控形态产生更改,则可能会产生这种状况当隶属服务器尚未复制所有匿名交互。*/
    else if (get_gtid_mode_from_copy(GTID_MODE_LOCK_NONE) == GTID_MODE_ON) {char buf[MYSQL_ERRMSG_SIZE];
      snprintf(buf, MYSQL_ERRMSG_SIZE,
               ER_THD(m_thd, ER_CANT_REPLICATE_ANONYMOUS_WITH_GTID_MODE_ON),
               log_file, log_pos);
      set_fatal_error(buf);
      return true;
    }
  } else if (type == binary_log::GTID_LOG_EVENT) {
    /*
      通常状况下,当主服务器有 GTID_MODE=OFF,因为当
      GTID_MODE=OFF。然而,如果主控形态产生更改,则
      可能会产生这种状况当从机尚未复制所有 GTID 时,GTID_MODE 敞开交互。*/
    if (get_gtid_mode_from_copy(GTID_MODE_LOCK_NONE) == GTID_MODE_OFF) {char buf[MYSQL_ERRMSG_SIZE];
      snprintf(buf, MYSQL_ERRMSG_SIZE,
               ER_THD(m_thd, ER_CANT_REPLICATE_GTID_WITH_GTID_MODE_OFF),
               log_file, log_pos);
      set_fatal_error(buf);
      return true;
    }
  }
  return false;
}


<center> 图 4 -4 Log_event::write </center>
  在写入 binlog 时会调用 Log_event::write 进行写入,如图 4 -4。Log_event::write 在 log_event.h 头文件中。

  写入 master-info 信息调度如下:

(lldb) bt
* thread #36, stop reason = breakpoint 8.1
  * frame #0: 0x0000000109a10c80 mysqld`Master_info::write_info(this=0x00007fe416209200, to=0x00007fe418204230) at rpl_mi.cc:666
    frame #1: 0x0000000109a0e9ba mysqld`Master_info::flush_info(this=0x00007fe416209200, force=false) at rpl_mi.cc:380
    frame #2: 0x0000000109a987eb mysqld`flush_master_info(mi=0x00007fe416209200, force=false, need_lock=false, do_flush_relay_log=false) at rpl_slave.cc:1424
    frame #3: 0x0000000109ab4565 mysqld`queue_event(mi=0x00007fe416209200, buf="\a�\x8e_\"\n", event_len=77, do_flush_mi=true) at rpl_slave.cc:7956
    frame #4: 0x0000000109a9e063 mysqld`::handle_slave_io(arg=0x00007fe416209200) at rpl_slave.cc:5462
    frame #5: 0x000000010ac5a6c5 mysqld`pfs_spawn_thread(arg=0x00007fe4175b28b0) at pfs.cc:2854
    frame #6: 0x00007fff7e063305 libsystem_pthread.dylib`_pthread_body + 126
    frame #7: 0x00007fff7e06626f libsystem_pthread.dylib`_pthread_start + 70
    frame #8: 0x00007fff7e062415 libsystem_pthread.dylib`thread_start + 13

  写入 Relay-log-info 信息调度如下:

(lldb) bt
* thread #41, stop reason = breakpoint 2.1
  * frame #0: 0x000000010c36f990 mysqld`Relay_log_info::write_info(this=0x00007f8a53de3400, to=0x00007f8a5512af50) at rpl_rli.cc:2200
    frame #1: 0x000000010c3677e1 mysqld`Relay_log_info::flush_info(this=0x00007f8a53de3400, force=true) at rpl_rli.cc:1905
    frame #2: 0x000000010c371fe8 mysqld`Relay_log_info::commit_positions(this=0x00007f8a53de3400) at rpl_rli.cc:2730
    frame #3: 0x000000010a4b72eb mysqld`Relay_log_info::pre_commit(this=0x00007f8a53de3400) at rpl_rli.h:1986
    frame #4: 0x000000010a4b6655 mysqld`ha_commit_trans(thd=0x00007f8a5605fa00, all=true, ignore_global_read_lock=false) at handler.cc:1656
    frame #5: 0x000000010aefeab4 mysqld`trans_commit(thd=0x00007f8a5605fa00, ignore_global_read_lock=false) at transaction.cc:241
    frame #6: 0x000000010ab202d3 mysqld`mysql_create_db(thd=0x00007f8a5605fa00, db="t136", create_info=0x000070000d37a898) at sql_db.cc:405
    frame #7: 0x000000010ac6f026 mysqld`mysql_execute_command(thd=0x00007f8a5605fa00, first_level=true) at sql_parse.cc:3660
    frame #8: 0x000000010ac676ad mysqld`mysql_parse(thd=0x00007f8a5605fa00, parser_state=0x000070000d37e028) at sql_parse.cc:5306
    frame #9: 0x000000010c24dc6c mysqld`Query_log_event::do_apply_event(this=0x00007f8a520d0620, rli=0x00007f8a53de3400, query_arg="create database t136", q_len_arg=20) at log_event.cc:4804
    frame #10: 0x000000010c24af13 mysqld`Query_log_event::do_apply_event(this=0x00007f8a520d0620, rli=0x00007f8a53de3400) at log_event.cc:4415
    frame #11: 0x000000010c2458fe mysqld`Log_event::apply_event(this=0x00007f8a520d0620, rli=0x00007f8a53de3400) at log_event.cc:3241
    frame #12: 0x000000010c4047e7 mysqld`apply_event_and_update_pos(ptr_ev=0x000070000d37f860, thd=0x00007f8a5605fa00, rli=0x00007f8a53de3400) at rpl_slave.cc:4350
    frame #13: 0x000000010c3f2032 mysqld`exec_relay_log_event(thd=0x00007f8a5605fa00, rli=0x00007f8a53de3400, applier_reader=0x000070000d3803d8, in=0x00007f8a520d0620) at rpl_slave.cc:4867
    frame #14: 0x000000010c3db543 mysqld`::handle_slave_sql(arg=0x00007f8a52b43200) at rpl_slave.cc:7022
    frame #15: 0x000000010d5936c5 mysqld`pfs_spawn_thread(arg=0x00007f8a554bcbf0) at pfs.cc:2854
    frame #16: 0x00007fff7e063305 libsystem_pthread.dylib`_pthread_body + 126
    frame #17: 0x00007fff7e06626f libsystem_pthread.dylib`_pthread_start + 70
    frame #18: 0x00007fff7e062415 libsystem_pthread.dylib`thread_start + 13

Master 断点

# 发送 binlog
(lldb)b mysql_binlog_send

#发送 binlog 包
(lldb)b Binlog_sender::send_packet

#循环事件
(lldb)b Binlog_sender::run

#发送 binlog
(lldb)b Binlog_sender::send_binlog

#发送心跳包事件
(lldb)b Binlog_sender::send_heartbeat_event

Slave 断点

# 读取头文件事件
b Binlog_event_data_istream::read_event_header

#填充工夫数据
b Binlog_event_data_istream::fill_event_data

#读取下一个事件
b Rpl_applier_reader::read_next_event

#slave sal 解决
b handle_slave_sql

#写入 relay-log-info
b Relay_log_info::write_info

#relay_log 事件
b exec_relay_log_event  

#写入 binlog 或者 relaylog 断点
b handler::ha_write_row
b MYSQL_BIN_LOG::write_event
b binlog_cache_data::write_event

#写入 relaylog 之后操作
MYSQL_BIN_LOG::after_write_to_relay_log

#写入 binlog
b MYSQL_BIN_LOG::write_buffer
b MYSQL_BIN_LOG::Binlog_ofile::write

总结

  1. 通过“show binary logs;”能够查看 binlog 日志。
  2. 通过“show relaylog events;”能够查看 relaylog 事件。
  3. 在 mysql8.0.20 中有 41 个事件,mysql5.1 中有 39 个。
  4. 基于行复制实现的的事件为 TABLE_MAP_EVENT、ROWS_EVENT、ROWS_QUERY_EVENT 三大类。
  5. relaylog-info 和 binlog-info 能够应用 file 或者 table 模式记录。

参考献文

复制协定
https://dev.mysql.com/doc/internals/en/replication-protocol.html

事件含意
https://dev.mysql.com/doc/internals/en/event-meanings.html

基于行的二进制日志记录
[https://dev.mysql.com/doc/int…
](https://dev.mysql.com/doc/int…

正文完
 0