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 結構
- 一個 Packet 中前三個 byte 表示 Payload 的長度(所以最大長度是 ffffff 16,777,215 即 2的24次方-1)
- 第四個 byte 表示序列號,序列號從 0 開始遞增,直到該命令完成後重置回 0
- 剩下的內容都屬於 Payload
MySQL 協議認證流程
- 客戶端連線後, MySQL 傳送 initial Handshake Packet
- 客戶端處理後傳送 Handshake Response Packet
- MySQL 認證透過後返回 OK_Packet,然後客戶端開始傳送查詢等命令
忽略斷開連線命令
php 程式在處理完請求後會向 MySQL 傳送 0x01 COM_QUIT
命令,該命令會使 MySQL 關閉連線,代理為了保持和複用連線,忽略該命令
備註
- 本例子只用於學習和驗證 golang 寫 MySQL 代理。因初學 golang,不能保證程式碼的正確和效率(測試了一下 php 連線 golang 代理 MySQL 反而效率更低了)。如果發現問題請不吝賜教
- 本例子只描述了 MySQL 協議的大概,並不完全準確,完整用法請查閱官方文件
本作品採用《CC 協議》,轉載必須註明作者和本文連結