第22章 MySQL Proxy深度实践:构建读写分离中间层
第22章 MySQL Proxy深度实践:构建读写分离中间层
在当今高并发的互联网应用中,数据库的读写压力越来越大。主从复制技术解决了数据备份和高可用问题,但应用程序需要自行处理读写分离逻辑:写操作发给主库,读操作发给从库。这不仅增加了代码复杂度,还需要在应用层维护多个数据源。MySQL Proxy作为一款轻量级的中间件,能够透明地实现读写分离,让应用程序像访问单库一样访问集群,大大简化了架构。
本章将深入讲解MySQL Proxy的原理、安装配置,并通过商业案例展示如何利用它构建高效的读写分离架构。同时,也会探讨其局限性及替代方案,帮助读者在实际项目中做出合理选型。
22.1 MySQL Proxy核心概念与作用
22.1.1 什么是MySQL Proxy
MySQL Proxy是一个位于客户端和MySQL服务器之间的代理程序,它监听客户端的连接请求,解析、修改或转发SQL语句到后端的MySQL服务器。它支持使用Lua脚本进行定制化处理,可以实现读写分离、负载均衡、查询过滤、日志记录等功能。
从架构上看,MySQL Proxy充当了数据库网关的角色。应用程序不再直接连接具体的MySQL实例,而是连接Proxy,由Proxy根据规则将请求分发到合适的后端数据库。这层抽象带来了诸多好处:
- 读写分离:自动将SELECT查询发送到从库,将INSERT/UPDATE/DELETE发送到主库。
- 负载均衡:在多个从库之间分发读请求,提升系统吞吐量。
- 高可用:当后端节点故障时,Proxy可以自动剔除故障节点,应用无感知。
- 安全控制:可以在Proxy层进行SQL过滤、权限检查,增强安全性。
- 监控与分析:记录所有SQL,便于性能分析和审计。
22.1.2 MySQL Proxy的工作原理
MySQL Proxy的核心是一个Lua虚拟机,它允许用户编写Lua脚本处理不同阶段的网络事件。主要事件包括:
- 连接事件:客户端连接建立时,可以决定是否允许连接、选择后端服务器。
- 认证事件:处理客户端认证信息。
- 查询事件:当客户端发送SQL查询时,Proxy可以解析SQL类型,修改SQL内容,选择后端服务器,并转发结果。
- 结果集事件:在返回结果前,可以修改结果集。
读写分离正是利用查询事件:在Lua脚本中判断SQL语句类型,如果是SELECT(且非事务内或特定标记),则选择一个从库转发;否则转发到主库。
22.1.3 适用场景与商业价值
在MySQL 5.7时代,MySQL Proxy是除应用层读写分离外的主流选择之一。尤其适合以下场景:
- 中小型互联网应用:初期使用一主多从架构,通过Proxy透明实现读写分离,避免应用改造。
- 快速迭代项目:希望快速上线读写分离功能,无需在代码中引入复杂的数据源路由。
- 数据库中间件轻量化需求:相比MyCAT、ShardingSphere等重量级中间件,MySQL Proxy更轻量,部署简单。
然而,随着技术的发展,MySQL Proxy存在一些局限(如性能瓶颈、Lua编程门槛、官方停止维护等),逐渐被MySQL Router、ProxySQL等替代。但在老系统中仍有大量部署,理解其原理对维护和迁移至关重要。
22.2 安装与配置实战
22.2.1 下载与安装MySQL Proxy
MySQL Proxy的官方下载地址为:https://downloads.mysql.com/archives/proxy/。MySQL 5.7对应推荐的Proxy版本为0.8.5。注意:MySQL Proxy已停止更新,但0.8.5版本在MySQL 5.7上运行稳定。
22.2.1.1 Windows平台安装
- 下载Windows版本的ZIP包(如mysql-proxy-0.8.5-win32.zip)。
- 解压到目标目录,例如C:\mysql-proxy。
- 进入bin目录,可以看到mysql-proxy.exe。
22.2.1.2 Linux平台安装
以CentOS 7为例,可以使用预编译的二进制包:
wget https://downloads.mysql.com/archives/get/p/21/file/mysql-proxy-0.8.5-linux-glibc2.3-x86-64bit.tar.gz
tar -zxvf mysql-proxy-0.8.5-linux-glibc2.3-x86-64bit.tar.gz -C /usr/local/
cd /usr/local
ln -s mysql-proxy-0.8.5-linux-glibc2.3-x86-64bit mysql-proxy
设置环境变量(可选):
export PATH=$PATH:/usr/local/mysql-proxy/bin
22.2.2 配置MySQL Proxy参数
MySQL Proxy通过命令行参数或配置文件指定运行选项。核心参数包括:
- –proxy-address:Proxy监听的IP和端口,默认:4040。
- –proxy-backend-addresses:指定后端主库地址,可多个(但通常主库只有一个)。
- –proxy-read-only-backend-addresses:指定只读从库地址,可多个。
- –proxy-lua-script:指定Lua脚本路径,用于自定义转发逻辑。
- –daemon:以守护进程运行。
- –log-file:日志文件路径。
- –log-level:日志级别(critical、error、warning、message、debug)。
22.2.2.1 基本配置示例
假设后端架构:
- 主库:192.168.1.10:3306
- 从库1:192.168.1.11:3306
- 从库2:192.168.1.12:3306
启动命令:
mysql-proxy \
--proxy-address=0.0.0.0:4040 \
--proxy-backend-addresses=192.168.1.10:3306 \
--proxy-read-only-backend-addresses=192.168.1.11:3306 \
--proxy-read-only-backend-addresses=192.168.1.12:3306 \
--proxy-lua-script=/usr/local/mysql-proxy/share/doc/mysql-proxy/rw-splitting.lua \
--daemon \
--log-file=/var/log/mysql-proxy.log \
--log-level=message
注意:rw-splitting.lua是MySQL Proxy自带的读写分离脚本,位于share/doc/mysql-proxy/目录下。也可以自定义Lua脚本。
22.2.3 配置Path变量(Windows)
为了方便在任意路径下执行mysql-proxy,可以将bin目录添加到系统Path环境变量中。操作步骤:
- 右键“此电脑” -> 属性 -> 高级系统设置 -> 环境变量。
- 在系统变量中找到Path,编辑,添加C:\mysql-proxy\bin。
- 确定保存。
之后即可在CMD中直接运行mysql-proxy命令。
22.3 使用MySQL Proxy实现读写分离
22.3.1 读写分离Lua脚本原理
自带的rw-splitting.lua脚本实现了一个简单的读写分离逻辑:
- 如果SQL语句以SELECT开头,且不在事务中(没有BEGIN/COMMIT标记),则选择从库;否则选择主库。
- 从库之间使用轮询(round-robin)负载均衡。
- 支持连接池复用。
但其功能相对简单,不支持更复杂的规则,如根据SQL正则、用户、表名等路由。在实际生产环境中,通常需要定制Lua脚本。
22.3.2 部署一主两从环境
在开始之前,确保已经配置好MySQL主从复制(参照第18章)。假设主库为192.168.1.10,从库1为192.168.1.11,从库2为192.168.1.12,复制正常运行。
22.3.3 启动MySQL Proxy
在服务器上执行上述启动命令。检查进程是否启动:
ps aux | grep mysql-proxy
查看日志文件/var/log/mysql-proxy.log,确认无错误。
22.3.4 测试读写分离
22.3.4.1 连接测试
使用MySQL客户端连接Proxy:
mysql -h 192.168.1.100 -P 4040 -u app_user -p
其中192.168.1.100是部署Proxy的服务器IP。输入密码后,应能正常登录。
22.3.4.2 执行写操作
USE ecommerce;
INSERT INTO orders (user_id, amount) VALUES (1001, 299);
执行后,检查主库和从库的数据一致性。由于主从复制,从库也应很快出现该记录。
22.3.4.3 执行读操作
SELECT * FROM orders WHERE user_id = 1001;
多次执行,观察从库的负载情况。可以在从库上执行SHOW PROCESSLIST查看连接来源,确认请求被分发到不同的从库。
22.3.4.4 事务测试
START TRANSACTION;
INSERT INTO orders (user_id, amount) VALUES (1002, 399);
SELECT * FROM orders WHERE user_id = 1002;
COMMIT;
在事务中的SELECT应路由到主库,以保证事务内一致性。rw-splitting.lua通过检测事务标记实现了这一点。
22.3.5 自定义Lua脚本实现更灵活的路由
实际业务中,可能需要更精细的控制,例如:
- 根据表名路由:查询订单表走从库,查询库存表走主库(因为库存实时性要求高)。
- 根据用户标记路由:某些VIP用户的查询直接走主库。
- 读写分离权重分配:不同从库性能不同,可分配不同权重。
下面是一个简单的自定义脚本示例,实现根据表名路由:
-- custom_rw.lua
local backend_ndx = 0
function read_query(packet)
-- 获取SQL语句
local cmd = packet:byte()
if cmd == proxy.COM_QUERY then
local query = packet:sub(2)
-- 转换为小写方便匹配
local lower_query = string.lower(query)
-- 判断是否为写操作
if lower_query:match("^insert") or lower_query:match("^update") or lower_query:match("^delete") then
-- 写操作使用主库
proxy.connection.backend_ndx = 1
else
-- 读操作进一步判断
if lower_query:match("inventory") then
-- 库存表查询走主库(实时性要求高)
proxy.connection.backend_ndx = 1
else
-- 其他读操作从从库轮询
local num_backends = #proxy.global.backends
if num_backends > 0 then
backend_ndx = (backend_ndx % (num_backends - 1)) + 2 -- 从库索引从2开始(1是主库)
proxy.connection.backend_ndx = backend_ndx
end
end
end
end
end
启动时使用自定义脚本:
mysql-proxy ... --proxy-lua-script=/path/to/custom_rw.lua
22.4 商业实战案例:电商系统读写分离架构
22.4.1 业务背景
某电商平台经过快速发展,数据库压力日益增大。核心订单表日增10万行,商品表频繁读取。当前架构为一主一从,主库承担所有读写,从库仅做冷备。随着大促临近,需要将读流量分流到从库,提升系统承载能力。
22.4.2 架构设计
目标架构:
- 一主两从:主库(写)、从库1(读)、从库2(读)。
- 部署MySQL Proxy作为中间层,应用连接Proxy,无感知实现读写分离。
- 对实时性要求极高的库存查询、用户余额查询,仍走主库(通过自定义Lua标记)。
22.4.3 实施步骤
22.4.3.1 环境准备
- 主库:192.168.1.10
- 从库1:192.168.1.11
- 从库2:192.168.1.12
- Proxy服务器:192.168.1.100(也可部署在应用服务器上)
所有数据库已配置好主从复制,且应用账号app_user具有相应权限。
22.4.3.2 安装MySQL Proxy
参照22.2节,在192.168.1.100上安装MySQL Proxy。
22.4.3.3 编写Lua脚本
考虑到业务需求,我们定制脚本ecommerce_rw.lua:
-- ecommerce_rw.lua
local backends = proxy.global.backends
local current_backend = 1 -- 默认主库
function read_query(packet)
local cmd = packet:byte()
if cmd ~= proxy.COM_QUERY then return end
local query = packet:sub(2)
local lower_query = string.lower(query)
-- 写操作一律主库
if lower_query:match("^insert") or lower_query:match("^update") or lower_query:match("^delete") then
proxy.connection.backend_ndx = 1
return
end
-- 读操作,判断是否为特殊表
if lower_query:match("inventory") or lower_query:match("balance") then
proxy.connection.backend_ndx = 1 -- 实时数据走主库
return
end
-- 其他读操作从从库中轮询
local slave_count = #backends - 1 -- 假设主库是第一个
if slave_count > 0 then
-- 简单轮询,可改为更复杂的策略
current_backend = ((current_backend - 1) % slave_count) + 2
proxy.connection.backend_ndx = current_backend
else
proxy.connection.backend_ndx = 1
end
end
注意:脚本中的proxy.global.backends是在启动时通过--proxy-backend-addresses和--proxy-read-only-backend-addresses指定的顺序,主库在前(索引1),从库依次。
22.4.3.4 启动Proxy
mysql-proxy \
--proxy-address=0.0.0.0:3306 \
--proxy-backend-addresses=192.168.1.10:3306 \
--proxy-read-only-backend-addresses=192.168.1.11:3306 \
--proxy-read-only-backend-addresses=192.168.1.12:3306 \
--proxy-lua-script=/etc/mysql-proxy/ecommerce_rw.lua \
--daemon \
--log-file=/var/log/mysql-proxy.log \
--log-level=message
注意:这里将Proxy监听端口设为3306,与应用原本连接数据库的端口一致,这样应用只需修改IP指向Proxy即可,无需改端口。
22.4.3.5 应用切换
修改应用配置中的数据库连接IP为192.168.1.100,端口3306,用户名密码不变。重启应用,观察日志和监控。
22.4.4 效果验证与监控
- 通过
SHOW PROCESSLIST观察主库和从库的连接数,确认读请求分布到从库。 - 使用压力测试工具模拟读写混合场景,对比系统QPS和响应时间。
- 监控Proxy日志,检查是否有错误或异常。
22.4.5 高可用考虑
MySQL Proxy本身是单点,存在故障风险。可以通过Keepalived实现VIP漂移,部署双Proxy(主备)来提升可用性。但Proxy无状态,可以挂载多台Proxy,前端用负载均衡器(如LVS、HAProxy)分发。
22.5 专家解惑
22.5.1 MySQL Proxy的性能如何?
MySQL Proxy使用单线程事件驱动模型,性能主要取决于Lua脚本的复杂度和网络IO。在普通硬件上,可以支撑数千QPS。但如果Lua脚本过于复杂,会成为瓶颈。对于高并发场景,建议使用C++编写的ProxySQL或MySQL Router。
22.5.2 如何处理事务中的读写分离?
在事务中,所有SQL(包括SELECT)都应路由到主库,以保证事务内数据一致性。自带脚本已经处理了这一点,通过检测BEGIN/COMMIT状态。自定义脚本时也需注意。
22.5.3 连接池与连接复用
MySQL Proxy本身不支持连接池,但可以复用客户端连接。每个客户端连接在Proxy上保持,Proxy与后端服务器也建立连接。当短连接频繁时,Proxy会消耗较多文件描述符。建议应用层使用连接池,减少连接创建开销。
22.5.4 主从延迟导致脏读问题
当从库延迟较大时,刚写入的数据可能读不到。解决方案:
- 在应用层标记关键读请求走主库(如用户刚下的订单查询)。
- 设置
--proxy-skip-profiling等参数无帮助,需在Lua脚本中实现更精细的控制,如检测写操作后的SELECT强制走主库。
22.5.5 MySQL Proxy的替代方案
由于MySQL Proxy已停止维护,新项目推荐使用:
- MySQL Router:官方推出的轻量级中间件,支持读写分离、故障转移。
- ProxySQL:功能强大的高性能代理,支持复杂路由、查询缓存、健康检查等。
- ShardingSphere:功能全面的数据库中间件,适用于分库分表场景。
22.5.6 如何调试Lua脚本?
可以在脚本中添加print语句,输出到Proxy日志。例如:
print("Query: " .. query)
需设置日志级别为message或debug。另外,可以使用proxy.debug()函数。
22.5.7 如何监控MySQL Proxy的健康状态?
MySQL Proxy提供管理接口(默认端口4041),可以通过mysql客户端连接该端口执行SELECT * FROM proxy.*查询内部状态。例如:
mysql -h 127.0.0.1 -P 4041
进入后可以查看后端的连接数、状态等。
22.5.8 如何实现从库的负载均衡权重?
修改Lua脚本,为每个从库分配权重。例如,用数组存储权重,轮询时按权重比例选择。
22.5.9 遇到MySQL server has gone away错误?
可能原因:
- Proxy与后端服务器连接超时,调整后端MySQL的
wait_timeout和interactive_timeout。 - Proxy本身空闲连接超时,可设置
--proxy-connect-timeout等参数。 - 网络问题,检查防火墙。
22.5.10 是否支持SSL连接?
MySQL Proxy 0.8.5支持SSL,需要在编译时启用。需配置--proxy-ssl-key、--proxy-ssl-cert等参数。
22.6 经典习题
- 简述MySQL Proxy在读写分离架构中的作用。
- 如何在一主两从环境下使用MySQL Proxy实现读写分离?写出启动命令。
- 如果业务要求某些读操作(如库存查询)必须走主库,如何修改Lua脚本实现?
- 什么是MySQL Proxy的Lua脚本?写出一个简单的Lua函数,记录所有SELECT查询。
- 如何监控MySQL Proxy的运行状态?
- MySQL Proxy存在哪些局限性?有哪些替代方案?
- 假设应用需要连接池,但MySQL Proxy不支持,你有什么办法?
- 在事务中执行
SELECT语句,应该路由到主库还是从库?为什么? - 如何解决因主从延迟导致的脏读问题?
- 使用MySQL Proxy后,如果某个从库宕机,Proxy会自动处理吗?如何处理?
本章全面介绍了MySQL Proxy的安装、配置、读写分离实现以及商业案例。虽然MySQL Proxy已不再是主流选择,但通过学习它,你可以深入理解数据库中间件的基本原理,为使用更先进的代理产品打下基础。在实际工作中,根据业务需求和团队技术栈,合理选择代理工具,才能构建出高效稳定的数据库架构。
更多推荐




所有评论(0)