寫了一個 MySQL 代理

moodrain發表於2021-02-02

github 地址 github.com/moodrain/mysql-proxy

背景

因為之前主要寫 php,知道 php 一次請求就需要重新連線一次 MySQL 會影響效能。後面也發現了一些例如 SMProxy、kingshard 等的代理庫,這些庫都非常完善,能在生產環境上使用,不用再造輪子了。但本著學習 golang 的目的,還是來嘗試一下寫 MySQL 代理。

簡述

主要是利用 golang 協程的便利,建立多個協程與 MySQL 建立連線。當 php 處理完請求釋放連線時,golang 保持這個連線,等到下一次 php 開啟連線時複用這個連線。

主要程式碼如下

for i := 0; i < connCount; i++ {

    go func(proxy lib.ProxyConn) {

        // 在這裡阻塞,首次連線時需要等待新的客戶端請求
        proxy.NewClientConn(server)
        proxy.NewMysqlConn(mysqlUrl)

        // 首次連線需要完整的 MySQL 和客戶端握手流程
        err := proxy.Handshake()
        if err != nil {
            proxy.Close()
        }

        go proxy.PipeMysql2Client()
        go proxy.PipeClient2Mysql()

        for {
            if !proxy.IsClientClose() {
                continue
            }
            // 當客戶端關閉連線後,這裡阻塞,代理等待新的客戶端連線
            proxy.NewClientConn(server)
            // 因為代理與 MySQL 保持了連線,所以代理與客戶端只需要假握手
            err := proxy.FakeHandshake()
            if err != nil {
                proxy.CloseClient()
            }
            go proxy.PipeClient2Mysql()
        }

    }(connList[i])

}

參考

MySQL Packet 結構

官方文件

  1. 一個 Packet 中前三個 byte 表示 Payload 的長度(所以最大長度是 ffffff 16,777,215 即 2的24次方-1)
  2. 第四個 byte 表示序列號,序列號從 0 開始遞增,直到該命令完成後重置回 0
  3. 剩下的內容都屬於 Payload
MySQL 協議認證流程

官方文件

  1. 客戶端連線後, MySQL 傳送 initial Handshake Packet
  2. 客戶端處理後傳送 Handshake Response Packet
  3. MySQL 認證透過後返回 OK_Packet,然後客戶端開始傳送查詢等命令
忽略斷開連線命令

官方文件

php 程式在處理完請求後會向 MySQL 傳送 0x01 COM_QUIT 命令,該命令會使 MySQL 關閉連線,代理為了保持和複用連線,忽略該命令

備註

  1. 本例子只用於學習和驗證 golang 寫 MySQL 代理。因初學 golang,不能保證程式碼的正確和效率(測試了一下 php 連線 golang 代理 MySQL 反而效率更低了)。如果發現問題請不吝賜教
  2. 本例子只描述了 MySQL 協議的大概,並不完全準確,完整用法請查閱官方文件
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章