【Mysql源碼分析】基於行的複製實現之「主從複製」

前言

  通過對《【Mysql源碼分析】基於行的複製實現之「主從關係創建」》瞭解了主從複製的一些原理,本章內容會深刻對binlog、relaylog作講解。並對流程作深刻了解。html

  在開始交接以前,咱們帶着幾個問題切入:mysql

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

1.如何查看binlog和relaylog

   在mysql中能夠經過以下命令能夠查看都有哪些binlog日誌,如圖1-1:sql

mysql> show binary logs;

11.png
<center>圖1-1 查看binlog日誌</center>安全

  • Log_name 日誌文件名稱。
  • File_size 文件大小。
  • Encrypted 是否加密「No」表明未加密。

  在查看binlog時、主有本身的binlog、從也有本身的binlog。bash

  若是要查看最新的binlog,能夠經過以下命令查看,如圖1-2服務器

mysql> show master status;

12.png
<center>圖1-2 查看最新binlog</center>app

  經過以下命令能夠查看當前binlog事件,如圖1-3:ide

mysql> show binlog events;

13.png
<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;

14.png
<center>圖1-4 查看relaylog事件</center>
relaylog事件的參數含義和binlog的一致,可參考binlog。

  除了查看relaylog事件外,還能夠查看relaylog參數,如圖1-5所示。經過以下命令能夠查看:

mysql> show variables like '%relay%';

15.png
<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。

21.png
<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所示。
31.png
<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

32.png
<center>圖3-2 binlog信息 </center>
  能夠經過一張圖解析下圖3-2中內容,從十六進制格式中解析一下binlog格式,如圖3-3所示。
33.jpg
<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主從同步源碼解析

41.jpg

<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所示。
42.png
<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。
43.png
<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;
}

44.png
<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...

相關文章
相關標籤/搜索