news 2026/10/23 21:50:47

ProxySQL mysqlx 插件运维级测试工具链:行为验证与压力测试实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ProxySQL mysqlx 插件运维级测试工具链:行为验证与压力测试实战指南
  • 后端
  • 数据库
  • 负载均衡

【免费下载链接】proxysql

High-performance proxy for MySQL and PostgreSQL

项目地址:https://gitcode.com/gh_mirrors/pr/proxysql
点击查看免费下载

导读

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,运行这些脚本需要满足三项前置:

  1. ProxySQL 已构建并加载 mysqlx 插件:构建时需要启用PROXYSQLGENAI=1,插件动态库位于plugins/mysqlx/ProxySQL_MySQLX_Plugin.so(插件的完整构建入口见 plugins/mysqlx/Makefile)。
  2. MySQL 8.x 后端启用 X Protocol(默认端口 33060),并有一个测试用户通过 ProxySQL 的mysqlx_users/mysqlx_routes映射到后端。
  3. 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-host127.0.0.1ProxySQL X-Protocol 监听地址
--proxysql-port6603ProxySQL 上的 X-Protocol 监听端口
--admin-host127.0.0.1ProxySQL admin 地址
--admin-port6032ProxySQL admin 端口(走经典 MySQL 协议)
--admin-useradminadmin 用户名
--admin-passadminadmin 密码
--user必填X-Protocol 测试用户
--password必填测试用户密码
--clients5并发客户端数
--external-shutdown关闭不由本进程发起PROXYSQL SHUTDOWN SLOW,而等待外部角色发起(适用于 docker 隔离的 TAP 封装运行)
--shutdown-wait-sec5.0等待外部触发的停机通知时间
--scenarioallshutdown/reload/all三选一
--route-namer1reload 场景中要删除的路由名

客户端会话的建立方式(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,然后断言两件事:

  1. 存量会话继续工作:已建立的 in-flight 会话继续向该路由原先对应的后端执行查询(脚本以"无错误且查询数 > 100"判定存活);
  2. 新连接被拒绝:向该路由发起的新连接得到 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-port127.0.0.1/6603X-Protocol 入口
--admin-host/--admin-port127.0.0.1/6032admin 入口(采集stats_mysqlx_routes)
--admin-user/--admin-passadmin/adminadmin 凭证
--user/--password必填X-Protocol 测试用户
--concurrent100并发客户端数(目标规模 N=1000)
--duration60m运行时长,支持60s/30m/24h等后缀(见 parse_duration)
--churn-interval30s连接更替周期
--churn-fraction0.1每个 churn tick 要更替的客户端比例
--metrics-out/tmp/mysqlx_stress_metrics.csv指标输出 CSV 路径
--metrics-interval-sec10指标采样间隔(秒)

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.csv

5.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

项目地址:https://gitcode.com/gh_mirrors/pr/proxysql
点击查看免费下载
上一篇:为什么你需要zvec-grep:统一ripgrep、BM25与向量搜索的Local-First混合检索神器
下一篇:Sinon Promises:创建可控制状态与手动兑现的 JavaScript 假 Promise

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 1:45:00

使用 PHP 绑定调用 Xberg::listRenderers() 管理文档渲染器注册表

后端AI 应用NLP 【免费下载链接】xberg Polyglot document intelligence with a Rust core: extract text, metadata, images, tables, and structured data from 106 formats across 140 file extensions, plus code intelligence for 371 languages. Fifteen bindings, with …

作者头像 李华
网站建设 2026/10/16 0:29:45

【ArkUI 练中学】第7课:动画体系

本节目标理解 ArkUI 动画体系的基本分类&#xff1a;属性动画、显式动画、转场动画、路径动画掌握 animateTo 显式动画接口的用法&#xff0c;理解其闭包驱动机制与适用场景掌握 .animation() 属性动画接口的用法&#xff0c;理解其声明式自动触发机制区分 animateTo 与…

作者头像 李华
网站建设 2026/10/15 2:34:30

【ArkUI 练中学】第6课:状态管理进阶

本节目标理解 State 在嵌套对象场景下的局限性&#xff0c;掌握 Observed 与 ObjectLink 配合实现深度监听的方法掌握 Watch 装饰器监听状态变量变化的能力&#xff0c;理解其严格相等触发机制及使用场景掌握 AppStorage 应用级全局状态存储的读写方法&#xff0c;理解 S…

作者头像 李华