解讀 MySQL Client/Server Protocol: Connection & Replication

解讀 MySQL Client/Server Protocol: Connection & Replication

MySQL 客戶端與服務器之間的通訊基於特定的 TCP 協議,本文將會詳解其中的 Connection 和 Replication 部分,這兩個部分分別對應的是客戶端與服務器創建鏈接、完成認證鑑權,以及客戶端註冊成爲一個 slave 並獲取 master 的 binlog 日誌。mysql

Connetcion Phase

MySQL 客戶端想要與服務器進行通訊,第一步就是須要成功創建鏈接,整個過程以下圖所示:
connection-phase算法

  1. client 發起一個 TCP 鏈接。
  2. server 響應一個 Initial Handshake Packet(初始化握手包),內容會包含一個默認的認證方式。
  3. 這一步是可選的,雙方創建 SSL 加密鏈接。
  4. client 迴應 Handshake Response Packet,內容須要包括用戶名和按照指定方式進行加密後的密碼數據。
  5. server 響應 OK_Packet 確認認證成功,或者 ERR_Packet 表示認證失敗並關閉鏈接。

Packet

一個 Packet 其實就是一個 TCP 包,全部包都有一個最基本的結構:
packetsql

如上圖所示,全部包均可以看做由 header 和 body 兩部分構成:第一部分 header 總共有 4 個字節,3 個字節用來標識 body 即 payload 的大小,1 個字節記錄 sequence ID;第二部分 body 就是 payload 實際的負載數據。數據庫

因爲 payload length 只有 3 個字節來記錄,因此一個 packet 的 payload 的大小不能超過 2^24 = 16 MB ,示例:
packet-example服務器

Packet :網絡

  • 當數據不超過 16 MB 時,準確來講是 payload 的大小不超過 2^24-1 Byte(三個字節所能表示的最大整數 0xFFFFFF),發送一個 packet 就夠了。
  • 當數據大小超過了 16 MB 時,就須要把數據切分紅多個 packet 傳輸。
  • 當數據 payload 的恰好是 2^24-1 Byte 時,一個包雖然足夠了,可是爲了表示數據傳輸完畢,仍然會多傳一個 payload 爲空的 packet 。


Sequence ID:包的序列號,從 0 開始遞增。在一個完整的會話過程當中,每一個包的序列號依次加一,當開始一個新的會話時,序列號從新從 0 開始。例如:在創建鏈接的階段,server 發送 Initial Handshake Packet( Sequence ID 爲 0 ),client 迴應 Handshake Response Packet( Sequence ID 爲 1 ),server 再響應 OK_Packet 或者 ERR_Packet( Sequence ID 爲 2 ),而後創建鏈接的階段就結束了,再有後續的命令數據,包的 Sequence ID 就從新從 0 開始;在命令階段(client 向 server 發送增刪改查這些都屬於命令階段),一個命令的請求和響應就能夠看做一個完整的會話過程,好比 client 先向 server 發送了一個查詢請求,而後 server 對這個查詢請求進行了響應,那麼這一次會話就結束了,下一個命令就是新的會話,Sequence ID 也就從新從 0 開始遞增。dom

Initial Handshake Packet <span id="Initial_Handshake_Packet"></span>

創建鏈接時,當客戶端發起一個 TCP 鏈接後,MySQL 服務端就會迴應一個 Initial Handshake Packet ,這個初始化握手包的數據格式以下圖所示:
handshakeV10異步

這個圖從上往下依次是:函數

  • 1 個字節的整數,表示 handshake protocol 的版本,如今都是 10 。
  • 以 NUL(即一個字節 0x00)結尾的字符串,表示 MySQL 服務器的版本,例如 5.7.18-log
  • 4 個字節的整數,表示線程 id,也是這個鏈接的 id。
  • 8 個字節的字符串,auth-plugin-data-part-1 後續密碼加密須要用到的隨機數的前 8 位。
  • 1 個字節的填充位。
  • 2 個字節的整數,capability_flags_1Capabilities Flags 的低位 2 位字節。
  • 1 個字節的整數,表示服務器默認的字符編碼格式,好比 utf8_general_ci
  • 2 個字節的整數,服務器的狀態標識。
  • 2 個字節的整數,capability_flags_2Capabilities Flags 的高位 2 位字節。
  • 1 個字節的整數,若是服務器具備 CLIENT_PLUGIN_AUTH 的能力(其實就是可以進行客戶端身份驗證,基本都支持),那麼傳遞的是 auth_plugin_data_len 即加密隨機數的長度,不然傳遞的是 0x00 。
  • 10 個字節的填充位,所有是 0x00 。
  • auth_plugin_data_len 指定長度的字符串,auth-plugin-data-part-2 加密隨機數的後 13 位。
  • 若是服務器具備 CLIENT_PLUGIN_AUTH 的能力(其實就是可以進行客戶端身份驗證,基本都支持),那麼傳遞的是 auth_plugin_name 即用戶認證方式的名稱。

對於 MySQL 5.x 版本,默認的用戶身份認證方式叫作 mysql_native_password(對應上面的 auth_plugin_name),這種認證方式的算法是:post

SHA1( password ) XOR SHA1( "20-bytes random data from server" <concat> SHA1( SHA1( password ) ) )

其中加密所需的 20 個字節的隨機數就是 auth-plugin-data-part-1( 8 位數)和 auth-plugin-data-part-2( 13 位中的前 12 位數)組成。

注意:MySQL 使用的小端字節序。

看到這,你可能還對 Capabilities Flags 感到很困惑。

Capabilities Flags

Capabilities Flags 其實就是一個功能標誌,用來代表服務端和客戶端支持並但願使用哪些功能。爲何須要這個功能標誌?由於首先 MySQL 有衆多版本,每一個版本可能支持的功能有區別,因此服務端須要代表它支持哪些功能;其次,對服務端來講,鏈接它的客戶端能夠是各類各樣的,這些客戶端但願使用哪些功能也是須要代表的。

Capabilities Flags 通常是 4 個字節的整數:
capability

如上圖所示,每一個功能都獨佔一個 bit 位。

Capabilities Flags 一般都是多個功能的組合表示,例如要表示 CLIENT_PROTOCOL_41CLIENT_PLUGIN_AUTHCLIENT_SECURE_CONNECTION 這三個功能,那麼就把他們對應的 0x000002000x000800000x00008000 進行比特位或運算就能獲得最終的值 0x00088200 也就是最終的 Capabilities Flags

根據 Capabilities Flags 判斷是否支持某個功能,例如 Capabilities Flags 的值是 0x00088200,要判斷它是否支持 CLIENT_SECURE_CONNECTION 的功能,則直接進行比特位與運算便可,即 Capabilities Flags & CLIENT_SECURE_CONNECTION == CLIENT_SECURE_CONNECTION

Handshake Response Packet <span id="Handshake_Response_Packet"></span>

創建鏈接的過程當中,當客戶端收到了服務端的 Initial Handshake Packet 後,須要向服務端迴應一個 Handshake Response Packet ,包的數據格式以下圖所示:
handshake_response_41

依次是:

  • 4 個字節的整數,Capabilities Flags,必定要設置 CLIENT_PROTOCOL_41,對於 MySQL 5.x 版本,使用默認的身份認證方式,還須要對應的設置 CLIENT_PLUGIN_AUTHCLIENT_SECURE_CONNECTION
  • 4 個字節的整數,包大小的最大值,這裏指的是命令包的大小,好比一條 SQL 最多能多大。
  • 1 個字節的整數,字符編碼方式。
  • 23 個字節的填充位,全是 0x00。
  • 以 NUL(0x00)結尾的字符串,登陸的用戶名。
  • CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA 通常不使用。
  • 1 個字節的整數,auth_response_length,密碼加密後的長度。
  • auth_response_length 指定長度的字符串,密碼與隨機數加密後的數據。
  • 若是 CLIENT_CONNECT_WITH_DB 直接指定了鏈接的數據庫,則須要傳遞以 NUL(0x00)結尾的字符串,內容是數據庫名。
  • CLIENT_PLUGIN_AUTH 通常都須要,默認方式須要傳遞的值就是 mysql_native_password

能夠看到,Handshake Response PacketInitial Handshake Packet 實際上是相對應的。

OK_Packet & ERR_Packet <span id="OK_ERR"></span>

OK_PacketERR_Packet 是 MySQL 服務端通用的響應包。

OK_Packet

MySQL 5.7.5 版本之後,OK_Packet 還包含了 EOF_Packet(用來顯示警告和狀態信息)。區分 OK_PacketEOF_Packet:

  • OK: header = 0x00 and length of packet > 7
  • EOF: header = 0xfe and length of packet < 9

MySQL 5.7.5 版本以前,EOF_Packet 是一個單獨格式的包:

EOF_Packet

若是身份認證經過、鏈接創建成功,返回的 OK_Packet 就會是:

0x07 0x00 0x00 0x02 0x00 0x00 0x00 0x02 0x00 0x00 0x00


若是鏈接失敗,或者出現錯誤則會返回 ERR_Packet 格式的包:

ERR_Packet

Replication

想要獲取到 master 的 binlog 嗎?只要你對接實現 replication 協議便可。

replication

  1. client 與 server 之間成功創建鏈接、完成身份認證,這個過程就是上文所述的 connection phase 。
  2. client 向 server 發送 COM_REGISTER_SLAVE 包,代表要註冊成爲一個 slave ,server 響應 OK_Packet 或者 ERR_Packet,只有成功才能進行後續步驟。
  3. client 向 server 發送 COM_BINLOG_DUMP 包,代表要開始獲取 binlog 的內容。
  4. server 響應數據,多是:

    • binlog network stream( binlog 網絡流)。
    • ERR_Packet,表示有錯誤發生。
    • EOF_Packet,若是 COM_BINLOG_DUMP 中的 flags 設置爲了 0x01 ,則在 binlog 沒有更多新事件時發送 EOF_Packet,而不是阻塞鏈接繼續等待後續 binlog event 。

COM_REGISTER_SLAVE

客戶端向 MySQL 發送 COM_REGISTER_SLAVE ,代表它要註冊成爲一個 slave,包格式以下圖:
com_register_slave

除了 1 個字節的固定內容 0x15 和 4 個字節的 server-id ,其餘內容一般都是空或者忽略,須要注意的是這裏的 user 和 password 並非登陸 MySQL 的用戶名和密碼,只是 slave 的一種標識而已。

COM_BINLOG_DUMP

註冊成爲 slave 以後,發送 COM_BINLOG_DUMP 就能夠開始接受 binlog event 了。

com_binlog_dump

  • 1 個字節的整數,固定內容 0x12 。
  • 4 個字節的整數,binlog-pos 即 binlog 文件開始的位置。
  • 2 個字節的整數,flags,通常狀況下 slave 會一直保持鏈接等待接受 binlog event,可是當 flags 設置爲了 0x01 時,若是當前 binlog 所有接收完了,則服務端會發送 EOF_Packet 而後結束整個過程,而不是保持鏈接繼續等待後續 binlog event 。
  • 4 個字節的整數,server-id,slave 的身份標識,MySQL 能夠同時存在多個 slave ,每一個 slave 必須擁有不一樣的 server-id
  • 不定長字符串,binlog-filename,開始的 binlog 文件名。查看當前的 binlog 文件名和 pos 位置,能夠執行 SQL 語句 show master status ,查看全部的 binlog 文件,能夠執行 SQL 語句 show binary logs

Binlog Event

客戶端註冊 slave 成功,而且發送 COM_BINLOG_DUMP 正確,那麼 MySQL 就會向客戶端發送 binlog network stream 即 binlog 網絡流,所謂的 binlog 網絡流其實就是源源不斷的 binlog event 包(對 MySQL 進行的操做,例如 inset、update、delete 等,在 binlog 中是以一個或多個 binlog event 的形式存在的)。

Replication 的兩種方式:

  • 異步,默認方式,master 不斷地向 slave 發送 binlog event ,無需 slave 進行 ack 確認。
  • 半同步,master 向 slave 每發送一個 binlog event 都須要等待 ack 確認回覆。


Binlog 有三種模式:

  • statement ,binlog 存儲的是原始 SQL 語句。
  • row ,binlog 存儲的是每行的實際先後變化。
  • mixed ,混合模式,binlog 存儲的一部分是 SQL 語句,一部分是每行變化。


Binlog Event 的包格式以下圖:
binlog_event

每一個 Binlog Event 包都有一個肯定的 event header ,根據 event 類型的不一樣,可能還會有 post header 以及 payload 。

Binlog Event 的類型很是多:

  • Binlog Management:

    • START_EVENT_V3
    • FORMAT_DESCRIPTION_EVENT: MySQL 5.x 及以上版本 binlog 文件中的第一個 event,內容是 binlog 的基本描述信息。
    • STOP_EVENT
    • ROTATE_EVENT: binlog 文件發生了切換,binlog 文件中的最後一個 event。
    • SLAVE_EVENT
    • INCIDENT_EVENT
    • HEARTBEAT_EVENT: 心跳信息,代表 slave 落後了 master 多少秒(執行 SQL 語句 SHOW SLAVE STATUS 輸出的 Seconds_Behind_Master 字段)。
  • Statement Based Replication Events(binlog 爲 statement 模式時相關的事件):

    • QUERY_EVENT: 原始 SQL 語句,例如 insert、update ... 。
    • INTVAR_EVENT: 基於會話變量的整數,例如把主鍵設置爲了 auto_increment 自增整數,那麼進行插入時,這個字段實際寫入的值就記錄在這個事件中。
    • RAND_EVENT: 內部 RAND() 函數的狀態。
    • USER_VAR_EVENT: 用戶變量事件。
    • XID_EVENT: 記錄事務 ID,事務 commit 提交了纔會寫入。
  • Row Based Replication Events(binlog 爲 row 模式時相關的事件):

    • TABLE_MAP_EVENT: 記錄了後續事件涉及到的表結構的映射關係。
    • v0 事件對應 MySQL 5.1.0 to 5.1.15 版本
    • DELETE_ROWS_EVENTv0: 記錄了行數據的刪除。
    • UPDATE_ROWS_EVENTv0: 記錄了行數據的更新。
    • WRITE_ROWS_EVENTv0: 記錄了行數據的新增。
    • v1 事件對應 MySQL 5.1.15 to 5.6.x 版本
    • DELETE_ROWS_EVENTv1: 記錄了行數據的刪除。
    • UPDATE_ROWS_EVENTv1: 記錄了行數據的更新。
    • WRITE_ROWS_EVENTv1: 記錄了行數據的新增。
    • v2 事件對應 MySQL 5.6.x 及其以上版本
    • DELETE_ROWS_EVENTv2: 記錄了行數據的刪除。
    • UPDATE_ROWS_EVENTv2: 記錄了行數據的更新。
    • WRITE_ROWS_EVENTv2: 記錄了行數據的新增。
  • LOAD INFILE replication(加載文件的特殊場景,本文不作介紹):

    • LOAD_EVENT
    • CREATE_FILE_EVENT
    • APPEND_BLOCK_EVENT
    • EXEC_LOAD_EVENT
    • DELETE_FILE_EVENT
    • NEW_LOAD_EVENT
    • BEGIN_LOAD_QUERY_EVENT
    • EXECUTE_LOAD_QUERY_EVENT

想要解析具體某個 binlog event 的內容,只要對照官方文檔數據包的格式便可。

結語

MySQL Client/Server Protocol 協議其實很簡單,就是相互之間按照約定的格式發包,而理解了協議,相信你本身就能夠實現一個 lib 去註冊成爲一個 slave 而後解析 binlog 。

公衆號

相關文章
相關標籤/搜索