花瓣網(wǎng)的搜索架構(gòu)需要重構(gòu)围小,尤其是在索引建立或者更新層面躯枢。
目前的一個(gè)架構(gòu)導(dǎo)致的結(jié)果就是時(shí)間越久歉井,數(shù)據(jù)本體與搜索引擎索引中的數(shù)據(jù)越不同步,相差甚大。
新的一個(gè)架構(gòu)打算從 MySQL 的 Binlog 中讀取數(shù)據(jù)更新、刪除驼壶、新增等歷史記錄,并把相應(yīng)信息提取出來(lái)丟到隊(duì)列中慢慢去同步矗烛。
所以我就在這里小小去了解一下 Binlog。
準(zhǔn)備工作
什么是 Binlog
MySQL Server 有四種類(lèi)型的日志——Error Log箩溃、General Query Log瞭吃、Binary Log 和 Slow Query Log。
第一個(gè)是錯(cuò)誤日志涣旨,記錄 mysqld 的一些錯(cuò)誤歪架。第二個(gè)是一般查詢(xún)?nèi)罩荆涗?mysqld 正在做的事情霹陡,比如客戶(hù)端的連接和斷開(kāi)和蚪、來(lái)自客戶(hù)端每條 Sql Statement 記錄信息;如果你想準(zhǔn)確知道客戶(hù)端到底傳了什么瞎 [嗶嗶] 玩意兒給服務(wù)端烹棉,這個(gè)日志就非常管用了攒霹,不過(guò)它非常影響性能。第四個(gè)是慢查詢(xún)?nèi)罩窘矗涗浺恍┎樵?xún)比較慢的 SQL 語(yǔ)句——這種日志非常常用催束,主要是給開(kāi)發(fā)者調(diào)優(yōu)用的。
剩下的第三種就是 Binlog 了伏社,包含了一些事件抠刺,這些事件描述了數(shù)據(jù)庫(kù)的改動(dòng)塔淤,如建表、數(shù)據(jù)改動(dòng)等速妖,也包括一些潛在改動(dòng)高蜂,比如 DELETE FROM ran WHERE bing = luan
,然而一條數(shù)據(jù)都沒(méi)被刪掉的這種情況罕容。除非使用 Row-based logging备恤,否則會(huì)包含所有改動(dòng)數(shù)據(jù)的 SQL Statement。
那么 Binlog 就有了兩個(gè)重要的用途——復(fù)制和恢復(fù)杀赢。比如主從表的復(fù)制烘跺,和備份恢復(fù)什么的。
啟用 Binlog
通常情況 MySQL 是默認(rèn)關(guān)閉 Binlog 的脂崔,所以你得配置一下以啟用它滤淳。
啟用的過(guò)程就是修改配置文件 my.cnf
了。
至于 my.cnf
位置請(qǐng)自行尋找砌左。例如通過(guò) OSX 的 brew
安裝的 mysql
默認(rèn)配置目錄通常在
/usr/local/Cellar/mysql/$VERSION/support-files/my-default.cnf
這個(gè)時(shí)候需要將它拷貝到 /etc/my.cnf
下面脖咐。
詳見(jiàn) <StackOverflow - MySQL 'my.cnf' location?>。
緊接著配置 log-bin
和 log-bin-index
的值汇歹,如果沒(méi)有則自行加上去屁擅。
log-bin=master-bin
log-bin-index=master-bin.index
這里的 log-bin
是指以后生成各 Binlog 文件的前綴,比如上述使用 master-bin
产弹,那么文件就將會(huì)是 master-bin.000001
派歌、master-bin.000002
等。而這里的 log-bin-index
則指 binlog index 文件的名稱(chēng)痰哨,這里我們?cè)O(shè)置為 master-bin.index
胶果。
如果上述工作做完之后重啟 MySQL 服務(wù),你可以進(jìn)入你的 MySQL CLI 驗(yàn)證一下是否真的啟用了斤斧。
$ mysql -u $USERNAME ...
然后在終端里面輸入下面一句 SQL 語(yǔ)句:
SHOW VARIABLES LIKE '%log_bin%';
如果結(jié)果里面出來(lái)這樣類(lèi)似的話(huà)就表示成功了:
+---------------------------------+---------------------------------------+
| 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)
更多的一些相關(guān)配置可以參考這篇《MySQL 的 binary log 初探》早抠。
隨便玩玩
然后你就可以隨便去執(zhí)行一些數(shù)據(jù)變動(dòng)的 SQL 語(yǔ)句了。當(dāng)你執(zhí)行了一堆語(yǔ)句之后就可以看到你的 Binlog 里面有內(nèi)容了撬讽。
如上表所示蕊连,log_bin_basename
的值是 /usr/local/var/mysql/master-bin
就是 Binlog 的基礎(chǔ)文件名了。
那我們進(jìn)去看游昼,比如我的這邊就有這么幾個(gè)文件:

很容易發(fā)現(xiàn)甘苍,里面有 master-bin.index
和 master-bin.000001
兩個(gè)文件,這兩個(gè)文件在上文中有提到過(guò)了烘豌。
我們打開(kāi)那個(gè) master-bin.index
文件羊赵,會(huì)發(fā)現(xiàn)這個(gè)索引文件就是一個(gè)普通的文本文件,然后列舉了各 binlog 的文件名。而 master-bin.000001
文件就是一堆亂碼了——畢竟人家是二進(jìn)制文件昧捷。
結(jié)構(gòu)解析
索引文件
索引文件就是上文中的 master-bin.index
文件闲昭,是一個(gè)普通的文本文件,以換行為間隔靡挥,一行一個(gè)文件名序矩。比如它可能是:
master-bin.000001
master-bin.000002
master-bin.000003
然后對(duì)應(yīng)的每行文件就是一個(gè) Binlog 實(shí)體文件了。
Binlog 文件
Binlog 的文件結(jié)構(gòu)大致由如下幾個(gè)方面組成跋破。
文件頭
文件頭由一個(gè)四字節(jié) Magic Number簸淀,其值為 1852400382
,在內(nèi)存中就是 "\xfe\x62\x69\x6e"
毒返,參考 MySQL 源碼的 log_event.h租幕,也就是 '\0xfe' 'b' 'i' 'n'
。
與平常二進(jìn)制一樣拧簸,通常都有一個(gè) Magic Number 進(jìn)行文件識(shí)別劲绪,如果 Magic Number 不吻合上述的值那么這個(gè)文件就不是一個(gè)正常的 Binlog。
事件
在文件頭之后盆赤,跟隨的是一個(gè)一個(gè)事件依次排列贾富。每個(gè)事件都由一個(gè)事件頭和事件體組成。
事件頭里面的內(nèi)容包含了這個(gè)事件的類(lèi)型(如新增牺六、刪除等)颤枪、事件執(zhí)行時(shí)間以及是哪個(gè)服務(wù)器執(zhí)行的事件等信息。
第一個(gè)事件是一個(gè)事件描述符淑际,描述了這個(gè) Binlog 文件格式的版本畏纲。接下去的一堆事件將會(huì)按照第一個(gè)事件描述符所描述的結(jié)構(gòu)版本進(jìn)行解讀。最后一個(gè)事件是一個(gè)銜接事件春缕,指定了下一個(gè) Binlog 文件名——有點(diǎn)類(lèi)似于鏈表里面的 next
指針盗胀。
根據(jù)《[High-Level Binary Log Structure and Contents](High-Level Binary Log Structure and Contents)》所述,不同版本的 Binlog 格式不一定一樣淡溯,所以也沒(méi)有一個(gè)定性读整。在我寫(xiě)這篇文章的時(shí)候簿训,目前有三種版本的格式咱娶。
- v1,用于 MySQL 3.2.3
- v3强品,用于 MySQL 4.0.2 以及 4.1.0
- v4膘侮,用于 MySQL 5.0 以及更高版本
實(shí)際上還有一個(gè) v2 版本,不過(guò)只在早期 4.0.x 的 MySQL 版本中使用過(guò)的榛,但是 v2 已經(jīng)過(guò)于陳舊并且不再被 MySQL 官方支持了琼了。
通常我們現(xiàn)在用的 MySQL 都是在 5.0 以上的了,所以就略過(guò) v1 ~ v3 版本的 Binlog,如果需要了解 v1 ~ v3 版本的 Binlog 可以自行前往上述的《High-level...》文章查看雕薪。
事件頭
一個(gè)事件頭有 19 字節(jié)昧诱,依次排列為四字節(jié)的時(shí)間戳、一字節(jié)的當(dāng)前事件類(lèi)型所袁、四字節(jié)的服務(wù)端 ID盏档、四字節(jié)的當(dāng)前事件長(zhǎng)度描述、四字節(jié)的下個(gè)事件位置(方便跳轉(zhuǎn))以及兩字節(jié)的標(biāo)識(shí)燥爷。
用 ASCII Diagram 表示如下:
+---------+---------+---------+------------+-------------+-------+
|timestamp|type code|server_id|event_length|next_position|flags |
|4 bytes |1 byte |4 bytes |4 bytes |4 bytes |2 bytes|
+---------+---------+---------+------------+-------------+-------+
也可以字節(jié)編造一個(gè)結(jié)構(gòu)體來(lái)解讀這個(gè)頭:
struct BinlogEventHeader
{
int timestamp;
char type_code;
int server_id;
int event_length;
int next_position;
char flags[2];
};
如果你要直接用這個(gè)結(jié)構(gòu)體來(lái)讀取數(shù)據(jù)的話(huà)蜈亩,需要加點(diǎn)手腳。
因?yàn)槟J(rèn)情況下 GCC 或者 G++ 編譯器會(huì)對(duì)結(jié)構(gòu)體進(jìn)行字節(jié)對(duì)齊前翎,這樣讀進(jìn)來(lái)的數(shù)據(jù)就不對(duì)了稚配,因?yàn)?Binlog 并不是對(duì)齊的。為了統(tǒng)一我們需要取消這個(gè)結(jié)構(gòu)體的字節(jié)對(duì)齊港华,一個(gè)方法是使用
#pragma pack(n)
道川,一個(gè)方法是使用__attribute__((__packed__))
,還有一種情況是在編譯器編譯的時(shí)候強(qiáng)制把所有的結(jié)構(gòu)體對(duì)其取消苹丸,即在編譯的時(shí)候使用fpack-struct
參數(shù)愤惰,如:
$ g++ temp.cpp -o a -fpack-struct=1
根據(jù)上述的結(jié)構(gòu)我們可以明確得到各變量在結(jié)構(gòu)體里面的偏移量,所以在 MySQL 源碼里面([libbinlogevents/include/binlog_event.h](https://github.com/mysql/mysql-server/blob/5.7/libbinlogevents/include/binlog_event.h#L353))有下面幾個(gè)常量以快速標(biāo)記偏移:
```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 里面被定義赘理。如有個(gè) FORMAT_DESCRIPTION_EVENT
事件的 type_code
是 15宦言、UPDATE_ROWS_EVENT
的 type_code
是 31。
還有那個(gè) next_position
商模,在 v4 版本中代表從 Binlog 一開(kāi)始到下一個(gè)事件開(kāi)始的偏移量奠旺,比如到第一個(gè)事件的 next_position
就是 4,因?yàn)槲募^有一個(gè)字節(jié)的長(zhǎng)度施流。然后接下去對(duì)于事件 n 和事件 n + 1 來(lái)說(shuō)响疚,他們有這樣的關(guān)系:
next_position(n + 1) = next_position(n) + event_length(n)
關(guān)于 flags 暫時(shí)不需要了解太多,如果真的想了解的話(huà)可以看看 MySQL 的相關(guān)官方文檔瞪醋。
事件體
事實(shí)上在 Binlog 事件中應(yīng)該是有三個(gè)部分組成忿晕,header
、post-header
和 payload
银受,不過(guò)通常情況下我們把 post-header
和 payload
都?xì)w結(jié)為事件體践盼,實(shí)際上這個(gè) post-header
里面放的是一些定長(zhǎng)的數(shù)據(jù),只不過(guò)有時(shí)候我們不需要特別地關(guān)心宾巍。想要深入了解可以去查看 MySQL 的官方文檔咕幻。
所以實(shí)際上一個(gè)真正的事件體由兩部分組成,用 ASCII Diagram 表示就像這樣:
+=====================================+
| event | fixed part (post-header) |
| data +----------------------------+
| | variable part (payload) |
+=====================================+
而這個(gè) post-header
對(duì)于不同類(lèi)型的事件來(lái)說(shuō)長(zhǎng)度是不一樣的顶霞,同種類(lèi)型來(lái)說(shuō)是一樣的肄程,而這個(gè)長(zhǎng)度的預(yù)先規(guī)定將會(huì)在一個(gè)“格式描述事件”中定好。
格式描述事件
在上文我們有提到過(guò),在 Magic Number 之后跟著的是一個(gè)格式描述事件(Format Description Event)蓝厌,其實(shí)這只是在 v4 版本中的稱(chēng)呼玄叠,在以前的版本里面叫起始事件(Start Event)。
在 v4 版本中這個(gè)事件的結(jié)構(gòu)如下面的 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 |
+=====================================+
這個(gè)事件的 type_code
是 15诸典,然后 event_length
是大于等于 91 的值的,這個(gè)主要取決于所有事件類(lèi)型數(shù)崎苗。
因?yàn)閺牡?76 字節(jié)開(kāi)始后面的二進(jìn)制就代表一個(gè)字節(jié)類(lèi)型的數(shù)組了狐粱,一個(gè)字節(jié)代表一個(gè)事件類(lèi)型的 post-header
長(zhǎng)度,即每個(gè)事件類(lèi)型固定數(shù)據(jù)的長(zhǎng)度胆数。
那么按照上述的一些線(xiàn)索來(lái)看肌蜻,我們能非常快地寫(xiě)出一個(gè)簡(jiǎn)單的解讀 Binlog 格式描述事件的代碼必尼。
如上文所述蒋搜,如果需要正常解讀 Binlog 文件的話(huà),下面的代碼編譯時(shí)候需要加上
-fpack-struct=1
這個(gè)參數(shù)判莉。
#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;
}
這個(gè)時(shí)候你得到的結(jié)果有可能就是這樣的了:
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
- ...
一共會(huì)輸出 40 種類(lèi)型(從 1 到 40)有决,如官方文檔所說(shuō)皿渗,這個(gè)數(shù)組從 START_EVENT_V3
事件開(kāi)始(type_code
是 1)。
跳轉(zhuǎn)事件
跳轉(zhuǎn)事件即 ROTATE_EVENT
,其 type_code
是 4痒芝,其 post-header
長(zhǎng)度為 8侠驯。
當(dāng)一個(gè) Binlog 文件大小已經(jīng)差不多要分割了兄纺,它就會(huì)在末尾被寫(xiě)入一個(gè) ROTATE_EVENT
——用于指出這個(gè) Binlog 的下一個(gè)文件着降。
它的 post-header
是 8 字節(jié)的一個(gè)東西,內(nèi)容通常就是一個(gè)整數(shù) 4
泳炉,用于表示下一個(gè) Binlog 文件中的第一個(gè)事件起始偏移量憾筏。我們從上文就能得出在一般情況下這個(gè)數(shù)字只可能是四,就偏移了一個(gè)魔法數(shù)字花鹅。當(dāng)然我們講的是在 v4 這個(gè) Binlog 版本下的情況氧腰。
然后在 payload
位置是一個(gè)字符串,即下一個(gè) Binlog 文件的文件名刨肃。
各種不同的事件體
由于篇幅原因這里就不詳細(xì)舉例其它普通的不同事件體了古拴,具體的詳解在 MySQL 文檔中一樣有介紹,用到什么類(lèi)型的事件體就可以自己去查詢(xún)之景。
小結(jié)
本文大概介紹了 Binlog 的一些情況斤富,以及 Binlog 的內(nèi)部二進(jìn)制解析結(jié)構(gòu)膏潮。方便大家造輪子用——不然老用別人的輪子锻狗,只知其然而不知其所以然多沒(méi)勁。
好了要下班了,就寫(xiě)到這里過(guò)吧轻纪。
參考
- MySQL's binary log 結(jié)構(gòu)簡(jiǎn)介油额,目測(cè)原文在 TaobaoDBA(已無(wú)法訪問(wèn))
- MySQL Binlog 的介紹
- MySQL 的 binary log 初探
- High-Level Binary Log Structure and Contents and related official documents
- #pragma pack vs -fpack-struct for Intel C