Mysql binlog日志解析


1. 摘要:

Mysql日志抽取與解析正如名字所將的那樣,分抽取和解析兩個部分。這里Mysql日志主要是指binlog日志。二進制日志由配置文件的log-bin選項負責啟用,Mysql服務器將在數據根目錄創建兩個新文件XXX-bin.001和XXX-bin.index,若配置選項沒有給出文件名,Mysql將使用主機名稱命名這兩個文件,其中.index文件包含一份全體日志文件的清單。Mysql會把用戶對所有數據庫的內容和結構的修改情況記入XXX-bin.n文件,而不會記錄SELECT和沒有實際操作意義的語句。

2. 設計概要:

本項目主要包括兩個獨立的模塊:1、日志抽取(mysql-tracker);2、日志解析(mysql-parser)。日志抽取主要負責與mysql進行交互,通過socket連接以及基於mysql的開源協議數據報文,來進行從mysql主庫上dump相應的日志數據下來。而日志解析主要負責與日志抽取模塊交互,通過將dump下來的bytes類型的數據,根據mysql協議,對bytes數據進行解析,並封裝成易讀的event對象。

2.1  流程概要:

mysql-tracker 總體流程設計:
tracker與mysql交互:
1. 建立socket連接
2. 加載上次退出時的位點信息(從checkpoint表中加載)
3. 利用socket連接發送基於mysql協議的數據包+checkpoint表中的位點信息,創建mysql主庫的binlog dump線程
4. 利用socket接受(監聽)mysql主庫傳過來的數據包
5. 解析數據包(有多種形式,OK包,EOF包,ERROR包,EVENT包等等)
6. 如果有EVENT包,將基於byte的數據包解析成event對象
7. 將對象存入List或Queue里面
tracker與hbase交互:
1. 從queue中接收固定量數據(上限:防止內存溢出,下限:防止頻繁I/O),或固定時間數據(防止內存溢出)。
2. 這里的數據就是event對象
3. 將event對象序列化,存入hbase(protobuf 和 entry)
4. 存入過程中,保證位點確認機制,如果有關於mysql binlog 的標志性位點,則將該event存入hbase后(注意這里有對特殊xid位點的確認機制,而parser是沒有的,直接確認即可),然后再將該event的位點信息存入checkpoint表(維護各種位點信息:包括mysql binlog位點,event表(存如序列化后的event)位點,entry表(存入反序列化后的event)位點)
5. 也就是說只要是存入hbase實體數據,都要伴隨位點確認機制。這里tracker確認兩個方面的位點:mysql binlog 位點(xid:binlog file name + next position) + event表位點(tracker寫位點:row key)

tracker 每分鍾記錄位點:

1. 每分中固定時間記錄確認的checkpoint位點(可能有重復,長時間沒有數據fetch重復最多)

mysql-parser 總體流程設計:(設計思路非常類似,只不過是mysql binlog變成了event表,parser fetch數據從這里fetch)
parser與event表交互:
1. 建立hbase連接
2. 加載上次退出的位點信息(從checkpoint表中加載)
3. 通過hbase連接+checkpoint表中的位點信息,不斷監聽event表一旦event表有更新,就從event表中把序列化的event fetch下來
4. 得到的序列化event(bytes) 存入List或Queue里面。
parser與hbase交互:
1. 從queue中接收固定量數據(上限:防止內存溢出,下限:防止頻繁I/O),或固定時間數據(防止內存溢出)。
2. 將序列化的event(bytes)反序列化成entry(其實就是event對象)
3. 將entry存入hbase。
4. 存入過程中,伴隨位點確認機制(直接確認位點,不需要特殊位點確認機制)(存入位點信息到checkpoint表中去:parser 讀 event表的位點(row key) + parser 寫entry表的位點(row key))
parser每分鍾確認位點:
1. 每分鍾固定時間記錄確認的checkpoint位點(可能有重復,長時間沒有數據fetch重復最多)

2.2 架構

tracker 與 parser都有同樣的3個線程結構 取數據線程、消費數據線程、每分鍾記錄線程。並且每個消費數據線程必定伴隨着位點確認的機制,就如前面2.1流程概要所說:
1、tracker的位點確認機制需要有xid的特殊event作為mysql主庫的位點確認點。
2、而hbase里面的位點確認除了tracker寫位點也需要是xid的event作為位點確認點,其他確認點沒有特殊位點的要求。

3. mysql 相關

主要涉及到mysql的通訊協議和mysql 日志協議。mysql通訊協議,這里主要是利用到與mysql交互中的收發數據包的解析;mysql日志協議,這里主要是利用受到數據包后得到event事件的數據包,然后解析event數據包會用到相關的日志協議。即前者主要用在mysql交互、數據收發上面;后者主要用於日志解析、數據封裝上面。

3.1 mysql 通訊協議

mysql通訊協議主要用於mysql客戶端與mysql服務端的交互,通訊協議通過SSL加密通訊、數據包壓縮通訊、連接階段的強交互性。

3.1.1 mysql數據包

如果客戶端要和服務端交互,他們會把數據打包成數據包的形式然后通過發送數據包的形式,實現信息的傳遞。數據包的具體格式如下:

 

例如一個COM_BINLOG_DUMP類型的數據包的payload(數據包體)是這樣的:

 


3.2 mysql 日志協議

binlog日志是一個對於mysql記錄各種變化的日志集合,開啟日志功能可以通--log-bin 選項來開啟。MySQL的二進制日志可以說或是MySQL最重要的日志了,它記錄了所有的DDL和DML(除了數據查詢語句)語句,以事件形式記錄,還包含語句所執行的消耗的時間,MySQL的二進制日志是失誤安全型的.
MySQL的二進制日志的作用是顯而易見的,可以方便的備份這些日志以便做數據恢復,也可以作為主從復制的同步文件。

3.2.1 event事件

mysql通過C++的類來描述事件的基本類型 log event,在這里我們可以通過mysql源碼的log_event.cc來詳細了解 各種各樣的event事件類型。log event是一個描述事件的基本類型,更加細致的log event 組成了基本的log event,即log event是可派生的,並派生處了一些描述事件信息更詳細的子事件類型。比如row event就是一個母事件類型。在mysql源碼中是通過一系列枚舉整數值來描述各個事件的,如下所示:
enum Log_event_type { 
  UNKNOWN_EVENT= 0, 
  START_EVENT_V3= 1, 
  QUERY_EVENT= 2, 
  STOP_EVENT= 3, 
  ROTATE_EVENT= 4, 
  INTVAR_EVENT= 5, 
  LOAD_EVENT= 6, 
  SLAVE_EVENT= 7, 
  CREATE_FILE_EVENT= 8, 
  APPEND_BLOCK_EVENT= 9, 
  EXEC_LOAD_EVENT= 10, 
  DELETE_FILE_EVENT= 11, 
  NEW_LOAD_EVENT= 12, 
  RAND_EVENT= 13, 
  USER_VAR_EVENT= 14, 
  FORMAT_DESCRIPTION_EVENT= 15, 
  XID_EVENT= 16, 
  BEGIN_LOAD_QUERY_EVENT= 17, 
  EXECUTE_LOAD_QUERY_EVENT= 18, 
  TABLE_MAP_EVENT = 19, 
  PRE_GA_WRITE_ROWS_EVENT = 20, 
  PRE_GA_UPDATE_ROWS_EVENT = 21, 
  PRE_GA_DELETE_ROWS_EVENT = 22, 
  WRITE_ROWS_EVENT = 23, 
  UPDATE_ROWS_EVENT = 24, 
  DELETE_ROWS_EVENT = 25, 
  INCIDENT_EVENT= 26, 
  HEARTBEAT_LOG_EVENT= 27, 
  ENUM_END_EVENT 
  /* end marker */ 
};
具體各種事件含義的詳細說明可以參照mysql官方說明文檔: http://dev.mysql.com/doc/internals/en/event-meanings.html

3.2.2 event 事件結構

接下來我們來看看一個通用事件的具體結構(參照mysql packet 數據包)
所有的event 都含有如下通用的事件結構:
+===================+
| event header      |
+===================+
| event data        |
+===================+
分別由時間頭和時間體組成。
而事件的內部結構隨mysql的版本不同而變化着,這里取出3個代表性的版本結構:
v1 :用於mysql 3.23
v3 :用於mysql 4.01
v4 :用於mysql 5.0 及 以上
v1 的event 結構:
+=====================================+
| event     | timestamp         0 : 4    |
| header  +----------------------------+
|               | type_code         4 : 1    |
|              +----------------------------+
|               | server_id         5 : 4    |
|              +----------------------------+
|               | event_length      9 : 4    |
+=====================================+
| event      | fixed part       13 : y    |
| data       +----------------------------+
|                | variable part              |
+=====================================+
v3 的 event 結構 :
+=====================================+
| event     | timestamp         0 : 4    |
| header  +----------------------------+
|              | type_code         4 : 1    |
|             +----------------------------+
|              | server_id         5 : 4    |
|             +----------------------------+
|              | event_length      9 : 4    |
|             +----------------------------+
|              | next_position    13 : 4    |
|             +----------------------------+
|              | flags            17 : 2    |
+=====================================+
| event    | fixed part       19 : y    |
| data     +----------------------------+
|             | variable part              |
+=====================================+
v4 的event結構:
+=====================================+
| event     | timestamp         0 : 4    |
| header  +----------------------------+
|              | type_code         4 : 1    |
|             +----------------------------+
|              | server_id         5 : 4    |
|             +----------------------------+
|              | event_length      9 : 4    |
|             +----------------------------+
|              | next_position    13 : 4    |
|             +----------------------------+
|              | flags            17 : 2    |
|             +----------------------------+
|              | extra_headers    19 : x-19 |
+=====================================+
| event     | fixed part        x : y    |
| data      +----------------------------+
|              | variable part              |
+=====================================+
更詳細的事件包數據可見: http://dev.mysql.com/doc/internals/en/event-header-fields.html相關頁面



4. 位點確認機制

在於mysql的交互過程中發現,xid event通常是作為一個事務的結尾(DML,DDL的話是Query作為結尾),現將DML和DDL的事件組成展示出來(過濾掉一些對解析日志無意義的事件):
DML:
1. QUERY EVENT
2. TABLE MAP EVENT
3. ROWS EVENT
4. XID EVENT
DDL:
1. QUERY EVENT
這里我們可以通過一定的辨識機制將DDL的QUERY EVENT 和 DML的XID EVENT歸為一類,所以我們把這種結束事務的時間統稱為特殊xid 事件。從調試中可以得到這樣一個推論:
在與mysql交互中,binlog dump線程的起始位點一定要是特殊xid事件的next position的。即特殊xid一定要作為mysql的結束標識,讀時候一定要確認這里的位點機制。
所以在tracker重啟,重新抓取數據時一定要從xid開始fetch數據,這樣就是位點確認機制的由來。
目前的位點確認機制有:
1. mysql的位點確認,必須是以xid位點來確認的,所以checpoint表存儲mysql位點信息的數據必須要是特殊xid事件
2. 寫event表的checkpoint位點確認,受mysql位點特殊xid的影響,這里checkpoint表中tracker寫event表的位點信息也必須是特殊xid的位點信息。(考慮這樣一種場景,大事務里面有很多個event,如果tracker在寫event表是crash掉了,這樣我們可以把大事務的第一個時間a[0] 到發生crash的時間a[i]成為臟數據,為什么呢??,因為如果重啟tracker,他與mysql的交互特性是必須要以xid作為起始位點才開始fetch event數據,所以我們tracker會又從這個大事務的a[0]開始fetch,如果hbase event不以xid作為位點確認,那么這次event表就變成a[0]......a[i] a[0] …..a [j] ,這樣a[0]......a[i]成了明確的臟數據,如果是以xid作為tracker寫event的位點確認,實際上就是重寫了一段a[0]......a[i]的數據,當然你可已在tracker fetch a[0]到a[i]這一段,先不寫hbase,到crash的位點再開始寫hbase也是可以的。注意這里有無限循環的bug漏洞)
3. 除以上兩個的位點,其他位點的確認均采取直接確認,不需要考慮特殊xid事件。

4.1 確認位點分類:

大致有以下幾類位點需要確認:




5. tracker設計

依照2.1的流程概要設計,其流程圖如下所示:




 這部分可以結合源代碼理解(Handler1.java)
1. prepare方法:
1. 建立mysql的兩個連接,其中一個連接用於fetch event數據,另外一個連接用於fetch表結構元數據。這里如果創建連接不成功,將一直處於創建連接流程。保證程序的存活不依賴與mysql的存活
2. 建立hbase連接,這里如果hbase沒有啟動會處於阻塞和重連的狀態。
3. 加載起始位點,既有mysql的起始位點也有event表的其實位點,注意這兩個位點都是受xid影響的位點,如果hbase沒有相關信息,這里我們用show master status的mysql命令,讓mysql位點處於本庫的最末端,而讓event表位點置0,即相當於清除所有數據,從0開始。
4. 啟動fetch 線程,開始從mysql主庫上fetch event數據。
5. 啟動per minute 線程,開始每分中記錄相應位點
6. 啟動persistence 線程,開始接受fetch到的event數據,並且序列化,然后存入hbase event表中,並且伴隨位點確認機制。(注意:這個線程其實就是Handler.run()方法,實際上run()方法也是一個線程的機制,只不過對Handler是不可見的而已)
2. fetch 線程:
1. preRun方法做一些初始化工作,包括設置binlog dump線程參數、send binlog dump讓fetch指針置為到起始位點(start position)、初始化數據抓取器fetcher
2. fetch方法,抓取一條event數據。
3. 加入queue多線程隊列中
4. kafka監控相關
5. 這里fetch是一個循環重連的機制,入股fetch方法失敗跳出第一層循環,通過外層循環和checkMysqlConn()方法時間fetch線程重連mysql。即如果fetch中途mysql crash掉,fetch線程會等待mysql有效后重連mysql。
3. Per minute 線程:
1. 每分鍾執行一次run方法
2. 具體是將得到的存儲位點的全局變量存入checkpoint中去。注意row key的設計
4. Persistence 線程 Handler1.run()方法:
1. 接受多線成queue的數據到list中,以此位一批數據,
2. 以數量的上限,下限和時間的閥值來判斷時候執行一批數據的持久化
3. 進行持久化。
4. 將一批event數據 序列化 然后 tracker寫入到event表中去。
5. 伴隨位點確認機制:當真正寫入event表數據成功后,看這一批數據是否有特殊xid事件,如果有則作為位點確認,寫入checkpoint表中去(tracker整體重啟,啟動的時候加載這個位點信息)







6. parser 設計

與tracker設計思路基本相同,不過是fetch的目的mysql換成hbase event表,以及位點信息的直接確認,不需要考慮特殊xid。這里不再詳述。
注意:所有的位點確認一定要是在持久化成功之后才開始位點確認。



7. 重連機制

tracker中的mysql connector建立過程加入重連與等待機制。
fetch線程中加入了正在fetch數據,mysql突然斷掉的重連機制。
hbase的斷掉的自身重連機制

8. 性能評測

目前尚未進行系統性的,正式的性能測試。
僅以單機作測試有 每1-2秒 tracker能fetch 1萬條數據,parser 每1萬條數據 需要耗時4~5秒左右。
本單機測試尚不能作為評測標准,其性能以機器的硬件性能的不同而不同,不能以此作為性能標准。

9. bug與優化

1. 對於巨大事務的海量事件的場景,可能存在潛在的無限循環bug,即到事件a[i] crash掉,然后重啟,重新fetch時 到 時間 a[i]再一次crash,然后再重啟,這樣一直不停地循環,永遠掃描不完着一個巨量的大事務。
2. tracker與parser的數據交接目前仍是單線程的模式,可以考慮大規模分布式並行的模式,使tracker與parser在數據交接上能夠提升效率(與mysql的交接,與hbase的交接)。

10. 結論

基於單機的,傳統的,MySQL解析就到這里,主要是利用了mysql的協議進行數據傳遞與解析,后面組件考慮基於分布式的,基於大規模並行化的,基於高HA的模式。

 

11. 項目地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM