花瓣網的搜索架構須要重構,尤爲是在索引創建或者更新層面。mysql
目前的一個架構致使的結果就是時間越久,數據本體與搜索引擎索引中的數據越不一樣步,相差甚大。linux
新的一個架構打算從 MySQL 的 Binlog 中讀取數據更新、刪除、新增等歷史記錄,並把相應信息提取出來丟到隊列中慢慢去同步。git
因此我就在這裏小小去了解一下 Binlog。github
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
一般狀況 MySQL 是默認關閉 Binlog 的,因此你得配置一下以啓用它。服務器
啓用的過程就是修改配置文件 my.cnf
了。
至於 my.cnf
位置請自行尋找。例如經過 OSX 的 brew
安裝的 mysql
默認配置目錄一般在
/usr/local/Cellar/mysql/$VERSION/support-files/my-default.cnf
這個時候須要將它拷貝到 /etc/my.cnf
下面。
緊接着配置 log-bin
和 log-bin-index
的值,若是沒有則自行加上去。
inilog-bin=master-bin log-bin-index=master-bin.index
這裏的 log-bin
是指之後生成各 Binlog 文件的前綴,好比上述使用 master-bin
,那麼文件就將會是 master-bin.000001
、master-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 的基礎文件名了。
那咱們進去看,好比個人這邊就有這麼幾個文件:
很容易發現,裏面有 master-bin.index
和 master-bin.000001
兩個文件,這兩個文件在上文中有提到過了。
咱們打開那個 master-bin.index
文件,會發現這個索引文件就是一個普通的文本文件,而後列舉了各 binlog 的文件名。而 master-bin.000001
文件就是一堆亂碼了——畢竟人家是二進制文件。
索引文件就是上文中的 master-bin.index
文件,是一個普通的文本文件,以換行爲間隔,一行一個文件名。好比它多是:
master-bin.000001 master-bin.000002 master-bin.000003
而後對應的每行文件就是一個 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 格式不必定同樣,因此也沒有一個定性。在我寫這篇文章的時候,目前有三種版本的格式。
實際上還有一個 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_EVENT
的 type_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 事件中應該是有三個部分組成,header
、post-header
和 payload
,不過一般狀況下咱們把 post-header
和 payload
都歸結爲事件體,實際上這個 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 的內部二進制解析結構。方便你們造輪子用——否則老用別人的輪子,只知其然而不知其因此然多沒勁。
好了要下班了,就寫到這裏過吧。