- 后端
- 数据库
- 负载均衡
【免费下载链接】proxysql
High-performance proxy for MySQL and PostgreSQL
导读
ProxySQL 的 mysqlx 插件为 MySQL X Protocol 提供代理能力,但常规的 TAP 单元测试无法覆盖真实运行环境下的停机、路由热更新与高并发连接压力。本文以仓库中的 test/scripts/mysqlx/README.md 为骨架,结合 behavioral_validation.py、stress.py 两个运维级测试脚本及插件源码,完整讲解如何在真实基础设施上验证 mysqlx 插件的关键运行时行为——优雅停机通知、运行中路由重载、长时间高并发稳定性,并给出可直接复制的环境搭建与执行配方。读者读完后可以搭建一套针对 mysqlx 插件的 smoke + soak 验证流程,并理解其底层实现依据。
一、工具定位:为什么需要"运维级测试"而不是 TAP 单测
仓库 test/tap 目录下的测试是标准的 TAP 单元测试,可在隔离环境中运行。而 mysqlx 插件的两个运维测试脚本 behavioral_validation.py 与 stress.py 则完全不同:
- 它们不是 TAP 单元测试,而是针对运行中的 ProxySQL 实例做端到端验证;
- 它们依赖真实基础设施:一台启用 X Protocol 的 MySQL 8.x 后端(默认端口 33060)、一个加载了 mysqlx 插件的 ProxySQL 实例,以及通过
mysqlx_users/mysqlx_routes映射好的测试用户; - 它们存在的目的是服务于issue #5677、#5678、#5681所跟踪的"合并后置信度"工作——即代码合并之后,通过真实流量验证其在生产形态下是否按设计工作。
换句话说,这两个脚本弥补了单元测试到真实运行之间的验证断层:单元测试保证"逻辑正确",运维测试保证"在真实网络、真实 MySQL、真实插件加载形态下依然正确"。
二、环境前置条件
根据 README,运行这些脚本需要满足三项前置:
- ProxySQL 已构建并加载 mysqlx 插件:构建时需要启用
PROXYSQLGENAI=1,插件动态库位于plugins/mysqlx/ProxySQL_MySQLX_Plugin.so(插件的完整构建入口见 plugins/mysqlx/Makefile)。 - MySQL 8.x 后端启用 X Protocol(默认端口 33060),并有一个测试用户通过 ProxySQL 的
mysqlx_users/mysqlx_routes映射到后端。 - Python 依赖:
mysql-connector-python(提供 X DevAPI 绑定):
pip install mysql-connector-python两个脚本在ImportError时都会直接打印提示并退出(见 behavioral_validation.py 与 stress.py),因此依赖缺失时失败信息是明确的。
三、behavioral_validation.py:两个关键运行时行为验证
该脚本对应issue #5678,通过真实 X-Protocol 流量验证两个对运维至关重要的行为。脚本默认同时运行两个场景,也可用--scenario单独执行。
3.1 完整命令行参数
源码 behavioral_validation.py 中定义的参数如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
--proxysql-host | 127.0.0.1 | ProxySQL X-Protocol 监听地址 |
--proxysql-port | 6603 | ProxySQL 上的 X-Protocol 监听端口 |
--admin-host | 127.0.0.1 | ProxySQL admin 地址 |
--admin-port | 6032 | ProxySQL admin 端口(走经典 MySQL 协议) |
--admin-user | admin | admin 用户名 |
--admin-pass | admin | admin 密码 |
--user | 必填 | X-Protocol 测试用户 |
--password | 必填 | 测试用户密码 |
--clients | 5 | 并发客户端数 |
--external-shutdown | 关闭 | 不由本进程发起PROXYSQL SHUTDOWN SLOW,而等待外部角色发起(适用于 docker 隔离的 TAP 封装运行) |
--shutdown-wait-sec | 5.0 | 等待外部触发的停机通知时间 |
--scenario | all | shutdown/reload/all三选一 |
--route-name | r1 | reload 场景中要删除的路由名 |
客户端会话的建立方式(open_session)显式禁用 TLS 与压缩(ssl-mode=DISABLED、compression=disabled),以保证验证聚焦于 X Protocol 数据面本身。
3.2 场景一:PROXYSQL SHUTDOWN SLOW运行中停机
该场景打开 N 个并发 X-Protocol 客户端持续执行SELECT 1,然后通过 admin 端口(经典协议)发出PROXYSQL SHUTDOWN SLOW,最后逐一核对每个客户端收到的是干净的Mysqlx::Error帧(错误码 1053,"Server is shutting down"),而不是未经宣布的 TCP RST。
脚本的判定逻辑利用了mysql-connector-python的异常语义:连接断开时它抛出mysqlx.errors.OperationalError,但TCP RST 与收到Mysqlx::Error帧都表现为同类异常,只能靠错误码区分——errno为1053说明是干净的停机通知,否则(包括没有errno属性)视为 TCP RST(见 steady_traffic_thread)。最终统计中,只有当clean_close == args.clients(每个客户端都收到 1053)时脚本才返回 PASS。
该行为对应的底层实现是MysqlxSession::shutdown_notify_client()(README 标注的提交55e90d1a7),源码位于 plugins/mysqlx/src/mysqlx_session.cpp#L2399-L2425。其实现要点:
- 尽力而为:客户端写侧已关闭(fd < 0)或会话已处于
X_SESSION_CLOSED/X_SESSION_CLOSING时直接返回,不让停机悬挂在半关闭的对端上; - 发送致命错误帧:调用
send_error(1053, "Server is shutting down", /*fatal=*/true)构造Mysqlx::Error消息(FATAL严重级别、HY000SQL 状态),并封装为ServerMessages_Type_ERROR帧入队(见 send_error); - 冲刷到线缆:单次
write_to_net()尽力将队列中的帧写出; - TLS 干净关闭:若会话启用 TLS,通过
SSL_set_quiet_shutdown(1)+SSL_shutdown()发出close_notify,使对端 TLS 栈看到干净关闭而非被撕裂的记录; - 最后将状态置为
X_SESSION_CLOSING并标记会话不健康。
这一链路正是脚本断言"客户端看到 1053 而非 TCP RST"的实现基础。
3.3 场景二:LOAD MYSQLX ROUTES TO RUNTIME运行中路由重载
该场景先让 N 个客户端在路由r1上跑流量,随后通过 admin 端口执行DELETE FROM mysqlx_routes WHERE name='r1'并LOAD MYSQLX ROUTES TO RUNTIME,然后断言两件事:
- 存量会话继续工作:已建立的 in-flight 会话继续向该路由原先对应的后端执行查询(脚本以"无错误且查询数 > 100"判定存活);
- 新连接被拒绝:向该路由发起的新连接得到 connection-refused。
该行为对应 README 指出的remove_listener_for_route语义,实现分布在两个文件中:
- 路由协调层:plugins/mysqlx/src/mysqlx_listener_reconcile.cpp#L107-L132 中,协调逻辑对"不再期望存在"或"bind 地址已变化"的路由调用
threads[tidx]->remove_listener_for_route(),并关闭其监听套接字(bind 地址变化按"先删后加"处理); - 监听器管理:plugins/mysqlx/src/mysqlx_thread.cpp#L406-L420 中,
remove_listener_for_route()在listener_mutex_保护下close()对应 fd,并同步从listener_fds_/listener_addrs_/listener_ports_/listener_route_names_四组并行数组中移除该路由条目。
值得注意的实现细节(源码注释 plugins/mysqlx/src/mysqlx_thread.cpp#L400-L405 明确说明):路由名只在认证阶段的后端目标解析时被查阅,因此一个已认证的会话不依赖路由名仍然存活——这从原理上解释了为什么"移除路由 + 重载"不会打断 in-flight 会话,只会拒绝新连接。这正是脚本场景二断言成立的底层依据。注释同时指出:若未来需要路由移除时主动断开存量会话,可以在sessions_中匹配default_route并对每个会话调用shutdown_notify_client()——即场景一验证的那条路径。
3.4 管理面命令的预期断开处理
脚本通过 admin 端口(经典协议,mysql.connector)执行管理命令。源码对管理面断开做了容错:执行PROXYSQL SHUTDOWN SLOW或路由重载后,若 admin 连接报errno 2006/2013或错误信息含Lost connection/gone away,会视为预期断开而非失败(见 issue_admin_shutdown)。这是"停机过程本身会关闭 admin 连接"这一真实行为的正确处理。
四、stress.py:长时间高并发压力与资源稳定性验证
该脚本对应issue #5681,目标是验证 mysqlx 插件在长时间、高并发、连接持续更替(churn)下的稳定性。
4.1 完整命令行参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--proxysql-host/--proxysql-port | 127.0.0.1/6603 | X-Protocol 入口 |
--admin-host/--admin-port | 127.0.0.1/6032 | admin 入口(采集stats_mysqlx_routes) |
--admin-user/--admin-pass | admin/admin | admin 凭证 |
--user/--password | 必填 | X-Protocol 测试用户 |
--concurrent | 100 | 并发客户端数(目标规模 N=1000) |
--duration | 60m | 运行时长,支持60s/30m/24h等后缀(见 parse_duration) |
--churn-interval | 30s | 连接更替周期 |
--churn-fraction | 0.1 | 每个 churn tick 要更替的客户端比例 |
--metrics-out | /tmp/mysqlx_stress_metrics.csv | 指标输出 CSV 路径 |
--metrics-interval-sec | 10 | 指标采样间隔(秒) |
4.2 工作负载模型
每个ClientWorker(stress.py)是一个守护线程,其运行模式为:反复mysqlx.get_session()建立会话 → 在会话内循环执行SELECT 1→ 出现异常时错误计数 +1 并sleep(0.5)退避,避免后端故障时占满 CPU。会话在每次循环迭代中重新建立,这本身就提供了自然的连接级 churn。
源码注释还如实说明了一个实现现状:churn tick(按errors降序挑选最挣扎的 worker)目前只记录"更替意图"到日志,并未强制重启线程,连接级 churn 由主循环的get_session()天然提供(stress.py)——读者理解指标时应以此为准。
4.3 指标采集维度
脚本每个采样周期采集五类数据并写入 CSV(表头见 stress.py):
- 吞吐:
total_queries与增量计算的queries_per_sec; - 错误率:
total_errors,结束时以errors / max(1, queries)计算; - 进程资源:通过
pidof proxysql定位进程(find_proxysql_pid),读取/proc/<pid>/status的VmRSS(KiB)与Threads,并统计/proc/<pid>/fd目录条数(proc_status); - 路由统计:周期查询 admin 端
SELECT * FROM stats_mysqlx_routes(fetch_route_stats),结束时打印最终快照。
CSV 列为t_sec, rss_kib, threads, fds, total_queries, total_errors, queries_per_sec,可直接绘图观察 RSS / fd / 线程数是否存在单调增长。
4.4 通过标准与判定
脚本内置的通过标准(对应 issue #5681):60 分钟运行无崩溃、无内存/fd 单调增长、错误率 < 0.1%、吞吐稳定。程序化判定为:最终error_rate < 0.001时输出PASS: error rate under 0.1%并返回 0,否则输出 FAIL(stress.py);其余维度(崩溃、资源单调增长)需要结合 CSV 曲线与进程存活情况人工确认。脚本还会收集最多 5 个不同的 worker 错误样本打印出来,便于定位错误类型。
五、端到端实操配方:Smoke + Soak
5.1 第一步:拉起 MySQL 8.x 后端
README 提供两种方式,任选其一:
方式 A——Docker:
docker run -d --name=test_mysql -p 3306:3306 -p 33060:33060 \ -e MYSQL_ROOT_PASSWORD=root mysql:8.4方式 B——dbdeployer:参见仓库内已提供的自动化脚本 test/tap/groups/mysqlx-e2e/setup-infras.bash,它完成:安装 dbdeployer、下载并解包 MySQL 8.4、以--port=13306部署单实例 sandbox、等待就绪,并创建mysqlx_test用户(mysql_native_password、全部权限)。
5.2 第二步:在 admin shell 中配置用户、后端与路由
通过 classic 协议连接 ProxySQL admin(默认127.0.0.1:6032,用户admin/ 密码admin),执行以下配置:
mysql -h 127.0.0.1 -P 6032 -u admin -padmin <<'SQL' INSERT INTO mysqlx_users(username, default_route) VALUES('alice','r1'); INSERT INTO mysqlx_backend_endpoints(hostname, mysql_port, mysqlx_port) VALUES('127.0.0.1', 3306, 33060); INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES(10, '127.0.0.1', 3306); INSERT INTO mysqlx_routes(name, bind, destination_hostgroup) VALUES('r1', '0.0.0.0:6603', 10); LOAD MYSQLX USERS TO RUNTIME; SAVE MYSQLX USERS TO DISK; LOAD MYSQLX ROUTES TO RUNTIME; SAVE MYSQLX ROUTES TO DISK; LOAD MYSQLX BACKEND ENDPOINTS TO RUNTIME; SAVE MYSQLX BACKEND ENDPOINTS TO DISK; SQL要点:mysqlx_users.default_route指定用户默认路由;mysqlx_routes的bind决定 X-Protocol 监听地址(这里为0.0.0.0:6603),destination_hostgroup指向mysql_servers中的后端主机组;每次修改后都要LOAD ... TO RUNTIME生效,SAVE ... TO DISK持久化。
5.3 第三步:先跑 Smoke(行为验证)
python3 test/scripts/mysqlx/behavioral_validation.py \ --proxysql-host 127.0.0.1 --proxysql-port 6603 \ --user alice --password whatever --duration 30s注:README 示例中的
--duration 30s属于验收流程描述;实际脚本中场景时长为固定节奏(场景一启动流量后约 5 秒完成停机验证,场景二约 7 秒完成重载验证),--duration参数在脚本的 argparse 定义中并未出现,运行时请以--clients、--scenario等实际参数为准。建议先用--scenario reload --clients 5单独验证路由重载,再跑--scenario all全量。
5.4 第四步:再跑 Soak(24–72 小时压测)
python3 test/scripts/mysqlx/stress.py \ --proxysql-host 127.0.0.1 --proxysql-port 6603 \ --user alice --password whatever \ --concurrent 100 --duration 24h \ --metrics-out /tmp/mysqlx_soak_metrics.csv5.5 第五步:周期性采集辅助指标
Soak 期间,README 建议配合以下外部监控命令,与脚本自身指标交叉验证:
# 进程资源:RSS 与线程数 cat /proc/$(pidof proxysql)/status # 文件描述符数量 ls /proc/$(pidof proxysql)/fd | wc -l # 路由级统计 mysql -h 127.0.0.1 -P 6032 -u admin -padmin -e "SELECT * FROM stats_mysqlx_routes"--metrics-out生成的 CSV(含t_sec, rss_kib, threads, fds, ...各列)可直接绘图,用于验证无单调增长——这是判断内存/句柄泄漏的关键证据。
六、当前状态与验证边界
README 的 Status 部分给出了重要的事实边界,引用时必须如实传达:这两个测试工具目前是"脚手架"(scaffolds)——可运行,但尚未在真实基础设施上跑过。它们晚于 PR #5651 的合并窗口,置信度提升取决于有人在 staging 环境运行并将结果反馈到关联 issue(#5677、#5678、#5681)。
因此,本文所介绍的全部通过标准(1053 干净通知、路由移除后存量会话存活、错误率 < 0.1% 等)是脚本与插件源码中定义的预期行为,读者应将其视为验证目标,而非已实测达成的结论。运行环境方面,脚本假定 ProxySQL 构建时启用了PROXYSQLGENAI=1且 mysqlx 插件已加载,这些前提不满足时脚本无法给出有意义的结论。
七、延伸阅读
- mysqlx 插件整体架构:doc/mysqlx/ARCHITECTURE.md
- 插件源码目录:plugins/mysqlx/src、头文件 plugins/mysqlx/include
- 停机通知实现:plugins/mysqlx/src/mysqlx_session.cpp#L2399-L2425
- 路由监听协调:plugins/mysqlx/src/mysqlx_listener_reconcile.cpp#L107-L132
- 监听器移除实现:plugins/mysqlx/src/mysqlx_thread.cpp#L406-L430
- dbdeployer 后端自动部署脚本:test/tap/groups/mysqlx-e2e/setup-infras.bash
- 后端
- 数据库
- 负载均衡
【免费下载链接】proxysql
High-performance proxy for MySQL and PostgreSQL
相关推荐
ProxySQL MySQLX 插件综合测试计划:三层测试架构与 400+ 断言实战指南
ProxySQL MySQLX 插件综合测试计划:三层测试架构与 400+ 断言实战指南 本文以仓库内 docs/superpowers/plans/2026
后端数据库负载均衡vue-fastapi-admin性能优化秘籍:前后端分离架构的性能提升策略
vue fastapi admin性能优化秘籍:前后端分离架构的性能提升策略 vue fastapi admin是基于FastAPI+Vue3+Naive UI
后端前端认证鉴权Navigation2 行为插件系统级测试(Behavior Tests)指南:架构、测试用例与实战运行
Navigation2 行为插件系统级测试(Behavior Tests)指南:架构、测试用例与实战运行 本篇技术指南聚焦 ROS 2 Navigation2
机器人ROS自动驾驶
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考