MySQL-NonMySQL同步工具源碼解讀——鑑權與註冊

好吧,其實這節是解讀一個MySQL的一個事件捕獲工具——MySQL replication。MySQL的主從同步就是通過向Binlog寫入事件,由主庫向從庫傳遞,來完成的。那麼如果非MySQL的應用也想從主庫做同步,就需要自行解決獲取事件和解析的問題。這個工具就是做這件事的,其中有個例子就是把數據庫事件寫入Lucenet索引。

項目主頁詳見:https://launchpad.net/mysql-replication-listener。全C++風格編寫,網絡傳輸使用的Boost的asio庫,所有數據寫入和讀取基本上都採用流式完成。看着很不爽,幾次想改成傳統的結構體封裝內存結構的方式,自認爲對MySQL的Binlog文檔已經讀了很多,但是提到細節的理解還是不夠深刻,所以一直沒有完成。雖然文檔和MySQL源碼是瞭解協議的最好方式,可是前者內容較多,每次都是抓不到重點,後者嘗試着讀過幾天,(昨天才發現mysqlbinlog也能模擬爲從庫方式向主庫同步,獲取時間信息,要是之前就發現就好了,還是自己能力不夠啊!)和別的模塊耦合比較嚴重,有種牽一髮而動全身的感覺(這個成語用在這裏是不對的,有種迅雷不及掩耳盜鈴兒響叮噹之勢),真正應用到自己寫的應用還是感覺有難度。所以還是先從這個開源小工具入手吧。


這個故事,就先從程序啓動開始。

工具以庫的方式提供給使用者,接口是Binlog類。首先需要創建這樣的對象,在這時根據參數確定是解析本地的Binlog文件還是以找主庫去同步去。這裏我們只討論後者,因此Binarary_log_driver使用的是Binlog_tcp_driver。

創建過後需要調用connect方法。確定數據庫的標識爲username password ip:port。因此這幾個參數都要傳進去(端口可以不指定,MySQL默認是3306,要和主庫一致)。這個方法執行三個步驟:鑑權--確定首次讀取的偏移--開始循環讀取。

每個數據包,頭部都由固定的4個字節開始。前三個字節是數據包長度,採用小端序,第四個字節是包的序號,一般是自增。第四個字節同包的正文(文檔中稱其爲payload),一起算到包的總長度中去,而前三個字節不算在內。後文介紹的包格式,沒有包括固定的這前4個字節。


鑑權(sync_connect_and_authenticate)

MySQL的鑑權分爲Plain text和SSL兩種,對於局域網的話,前者就可以了,實現也比較簡單。整個流程可以用下圖來表示。

認證協議這塊還要多說兩句。歷史上,MySQL有兩種鑑權協議。4.0以及以前的版本使用Old Password Authentication從MySQL4.1開始,使用  Secure Password Authentication作爲密碼的簽名方式。舊的加密採用8Byte的密鑰,簽名結果也是採用8Byte。這種加密強度很差,其後更換了加密方式,即20byte的密鑰,20Byte的結果。

由於使用的MySQL是5.5+,這裏忽略舊的加密方式以及加密切換,也不使用鑑權相關的plug-in,只介紹官方的方式。

wKiom1L5mcGxBi9BAAB9vaQoi0I217.jpg

①以io_service結構體建立到MySQL的tcp連接,主要是通過域名解析ip,逐個嘗試每個ip,直到連接建立好。

②讀取MySQL發來的握手包,版本一般是V10。格式如下,

1              [0a] protocol version
string[NUL]    server version
4              connection id
string[8]      auth-plugin-data-part-1
1              [00] filler
2              capability flags (lower 2 bytes)
  if more data in the packet:
1              character set
2              status flags
2              capability flags (upper 2 bytes)
  if capabilities & CLIENT_PLUGIN_AUTH {
1              length of auth-plugin-data
  } else {
1              [00]
  }
string[10]     reserved (all [00])
  if capabilities & CLIENT_SECURE_CONNECTION {
string[$len]   auth-plugin-data-part-2 ($len=MAX(13, length of auth-plugin-data - 8))
  if capabilities & CLIENT_PLUGIN_AUTH {
string[NUL]    auth-plugin name
  }


整個反序列化解析的過程在函數proto_get_handshake_package裏面,需要注意的是,這裏的capability flags非常有用,此後的解析都需要他。特別是其中的CLIENT_PROTOCOL_41,非常重要。其他位可以參見https://dev.mysql.com/doc/internals/en/capability-flags.html。可以說,MySQL4.1是新舊協議上的重大分水嶺。

③發起認證(authenticate)

類似的,認證包也有衆多格式。對於5.5+的MySQL而言,一般是採用Handshake41版,格式如下。

4              capability flags, CLIENT_PROTOCOL_41 always set
4              max-packet size
1              character set
string[23]     reserved (all [0])
string[NUL]    username
  if capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA {
lenenc-int     length of auth-response
string[n]      auth-response
  } else if capabilities & CLIENT_SECURE_CONNECTION {
1              length of auth-response
string[n]      auth-response
  } else {
string[NUL]    auth-response
  }
  if capabilities & CLIENT_CONNECT_WITH_DB {
string[NUL]    database
  }
  if capabilities & CLIENT_PLUGIN_AUTH {
string[NUL]    auth plugin name
  }
  if capabilities & CLIENT_CONNECT_ATTRS {
lenenc-int     length of all key-values
lenenc-str     key
lenenc-str     value
   if-more data in 'length of all key-values', more keys and value pairs
  }


最終的密碼是將裸密碼經過三次SHA-1計算而得,Key用到了先前的兩個scramble_buff。prot_client_flags由客戶端自行選擇,爲了實現簡單,最好不要啓用SSL、壓縮、分段事件等複雜協議。最大報文長度傳0xffffff。


讀服務端傳來的認證結果。

   這裏涉及MySQL包的主庫通用返回類型,主要是三種:OK、ERROR、EOF。對應的格式分別是:

   OK

1              [00] the OK header
lenenc-int     affected rows
lenenc-int     last-insert-id
  if capabilities & CLIENT_PROTOCOL_41 {
2              status_flags
2              warnings
  } elseif capabilities & CLIENT_TRANSACTIONS {
2              status_flags
  }
string[EOF]    info


ERROR

1              [ff] the ERR header
2              error code
  if capabilities & CLIENT_PROTOCOL_41 {
string[1]      '#' the sql-state marker
string[5]      sql-state
  }
string[EOF]    error-message

EOF

1              [fe] the EOF header
  if capabilities & CLIENT_PROTOCOL_41 {
2              warning count
2              status flags
  }


④向主庫註冊爲從庫

1              [14] COM_REGISTER_SLAVE
4              server-id
1              slaves hostname length
string[$len]   slaves hostname
1              slaves user len
string[$len]   slaves user
1              slaves password len
string[$len]   slaves password
2              slaves mysql-port
4              replication rank
4              master-id

這裏使用的命令是COM_REGISTER_SLAVE。MySQL暴露了一系列的COM_命令,其中有一部分稱之爲TEXT_PROTOCOL。使用這個COM_REGISTER_SLAVE要注意,任何從庫的ID都不要相同,否則會出現同步問題(因爲我們將不是用GTID的同步方式)。裏面有個很奇怪的字段叫replication rank,不知道是做什麼的,目前的MySQL只是簡單的忽略這個字段,所以傳什麼都OK。

隨後依舊是需要向③一樣接收並解析OK包。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章