MySQL Proxy 是官方曾推出的基於 Lua 腳本的中間件,通過攔截 SQL 協議實現讀寫分離、負載均衡、SQL 審計、熔斷等功能。雖官方已停止維護 (GA 版停留在 0.8.x),但其**架構設計極具啟發性**,理解其原理有助於掌握現代中間件 (MyCat, ProxySQL, ShardingSphere-Proxy) 的核心邏輯。本文詳細還原部署流程、Lua 腳本機制、測試驗證及生產選型建議。
Client (App)
│
▼
MySQL Proxy (Listener Port 3307)
├── Lua Script Engine (rw-splitting.lua)
│ ├── 決策邏輯: SELECT → 從庫 ; 非 SELECT / 事務 → 主庫
│ ├── 連接池管理: 保持到後端的長連接
│ └── 狀態檢測: 後端存活檢查
│
├── Backend Master (192.168.100.104:3306) -- 寫入
└── Backend Slaves (192.168.100.210:3306, ...) -- 讀取
# Lua (Proxy 依賴)
yum install -y lua lua-devel
# 或源碼安裝指定版本
wget https://www.lua.org/ftp/lua-5.1.4.tar.gz
tar zxf lua-5.1.4.tar.gz && cd lua-5.1.4 && make linux test && make install
# MySQL Proxy (預編譯包)
wget https://downloads.mysql.com/archives/get/file/mysql-proxy-0.8.5-linux-glibc2.12-x86-64bit.tar.gz
tar zxf mysql-proxy-0.8.5-*.tar.gz
mv mysql-proxy-0.8.5 /usr/local/mysql-proxy
export PATH=$PATH:/usr/local/mysql-proxy/bin
腳本核心函數 read_query() 攔截每條 SQL:
-- 簡化版核心邏輯
function read_query(packet)
if packet:byte() == proxy.COM_QUERY then
local sql = packet:sub(2)
-- 事務控制語句路由到主庫
if sql:match("^%s*(START TRANSACTION|BEGIN|COMMIT|ROLLBACK)") then
proxy.connection.backend_ndx = 1 -- Master 索引
return
end
-- SELECT (非事務中) 路由到從庫
if sql:match("^%s*SELECT") and not proxy.connection.in_transaction then
proxy.connection.backend_ndx = math.random(2, #proxy.global.backends) -- 隨機從庫
return
end
-- 其他 (INSERT/UPDATE/DELETE/DDL) 路由到主庫
proxy.connection.backend_ndx = 1
end
end
mysql-proxy --daemon \
--proxy-address=:3307 \ # Proxy 監聽端口 (應用連這裡)
--proxy-backend-addresses=192.168.100.104:3306 \ # Master (索引 1)
--proxy-read-only-backend-addresses=192.168.100.210:3306 \ # Slave 1 (索引 2)
--proxy-read-only-backend-addresses=192.168.100.208:3306 \ # Slave 2 (索引 3)
--proxy-lua-script=/usr/local/mysql-proxy/share/mysql-proxy/rw-splitting.lua \
--admin-address=:4041 \ # 管理接口 (可查看連接池狀態)
--admin-username=admin --admin-password=secret \
--log-level=message --log-file=/var/log/mysql-proxy.log \
--pid-file=/var/run/mysql-proxy.pid
-- Master & Slave 均需創建代理用戶
GRANT ALL PRIVILEGES ON `business_db`.* TO 'proxy_user'@'192.168.100.%' IDENTIFIED BY 'StrongPass!';
FLUSH PRIVILEGES;
# 連接 Proxy
mysql -h127.0.0.1 -P3307 -uproxy_user -p
# 測試 1: 寫入 → 應路由到 Master (104)
CREATE TABLE test_rw (id INT PRIMARY KEY, val VARCHAR(20));
INSERT INTO test_rw VALUES (1, 'write_master');
-- 在 104 和 210/208 分別查詢:只有 104 有數據
# 測試 2: 讀取 → 應路由到 Slave (210/208)
SELECT * FROM test_rw;
-- 觀察 Proxy 日誌或管理接口確認後端連接分發
# 測試 3: 事務 → 必須路由到 Master
BEGIN;
INSERT INTO test_rw VALUES (2, 'in_tx');
COMMIT;
-- 事務內的 SELECT 也應走 Master (rw-splitting.lua 通過 in_transaction 標識保證)
-- 連接池設置 (關鍵!默認值極小)
-- min_idle_connections: 每個後端保持的最小空閒連接數
-- max_idle_connections: 最大空閒連接數
-- 建議根據 max_connections 設置,如:
min_idle_connections = 4
max_idle_connections = 16
-- 連接驗證 (防止後端斷開導致報錯)
-- is_master_down() 邏輯優化
-- 自動剔除故障節點
| 問題 | 影響 | 現狀 | ||
|---|---|---|---|---|
| 單線程事件循環 | 無法利用多核 CPU,高併發下 CPU 100% 成為瓶頸,延遲飆升 | 架構限制,無法修復 | ||
| Lua 腳本阻塞 | 複雜邏輯 (如解析 SQL、熔斷) 會阻塞事件循環,影響所有連接 | 需極度簡化腳本 | ||
| 無連接池複用 (早期版本) | 每請求建連,極大開銷 (後版本改進但仍弱於 ProxySQL) | 性能上限低 | ||
| 官方停止維護 (2016 年後) | 無安全補丁、無新功能、不兼容 MySQL 8.0 新協議 (caching_sha2_password) | **生產環境禁用** | ||
| 單點故障 | Proxy 掛掉 = 全業務不可用,需 Keepalived/VIP 搭建 HA | 運維複雜度高 |
| 中間件 | 語言/架構 | 核心優勢 | 適用場景 | 學習曲線 |
|---|---|---|---|---|
| ProxySQL | C++ / 多線程事件循環 | 高性能、多核、豐富路由規則 (Query Rules)、在線配置變更、Mirroring、原生支持 8.0、集群模式 | **首選**:讀寫分離、分庫分表、SQL 限流、緩存、遷移 | 中 |
| MyCat | Java / NIO | 成熟分片邏輯、多租戶、跨庫 Join、擴展性好 | 分庫分表 (Sharding) 核心場景 | 較陡 |
| ShardingSphere-Proxy | Java / Netty | Apache 頂級項目、分片/讀寫分離/影子庫/加密、標準化 SPI 擴展 | 雲原生、微服務治理、異構數據源 | 中 |
| Vitess | Go / gRPC | YouTube 驗證、極致擴展性、K8s 原生、連接池、查詢重寫、物理備份 | 超大規模、K8s 環境、雲廠商托管 | 高 |
| MaxScale (MariaDB) | C / 模塊化 | MariaDB 生態最佳、Binlog 服務器、自動故障轉移 | MariaDB 叢集、CDC 需求 | 中 |
MySQL Proxy 以 Lua 腳本靈活性開創了「可編程數據庫中間件」先河,**教科書級架構案例**。但受限於單線程、停止維護,**絕不建議用於生產核心鏈路**。現代選型首推 ProxySQL (輕量、高性能、功能全) 或 ShardingSphere-Proxy (標準化、生態強)。理解 Proxy 的「攔截-路由-連接池」三部曲,能讓你駕馭任何中間件得心應手。