初探 MySQL 的 Binlog

原文地址:https://xcoder.in/2015/08/10/mysql-binlog-try/html

  花瓣網的搜索架構須要重構,尤爲是在索引創建或者更新層面。mysql

  目前的一個架構致使的結果就是時間越久,數據本體與搜索引擎索引中的數據越不一樣步,相差甚大。linux

  新的一個架構打算從 MySQL 的 Binlog 中讀取數據更新、刪除、新增等歷史記錄,並把相應信息提取出來丟到隊列中慢慢去同步。git

  因此我就在這裏小小去了解一下 Binlog。github

準備工做

什麼是 Binlog

  MySQL Server 有四種類型的日誌——Error Log、General Query Log、Binary Log 和 Slow Query Log。sql

  第一個是錯誤日誌,記錄 mysqld 的一些錯誤。第二個是通常查詢日誌,記錄 mysqld 正在作的事情,好比客戶端的鏈接和斷開、來自客戶端每條 Sql Statement 記錄信息;若是你想準確知道客戶端到底傳了什麼瞎 [嗶嗶] 玩意兒給服務端,這個日誌就很是管用了,不過它很是影響性能。第四個是慢查詢日誌,記錄一些查詢比較慢的 SQL 語句——這種日誌很是經常使用,主要是給開發者調優用的。數據庫

  剩下的第三種就是 Binlog 了,包含了一些事件,這些事件描述了數據庫的改動,如建表、數據改動等,也包括一些潛在改動,好比 DELETE FROM ran WHERE bing = luan,然而一條數據都沒被刪掉的這種狀況。除非使用 Row-based logging,不然會包含全部改動數據的 SQL Statement。數組

  那麼 Binlog 就有了兩個重要的用途——複製和恢復。好比主從表的複製,和備份恢復什麼的。xcode

啓用 Binlog

  一般狀況 MySQL 是默認關閉 Binlog 的,因此你得配置一下以啓用它。服務器

  啓用的過程就是修改配置文件 my.cnf 了。

  至於 my.cnf 位置請自行尋找。例如經過 OSX 的 brew 安裝的 mysql 默認配置目錄一般在

/usr/local/Cellar/mysql/$VERSION/support-files/my-default.cnf

  這個時候須要將它拷貝到 /etc/my.cnf 下面。

詳見 <StackOverflow - MySQL 'my.cnf' location?>。

  緊接着配置 log-binlog-bin-index 的值,若是沒有則自行加上去。

inilog-bin=master-bin
log-bin-index=master-bin.index

  這裏的 log-bin 是指之後生成各 Binlog 文件的前綴,好比上述使用 master-bin,那麼文件就將會是 master-bin.000001master-bin.000002 等。而這裏的 log-bin-index 則指 binlog index 文件的名稱,這裏咱們設置爲 master-bin.index

  若是上述工做作完以後重啓 MySQL 服務,你能夠進入你的 MySQL CLI 驗證一下是否真的啓用了。

sh$ mysql -u $USERNAME ...

  而後在終端裏面輸入下面一句 SQL 語句:

sqlSHOW VARIABLES LIKE '%log_bin%';

  若是結果裏面出來這樣相似的話就表示成功了:

sh+---------------------------------+---------------------------------------+
| Variable_name                   | Value                                 |
+---------------------------------+---------------------------------------+
| log_bin                         | ON                                    |
| log_bin_basename                | /usr/local/var/mysql/master-bin       |
| log_bin_index                   | /usr/local/var/mysql/master-bin.index |
| log_bin_trust_function_creators | OFF                                   |
| log_bin_use_v1_row_events       | OFF                                   |
| sql_log_bin                     | ON                                    |
+---------------------------------+---------------------------------------+
6 rows in set (0.00 sec)

  更多的一些相關配置能夠參考這篇《MySQL 的 binary log 初探》。

隨便玩玩

  而後你就能夠隨便去執行一些數據變更的 SQL 語句了。當你執行了一堆語句以後就能夠看到你的 Binlog 裏面有內容了。

  如上表所示,log_bin_basename 的值是 /usr/local/var/mysql/master-bin 就是 Binlog 的基礎文件名了。

  那咱們進去看,好比個人這邊就有這麼幾個文件:

Binlog 文件

  很容易發現,裏面有 master-bin.indexmaster-bin.000001 兩個文件,這兩個文件在上文中有提到過了。

  咱們打開那個 master-bin.index 文件,會發現這個索引文件就是一個普通的文本文件,而後列舉了各 binlog 的文件名。而 master-bin.000001 文件就是一堆亂碼了——畢竟人家是二進制文件。

結構解析

索引文件

  索引文件就是上文中的 master-bin.index 文件,是一個普通的文本文件,以換行爲間隔,一行一個文件名。好比它多是:

master-bin.000001
master-bin.000002
master-bin.000003

  而後對應的每行文件就是一個 Binlog 實體文件了。

Binlog 文件

  Binlog 的文件結構大體由以下幾個方面組成。

文件頭

  文件頭由一個四字節 Magic Number,其值爲 1852400382,在內存中就是 "\xfe\x62\x69\x6e",參考 MySQL 源碼的 log_event.h,也就是 '\0xfe' 'b' 'i' 'n'

  與日常二進制同樣,一般都有一個 Magic Number 進行文件識別,若是 Magic Number 不吻合上述的值那麼這個文件就不是一個正常的 Binlog。

事件

  在文件頭以後,跟隨的是一個一個事件依次排列。每一個事件都由一個事件頭和事件體組成。

  事件頭裏面的內容包含了這個事件的類型(如新增、刪除等)、事件執行時間以及是哪一個服務器執行的事件等信息。

  第一個事件是一個事件描述符,描述了這個 Binlog 文件格式的版本。接下去的一堆事件將會按照第一個事件描述符所描述的結構版本進行解讀。最後一個事件是一個銜接事件,指定了下一個 Binlog 文件名——有點相似於鏈表裏面的 next 指針。

  根據《[High-Level Binary Log Structure and Contents](High-Level Binary Log Structure and Contents)》所述,不一樣版本的 Binlog 格式不必定同樣,因此也沒有一個定性。在我寫這篇文章的時候,目前有三種版本的格式。

  • v1,用於 MySQL 3.2.3
  • v3,用於 MySQL 4.0.2 以及 4.1.0
  • v4,用於 MySQL 5.0 以及更高版本

  實際上還有一個 v2 版本,不過只在早期 4.0.x 的 MySQL 版本中使用過,可是 v2 已通過於陳舊而且再也不被 MySQL 官方支持了。

一般咱們如今用的 MySQL 都是在 5.0 以上的了,因此就略過 v1 ~ v3 版本的 Binlog,若是須要了解 v1 ~ v3 版本的 Binlog 能夠自行前往上述的《High-level...》文章查看。

事件頭

  一個事件頭有 19 字節,依次排列爲四字節的時間戳、一字節的當前事件類型、四字節的服務端 ID、四字節的當前事件長度描述、四字節的下個事件位置(方便跳轉)以及兩字節的標識。

  用 ASCII Diagram 表示以下:

+---------+---------+---------+------------+-------------+-------+
|timestamp|type code|server_id|event_length|next_position|flags  |
|4 bytes  |1 byte   |4 bytes  |4 bytes     |4 bytes      |2 bytes|
+---------+---------+---------+------------+-------------+-------+

  也能夠字節編造一個結構體來解讀這個頭:

cstruct BinlogEventHeader
{
    int   timestamp;
    char  type_code;
    int   server_id;
    int   event_length;
    int   next_position;
    char  flags[2];
};

若是你要直接用這個結構體來讀取數據的話,須要加點手腳。

由於默認狀況下 GCC 或者 G++ 編譯器會對結構體進行字節對齊,這樣讀進來的數據就不對了,由於 Binlog 並非對齊的。爲了統一咱們須要取消這個結構體的字節對齊,一個方法是使用 #pragma pack(n),一個方法是使用 __attribute__((__packed__)),還有一種狀況是在編譯器編譯的時候強制把全部的結構體對其取消,即在編譯的時候使用 fpack-struct 參數,如:

```sh
$ g++ temp.cpp -o a -fpack-struct=1


  根據上述的結構咱們能夠明確獲得各變量在結構體裏面的偏移量,因此在 MySQL 源碼裏面([libbinlogevents/include/binlog_event.h](https://github.com/mysql/mysql-server/blob/5.7/libbinlogevents/include/binlog_event.h#L353))有下面幾個常量以快速標記偏移: ```c #define EVENT_TYPE_OFFSET 4 #define SERVER_ID_OFFSET 5 #define EVENT_LEN_OFFSET 9 #define LOG_POS_OFFSET 13 #define FLAGS_OFFSET 17

  而具體有哪些事件則在 libbinlogevents/include/binlog_event.h#L245 裏面被定義。若有個 FORMAT_DESCRIPTION_EVENT 事件的 type_code 是 1五、UPDATE_ROWS_EVENTtype_code 是 31。

  還有那個 next_position,在 v4 版本中表明從 Binlog 一開始到下一個事件開始的偏移量,好比到第一個事件的 next_position 就是 4,由於文件頭有一個字節的長度。而後接下去對於事件 n 和事件 n + 1 來講,他們有這樣的關係:

next_position(n + 1) = next_position(n) + event_length(n)

  關於 flags 暫時不須要了解太多,若是真的想了解的話能夠看看 MySQL 的相關官方文檔

事件體

  事實上在 Binlog 事件中應該是有三個部分組成,headerpost-headerpayload,不過一般狀況下咱們把 post-headerpayload 都歸結爲事件體,實際上這個 post-header 裏面放的是一些定長的數據,只不過有時候咱們不須要特別地關心。想要深刻了解能夠去查看 MySQL 的官方文檔。

  因此實際上一個真正的事件體由兩部分組成,用 ASCII Diagram 表示就像這樣:

+=====================================+
| event  | fixed part (post-header)   |
| data   +----------------------------+
|        | variable part (payload)    |
+=====================================+

  而這個 post-header 對於不一樣類型的事件來講長度是不同的,同種類型來講是同樣的,而這個長度的預先規定將會在一個「格式描述事件」中定好。

格式描述事件

  在上文咱們有提到過,在 Magic Number 以後跟着的是一個格式描述事件(Format Description Event),其實這只是在 v4 版本中的稱呼,在之前的版本里面叫起始事件(Start Event)。

  在 v4 版本中這個事件的結構以下面的 ASCII Diagram 所示。

+=====================================+
| event  | timestamp         0 : 4    |
| header +----------------------------+
|        | type_code         4 : 1    | = FORMAT_DESCRIPTION_EVENT = 15
|        +----------------------------+
|        | server_id         5 : 4    |
|        +----------------------------+
|        | event_length      9 : 4    | >= 91
|        +----------------------------+
|        | next_position    13 : 4    |
|        +----------------------------+
|        | flags            17 : 2    |
+=====================================+
| event  | binlog_version   19 : 2    | = 4
| data   +----------------------------+
|        | server_version   21 : 50   |
|        +----------------------------+
|        | create_timestamp 71 : 4    |
|        +----------------------------+
|        | header_length    75 : 1    |
|        +----------------------------+
|        | post-header      76 : n    | = array of n bytes, one byte per event
|        | lengths for all            |   type that the server knows about
|        | event types                |
+=====================================+

  這個事件的 type_code 是 15,而後 event_length 是大於等於 91 的值的,這個主要取決於全部事件類型數。

  由於從第 76 字節開始後面的二進制就表明一個字節類型的數組了,一個字節表明一個事件類型的 post-header 長度,即每一個事件類型固定數據的長度。

  那麼按照上述的一些線索來看,咱們能很是快地寫出一個簡單的解讀 Binlog 格式描述事件的代碼。

如上文所述,若是須要正常解讀 Binlog 文件的話,下面的代碼編譯時候須要加上 -fpack-struct=1 這個參數。

cpp#include <cstdio>
#include <cstdlib>

struct BinlogEventHeader
{
    int  timestamp;
    unsigned char type_code;
    int  server_id;
    int  event_length;
    int  next_position;
    short flags;
};

int main()
{
    FILE* fp = fopen("/usr/local/var/mysql/master-bin.000001", "rb");
    int magic_number;
    fread(&magic_number, 4, 1, fp);

    printf("%d - %s\n", magic_number, (char*)(&magic_number));

    struct BinlogEventHeader format_description_event_header;
    fread(&format_description_event_header, 19, 1, fp);

    printf("BinlogEventHeader\n{\n");
    printf("    timestamp: %d\n", format_description_event_header.timestamp);
    printf("    type_code: %d\n", format_description_event_header.type_code);
    printf("    server_id: %d\n", format_description_event_header.server_id);
    printf("    event_length: %d\n", format_description_event_header.event_length);
    printf("    next_position: %d\n", format_description_event_header.next_position);
    printf("    flags[]: %d\n}\n", format_description_event_header.flags);

    short binlog_version;
    fread(&binlog_version, 2, 1, fp);
    printf("binlog_version: %d\n", binlog_version);

    char server_version[51];
    fread(server_version, 50, 1, fp);
    server_version[50] = '\0';
    printf("server_version: %s\n", server_version);

    int create_timestamp;
    fread(&create_timestamp, 4, 1, fp);
    printf("create_timestamp: %d\n", create_timestamp);

    char header_length;
    fread(&header_length, 1, 1, fp);
    printf("header_length: %d\n", header_length);

    int type_count = format_description_event_header.event_length - 76;
    unsigned char post_header_length[type_count];
    fread(post_header_length, 1, type_count, fp);
    for(int i = 0; i < type_count; i++) 
    {
        printf("  - type %d: %d\n", i + 1, post_header_length[i]);
    }

    return 0;
}

  這個時候你獲得的結果有可能就是這樣的了:

1852400382 - �binpz�
BinlogEventHeader
{
    timestamp: 1439186734
    type_code: 15
    server_id: 1
    event_length: 116
    next_position: 120
    flags[]: 1
}
binlog_version: 4
server_version: 5.6.24-log
create_timestamp: 1439186734
header_length: 19
  - type 1: 56
  - type 2: 13
  - type 3: 0
  - type 4: 8
  - type 5: 0
  - type 6: 18
  - ...

  一共會輸出 40 種類型(從 1 到 40),如官方文檔所說,這個數組從 START_EVENT_V3 事件開始(type_code 是 1)。

跳轉事件

  跳轉事件即 ROTATE_EVENT,其 type_code 是 4,其 post-header 長度爲 8。

  當一個 Binlog 文件大小已經差很少要分割了,它就會在末尾被寫入一個 ROTATE_EVENT——用於指出這個 Binlog 的下一個文件。

  它的 post-header 是 8 字節的一個東西,內容一般就是一個整數 4,用於表示下一個 Binlog 文件中的第一個事件起始偏移量。咱們從上文就能得出在通常狀況下這個數字只多是四,就偏移了一個魔法數字。固然咱們講的是在 v4 這個 Binlog 版本下的狀況。

  而後在 payload 位置是一個字符串,即下一個 Binlog 文件的文件名。

各類不一樣的事件體

  因爲篇幅緣由這裏就不詳細舉例其它普通的不一樣事件體了,具體的詳解在 MySQL 文檔中同樣有介紹,用到什麼類型的事件體就能夠本身去查詢。

小結

  本文大概介紹了 Binlog 的一些狀況,以及 Binlog 的內部二進制解析結構。方便你們造輪子用——否則老用別人的輪子,只知其然而不知其因此然多沒勁。

  好了要下班了,就寫到這裏過吧。

參考

  1. MySQL's binary log 結構簡介,目測原文在 TaobaoDBA(已沒法訪問)
  2. MySQL Binlog 的介紹
  3. MySQL 的 binary log 初探
  4. High-Level Binary Log Structure and Contents and related official documents
  5. #pragma pack vs -fpack-struct for Intel C
相關文章
相關標籤/搜索