MYSQL Binlog協議分析
此處不討論建立連接,驗證和handshake的交互協議
Binlog協議
一個MYSQL 通信包由包頭包體組成
包體根據具體的交互協議有自身的組成結構, 在binlog消息體組成結構如下
+=====================================+
| 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 |
+=====================================+
注意: 消息體里組成是包括2部分的,第一個字節是master發送給slave的errorcode, 第二個字節開始才是具體消息, 所以一個完整的MYSQL binlog通信包組成如下
一次binlog復制通信包含若干個binlog通信包
我們通過抓包工具抓取一次binlog復制來分析
我們在master里更新了一條數據, 產生了 41 這條通信數據, 里面包含了4個mysql binlog通信包
第一個binlog事件消息
頭3個字節 45 00 00就是消息長度, 注意,在mysql協議里,數值類型是用小序列來傳輸數據的(小序列: 低位先傳), 這條消息里實際消息長度內容是 00 00 45 , 也就是69(45是16進制, 69是10進制)
第4個字節2c是master傳過來的序號
第5個字節00時errorcode, 0表示沒有錯誤, 其他是錯誤
第6~9 4個字節是timestamp
第10 個字節02 是event type, 比如query, insert, update,delete等的類型碼
第11-14個字節是serverid, 這里是02 00 00 00, 也是小序列
第15~19個字節是eventlengthoffset, 這里是44 00 00 00, 也是小序列
第二個binlog事件消息
結構和第一個基本一樣, 只是消息長度,序號,eventtype等內容不一樣
以上是原生mysql復制binlog的交互協議情況, 如果開啟了semi半同步, 協議就發生變化, tcp通信次數, 包體結構等都和原來的有差異
抓包分析:
我們在master里更新了一條數據, 產生了 5 這條通信數據, 里面包含了5個mysql binlog通信包, 比原生的多了1條, 而且包結構也變成了以下結構
第一個binlog事件消息是新增的事件
第6個字節是新增的字節碼, ef是固定的值,表示這是個semi的消息
第7個字節是新增的字節碼, 0或者1, 0表示不需要回一個ack給master, 1表示需要, 一次復制通信(包含5個binlog事件)只有最后一個binlog事件的值為1
第8個字節開始就是具體消息, 和原生的一樣
最后一個binlog事件消息
第7個字節值是1 , 表示要返回一個ack給master
以下就是ack給master的內容
Slave ack給master 的消息是7這條通信數據
第1-3個字節師消息長度,這里的內容是12 00 00, 十進制內容是18
第4個字節是序號 , 這里是0, (注意: 每次tcp交互里這個序號都被重至為0)
第5個字節是semi標示 ,固定為ef
第6~14個字節,一共8個字節表示位置offset, ef 0a 00 00 00 00 00 00 同樣是小序列
第15個字節開始是binlog的文件名 每個字節存儲的是ascii碼, 如這里的4f=O, 4e=N,2e=.,30=0,36=6, 就是ON.000006
如果安裝了semi插件后,並不啟用semi同步,就不會發送ack給master, 就是上面7這個數據包不會發送
Master要發送semi字節給slave前提是slave連接上master后必須發送SET @rpl_semi_sync_slave= 1 指令給master, 看下面抓包效果
1c 00 00 00 是包頭
0x03 是指令碼 COM_QUERY
53開始后面的字節都是具體字符串的ascii碼
完整事件流
Slave連接上master后, master 首先會發ROTATE_EVENT和FORMAT_DESCRIPTION_EVENT 2個事件給slave, ROTATE_EVENT事件告訴slave下一個要讀取的binlog(可以理解成初始化要讀取的binlog)
Master發生一個插入sql, 如insert into test1 values(15), 分別發送給slave的事件是ANONYMOUS_GTID_LOG_EVENT-->QUERY_EVENT-->TABLE_MAP_EVENT-->WRITE_ROWS_EVENT-->XID_EVENT
(1)
ANONYMOUS_GTID_LOG_EVENT
(2)
QUERY_EVENT
header {
version: 1
logfileName: "20170105-162017-bin.000001"
logfileOffset: 1920
serverId: 1
serverenCode: "UTF-8"
executeTime: 1508809530000
sourceType: MYSQL
schemaName: ""
tableName: ""
eventLength: 72
}
entryType: TRANSACTIONBEGIN
storeValue: " \354\001"
(3)
TABLE_MAP_EVENT
(4)
WRITE_ROWS_EVENT
header {
version: 1
logfileName: "20170105-162017-bin.000001"
logfileOffset: 2040
serverId: 1
serverenCode: "UTF-8"
executeTime: 1508809530000
sourceType: MYSQL
schemaName: "test"
tableName: "test1"
eventLength: 40
eventType: INSERT
}
entryType: ROWDATA
storeValue: "\b\333\001\020\001P\000b\035\022\033\b\000\020\004\032\002id \000(\0010\000B\00215R\aint(11)"
(5)
XID_EVENT
header {
version: 1
logfileName: "20170105-162017-bin.000001"
logfileOffset: 2080
serverId: 1
serverenCode: "UTF-8"
executeTime: 1508809530000
sourceType: MYSQL
schemaName: ""
tableName: ""
eventLength: 31
}
entryType: TRANSACTIONEND
storeValue: "\022\003184"
PS:
如果本地沒有存儲position,在slave啟動的時候就會調用show master status 獲取master 最新的position更新到本地
在實驗中發現 master 的timeout時間設置很長, 當新日志送到canal, canal關閉,master由於收不到ack,一直hold着,再次啟動canal, master hold住的事務能夠進行下去,數據也落盤到master硬盤里,但再次啟動的canal就沒有收到之前中斷掉的binlog(開始position是master最后的position)