写了一个 MySQL 代理
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 协议》,转载必须注明作者和本文链接
推荐文章: