news 2026/10/23 23:29:56

ProxySQL 覆盖率强化实战:GTID 因果路由、Aurora 延迟感知路由与内置 Admin 命令的 GCOV 覆盖设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ProxySQL 覆盖率强化实战:GTID 因果路由、Aurora 延迟感知路由与内置 Admin 命令的 GCOV 覆盖设计
  • 后端
  • 数据库
  • 负载均衡

【免费下载链接】proxysql

High-performance proxy for MySQL and PostgreSQL

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

本文围绕 GTID、Aurora 与 Admin 覆盖设计文档 展开,系统讲解 ProxySQL 如何通过 TAP 测试提升MyHGC.cpp与ProxySQL_Admin_Tests2.cpp的有效 GCOV 覆盖率。你将理解三大覆盖策略的取舍——复用既有 GTID 因果负载、用TEST_AURORA模拟器驱动 Aurora 延迟路由、用PROXYSQLTEST内建命令验证 Admin 行为——并掌握对应的测试注册、聚焦运行与 LCOV 验证方法。

一、目标:让覆盖率命中真实可观测路径

设计文档的核心目标非常明确:通过对可观测生产路径执行 TAP 测试,提升MyHGC.cpp与ProxySQL_Admin_Tests2.cpp中有意义的 GCOV 覆盖率。

这句话包含两层含义:

  1. "有意义"(meaningful):覆盖率不追求数字堆砌,而是要求 GCOV 记录到的分支确实由真实业务路径触发。若一个 TAP 虽然"跑了",但从未被纳入上传 GCOV 数据的 CI 组,那么它的执行对覆盖率报告毫无贡献——这正是 GTID 测试当时面临的状况。
  2. "可观测生产路径"(observable production paths):覆盖应当发生在真实的前端连接、真实的查询路由、真实的 Admin SQL 接口之上,而不是通过单元测试直接调用内部函数绕过接口。

实现计划(2026-08-15-gtid-aurora-admin-coverage.md)进一步明确了三条全局约束,它们是整个设计的事实基线:

  • 对 ProxySQL 的所有请求一律使用普通 libmysql 客户端流量,不手工构造客户端协议包;
  • TEST_AURORA仅用于提供 Aurora 监控状态,端点选择与状态影响必须由前端查询断言;
  • Admin 状态的恢复一律通过LOAD ... FROM DISK与LOAD ... TO RUNTIME完成。

二、三大覆盖领域与决策概览

设计文档将工作拆分为三个互不重叠的领域,每个领域对应一个已经落地于 test/tap/groups/groups.json 的 TAP 测试:

覆盖领域测试文件注册的 GCOV 组覆盖目标
GTID 因果路由test_gtid_from_ok-t.cpp(14 项断言)mysql84-g5(追加)MyHGC.cpp中gtid_trxid候选分支
Aurora 查询路由test_aurora_query_routing-t.cpp(9 项断言)cluster_sim_aurora-g1MyHGC.cpp中 lag 过滤与 writer 移除路径
内置 Admin 测试test_admin_builtin_coverage-t.cpp(23 项断言)mysql84-g5ProxySQL_Admin_Tests2.cpp中PROXYSQLTEST2/3/6/12/13/16

三者的共同点是:不修改任何生产代码(计划中明确 "No production code changes"),仅通过测试注册、模拟器状态注入与 TAP 行为断言来扩大 GCOV 的执行覆盖。实现计划中的执行证据(2026-08-15)显示:GTID 测试在 MySQL 8.4 聚焦运行通过并记录到目标分支,Aurora 测试 9 项断言全过,Admin 测试 23 项断言全过且 GCOV 记录了目标case。

三、GTID 因果路由覆盖:复用而非重写

3.1 问题所在:测试存在,但覆盖率缺失

GTID 因果路由的测试其实早就存在。test_gtid_from_ok-t.cpp 已经完成了完整的"真实写入 →min_gtid因果读取 → 验证只读主机组查询计数"工作负载。设计文档明确指出,这个测试当前只注册在不上传 GCOV 数据的 binlog 组中(即legacy-binlog-g1、mysql84-binlog-g1、mysql90-binlog-g1、mysql95-binlog-g1),因此它的执行无法反映到覆盖率报告里。

关键决策:先在 MySQL 8.4 GCOV 环境中"原样"运行这个未修改的 TAP。如果它通过、且覆盖率报告包含 GTID 候选分支,就把mysql84-g5追加进它的组注册。既不复制工作量,也不用单元测试替换它。

落地后的注册(groups.json 第 583 行)为:

"test_gtid_from_ok-t" : [ "legacy-binlog-g1", "mysql84-binlog-g1", "mysql84-g5", "mysql90-binlog-g1", "mysql95-binlog-g1" ]

3.2 测试如何构造"真实因果负载"

从源码看,测试搭建了一套专用的、高优先级的路由环境,避免与组基线的通用testuser规则冲突:

  • 专用前端账号gtid_from_ok_tap(注释明确说明该账号故意不属于任何组基线,测试会安装高优先级专用规则,否则会被覆盖率组里的通用规则抢占);
  • 专用主机组:写主机组RW_HG=15983、只读主机组RO_HG=15984;
  • 专用规则区间159830~159832:INSERT 与session_track_gtids查询走向写组,SELECT id FROM test.gtid_from_ok走向只读组;
  • 后端行为控制:将mysql-update_gtid_from_ok先设为false,验证 min_gtid 读取被拒绝("disabled OK-packet ingestion rejects min_gtid read"),再设为true并LOAD MYSQL VARIABLES TO RUNTIME,验证因果读取生效。

测试的核心执行链(run_test依次串联):

  1. resolve_writer_endpoint:从runtime_mysql_servers解析 ONLINE 写端点并转成数字 IP(BINLOG_WHG环境变量可指定优先的写主机组);
  2. install_dedicated_routing:插入前端用户、双主机组服务器(只读组gtid_port=1)与三条专用规则;
  3. configure_gtid_variables:保存并覆盖mysql-connect_timeout_server_max、mysql-client_session_track_gtid、mysql-default_session_track_gtids、mysql-server_capabilities(追加CLIENT_SESSION_TRACKING)与mysql-update_gtid_from_ok;
  4. verify_disabled_ingestion:SET SESSION session_track_gtids=OWN_GTID后 INSERT 第 1 行,从 OK 包取回 GTID,确认关闭状态下/*+ ;min_gtid=... */读取被拒绝;
  5. verify_untracked_insert:确认未请求跟踪的会话 INSERT 不回传 GTID、端点 GTID 记录保持为空;
  6. verify_tracked_causal_read:INSERT 第 3 行拿到 GTID 后,轮询stats_mysql_gtid_executed直至端点学习到该 GTID,再发起/*+ ;min_gtid=gtid3 */因果读取,断言读回id=3,并验证只读组15984的查询计数严格递增。

3.3 源码印证:候选分支在MyHGC.cpp

测试想要打中的 GCOV 分支位于 lib/MyHGC.cpp 的get_random_MySrvC(char *gtid_uuid, uint64_t gtid_trxid, int max_lag_ms, MySQL_Session *sess)(定义于第 107 行)。在该函数的候选扫描循环中:

  • 当gtid_trxid非零时(即 GTID 因果读取场景),每个 ONLINE 服务器必须通过MyHGM->gtid_exists(mysrvc, gtid_uuid, gtid_trxid)校验才进入候选数组(第 167–173 行);
  • 同时max_lag_ms >= 0时还要求(unsigned int)max_lag_ms >= mysrvc->aws_aurora_current_lag_us/1000(第 175–176 行)。

由于测试走真实前端写入并经 OK 包学习 GTID,再以min_gtid注释触发因果读,get_random_MySrvC的gtid_trxid分支会在运行时真正被执行,从而被编译器匹配的 GCOV 记录为已覆盖。

四、Aurora 查询路由覆盖:模拟器只做控制面

4.1 为什么需要TEST_AURORA模拟器

普通 MySQL 无法提供 Aurora 专属的REPLICA_HOST_STATUS监控源。设计文档明确说明:只有带REPLICA_HOST_STATUS的源才能让 ProxySQL 分配被测代码依赖的 Aurora lag 字段(aws_aurora_current_lag_us)。因此备选方案"用普通 MySQL 充当 Aurora 源"被直接否决。

而TEST_AURORA模拟器在此处的职责被严格限定:只用于控制监控状态(monitor state),不构造客户端流量、也不充当裸线客户端助手。真正的断言流量是普通 libmysql 查询,经由 ProxySQL 前端转发。

4.2 模拟器 fixture:三节点 Aurora 拓扑

状态注入由模拟器 payload 描述:query_routing.json 定义了三节点(实际为四台服务器)拓扑:

主机角色初始 lag(REPLICA_LAG_IN_MILLISECONDS)主机组
host.1.11.aws-test.com写节点(MASTER_SESSION_ID)01271
host.1.12.aws-test.com零延迟只读副本01272
host.1.13.aws-test.com低延迟合格副本41272
host.1.14.aws-test.com高延迟副本(高于查询注解的max_lag_ms)501272

配套的 Aurora 主机组配置(mysql_aws_aurora_hostgroups)为:写组 1271、读组 1272、domain_name=".aws-test.com"、监控阈值max_lag_ms=100(高延迟服务器保持在监控阈值之下 ONLINE,从而测试"服务器在线但被查询注解过滤"的场景)、writer_is_also_reader=1。

4.3 TAP 的执行链与断言

test_aurora_query_routing-t.cpp 的流程与设计文档的四步完全对应:

  1. 注入状态:以cluster_simulator --mode verify -f <payload>运行模拟器(依赖环境变量CLUSTER_SIM_BINARY_PATH与CLUSTER_SIM_TESTS_ROOT),并校验其 JSON 输出err_type == "none";
  2. 等待收敛:通过AuroraVariableRestore::set_one_replica_minimum()将mysql-aurora_max_lag_ms_only_read_from_replicas置为 1 并LOAD MYSQL VARIABLES TO RUNTIME(该 RAII 守卫同时保存旧值、在析构时恢复,防止部分 SET/LOAD 泄漏状态);
  3. 发出真实前端查询:以aurora1/pass1账号连接 ProxySQL 普通前端,执行带注解的查询:
SELECT @@version_comment LIMIT 1 /* ;max_lag_ms=10;create_new_connection=1 */
  1. 断言后端身份与计数(共 9 项断言,plan(9)):
    • 查询成功;
    • host.1.13(lag 4ms,处于注解阈值 10ms 内)的连接池计数ConnUsed+ConnFree增加;
    • host.1.12(零延迟候选)与写节点host.1.11的连接池计数保持不变;
    • stats_mysql_global中的get_aws_aurora_replicas_skipped_during_query计数器严格增加(证明host.1.14因超过max_lag_ms=10被跳过)。

测试开头注释精确概括了分工:"cluster simulator 被刻意限制在监控控制面:它发布一个写节点、一个低延迟副本、一个高延迟副本;断言流量本身是经过 ProxySQL 前端的普通 libmysql 流量。"

4.4 源码印证:lag 过滤、跳过计数与 writer 移除

MyHGC.cpp中与此测试对应的三条执行路径:

(1)lag 过滤与跳过计数(第 175–183 行):候选扫描中,max_lag_ms >= 0时比较注解阈值与服务器aws_aurora_current_lag_us/1000;不满足的服务器走else分支,递增st_var_aws_aurora_replicas_skipped_during_query——这正是 TAP 断言计数器增加所验证的代码:

if ((unsigned int)max_lag_ms >= mysrvc->aws_aurora_current_lag_us/1000) { ... mysrvcCandidates[num_candidates]=mysrvc; num_candidates++; } else { sess->thread->status_variables.stvar[st_var_aws_aurora_replicas_skipped_during_query]++; }

(2)writer 排除(第 271–296 行):max_lag_ms > 0时读取aurora_max_lag_ms_only_read_from_replicas(源码取名为min_num_replicas);当候选数 ≥ 该值、且候选的aws_aurora_current_lag_us总和非零(避免误判全零场景)时,将所有aws_aurora_current_lag_us==0的候选(即写节点)从候选数组中移除。TAP 断言"写节点连接数不增加",正是这条 writer-removal 路径被执行并生效的证明。

(3)TEST_AURORA统计插桩(第 115–122 行):#ifdef TEST_AURORA下维护array_mysrvc_total/array_mysrvc_cands用于模拟器环境下的候选统计,说明该模拟器代码路径本身即为覆盖率设计的一部分。

注册(groups.json 第 505 行):

"test_aurora_query_routing-t" : [ "cluster_sim_aurora-g1" ]

cluster_sim_aurora-g1是会上传 GCOV 数据的工作流组,保证该测试的执行能进入覆盖率报告。

五、内置 Admin 测试覆盖:行为断言而非盲目派发

5.1 原则:扩展已有调用者的行为覆盖

PROXYSQLTEST是一条由 Admin 接口处理的测试命令,其派发逻辑位于 lib/ProxySQL_Admin_Tests2.cpp(第 1156–1158 行以sscanf解析PROXYSQLTEST <n> <arg1> <arg2> ...)。设计文档的原则是:"扩展它们的行为覆盖,而不是盲目地派发每一个编号"——即不追求把所有 case 都跑一遍,而是挑出已有 TAP 调用者从未覆盖、且具备可观测行为后置条件的命令,逐一断言其效果。

5.2 Digest 路径:命令 2 / 3 / 6

对应源码中的三个 case:

  • case 1(第 1161–1169 行):ProxySQL_Test___GenerateRandomQueryInDigestTable(test_arg1),生成test_arg1*1000条随机 digest 条目;
  • case 2(第 1170–1176 行):ProxySQL_Test___GetDigestTable(false, false)——只快照digest map,不写 DB;
  • case 3(第 1177–1183 行):ProxySQL_Test___GetDigestTable(true, false)——快照并 reset,不写 DB;
  • case 6(第 1196–1201 行):ProxySQL_Test___PurgeDigestTable<...>(true, false, arg1)——异步 purgedigest map。

test_admin_builtin_coverage-t.cpp 依次验证(前 11 项断言覆盖该路径):

  1. PROXYSQLTEST 3 0:清空任何既有内存 digest;
  2. PROXYSQLTEST 1 1:生成 1000 条(断言affected_rows == 1000);
  3. PROXYSQLTEST 2 0:快照,断言行数 > 0;
  4. PROXYSQLTEST 3 0:快照并 reset,断言返回行数等于快照行数;
  5. PROXYSQLTEST 2 0:再次快照,断言为 0(reset 确实生效);
  6. PROXYSQLTEST 1 1:重新生成 1000 条;
  7. PROXYSQLTEST 6 0:启动异步 purge,断言接受的条目数 > 0,随后有界轮询PROXYSQLTEST 2 0直至返回 0 行(10 秒超时,wait_for_empty_digest_table),证明异步 purge 真正完成。

这里对"异步完成"的验证是本测试区别于"仅接受命令成功"的关键——测试用mysql_affected_rows观察命令返回值,并用轮询观察 digest 表的终态,而不是简单调用后直接断言成功。

5.3 Fast-routing 路径:命令 12 / 13 / 16

对应源码中的 case:

  • case 12/case 16(第 1230–1248 行):ProxySQL_Test___GenerateRandom_mysql_query_rules_fast_routing(arg1, with_username)生成随机 fast-routing 表——12生成带 username的规则,16生成空 username(true参数)的规则;两者生成后都会执行load_mysql_query_rules_to_runtime()把表载入运行时;
  • case 13(第 1249–1262 行):循环load_mysql_query_rules_to_runtime()指定次数。

TAP 的对应断言(第 12–23 项):

  • PROXYSQLTEST 12 64:断言影响行数 = 64,并分别统计配置表mysql_query_rules_fast_routing与运行时表runtime_mysql_query_rules_fast_routing中username <> ''的行数,二者均为 64——证明生成且已加载到 runtime;
  • PROXYSQLTEST 13 2:重复加载两次后,runtime 中命名规则仍为 64 条——证明重复加载无损;
  • PROXYSQLTEST 16 64:断言影响行数 = 64,配置表与运行时表中username = ''的行数均为 64。

5.4 Teardown:从磁盘恢复规则

测试用 RAII 守卫QueryRulesRestore在构造时生效、所有退出路径(含早期 return 与异常路径)上执行:

LOAD MYSQL QUERY RULES FROM DISK LOAD MYSQL QUERY RULES TO RUNTIME

从而保证 fast-routing 生成器对规则表的污染在测试结束后被完全还原。设计文档还特别划定了不覆盖的边界:

  • 命令 31 的 mode 2/3:存在未调查的FIXME,不做覆盖尝试;
  • 无条件 early return 之后的代码、以及#ifdef DEBUG保护下的代码:不属于正常 GCOV 目标,使其覆盖需要生产代码或构建矩阵层面的决策,而不是增加 TAP 命令。

注册(groups.json 第 501 行):

"test_admin_builtin_coverage-t" : [ "mysql84-g5" ]

六、备选方案为何被否决

设计文档给出了三个被系统否决的备选方案,理解它们有助于把握设计意图:

  1. 为三个领域分别新增单元测试——否决。单元测试绕过了路由与 Admin 会话的集成接口,而恰恰是这些接口的集成覆盖存在缺口。
  2. 新增一个 GTID TAP——否决。现有测试已经具备所需的真实因果负载,缺陷仅仅是它没有在 GCOV 环境中执行;新增测试是重复工作量。
  3. 用普通 MySQL 充当 Aurora 源——否决。它缺少REPLICA_HOST_STATUS,无法让 ProxySQL 分配被测的 Aurora lag 字段,因而根本无法驱动目标分支。

七、验证方法与 CI 运行

设计文档对验证有硬性要求:每个变更的 TAP 必须在它所属的隔离组中运行,并且聚焦运行的 LCOV 报告必须命中目标行。实现计划给出了可复制的运行方式。

7.1 用 GCOV 构建目标测试

以 GTID 测试为例(ubuntu22_dbg_build容器内):

git_version=$(git describe --long --abbrev=7) git_epoch=$(git show -s --format=%ct HEAD) docker compose run --rm --no-deps --entrypoint bash \ -e GIT_VERSION_BASE="$git_version" -e GIT_VERSION="$git_version" \ -e SOURCE_DATE_EPOCH="$git_epoch" -e WITHGCOV=1 \ -w /opt/proxysql ubuntu22_dbg_build -lc ' make -j8 WITHGCOV=1 GIT_VERSION_BASE="$GIT_VERSION_BASE" GIT_VERSION="$GIT_VERSION" debug make -C test/tap -j8 WITHGCOV=1 GIT_VERSION="$GIT_VERSION" tap make -C test/tap/tests -j8 WITHGCOV=1 GIT_VERSION="$GIT_VERSION" test_gtid_from_ok-t '

7.2 在隔离组中聚焦运行并检查 LCOV

export INFRA_ID=coverage-gtid-proof export TAP_GROUP=mysql84-g5 export TEST_PY_TAP_INCL='test_gtid_from_ok-t' export COVERAGE=1 ./test/infra/control/ensure-infras.bash ./test/infra/control/run-tests-isolated.bash

预期结果:TAP 通过,且 LCOV 报告把 MyHGC.cpp 中的 GTID 候选行标记为已执行。Aurora 组(TAP_GROUP=cluster_sim_aurora-g1)的预期是 LCOV 覆盖 lag 过滤与 writer 移除路径;Admin 组(mysql84-g5)的预期是ProxySQL_Admin_Tests2.cpp覆盖 case 2/3/6/12/13/16。

7.3 组注册校验与最终检查

所有 groups.json 变更须通过仓库 linter,全部变更文件须通过空白检查:

python3 test/tap/groups/lint_groups_json.py python3 test/tap/groups/check_groups.py --source python3 test/tap/groups/lint_group_coverage.py test/tap/groups/groups.json git diff --check

还可直接在覆盖率报告产物中检索目标文件确认命中:

rg -n 'MyHGC\.cpp|ProxySQL_Admin_Tests2\.cpp' ci_infra_logs/*/coverage-report/*.info

八、小结

这份设计给出了一个可复用的"覆盖率缺口修补"方法论:优先复用并重定位已有真实负载(GTID)、把模拟器严格限制在控制面(Aurora)、以行为后置条件驱动内建命令覆盖(Admin)。三者都坚持走真实前端/Admin 接口、坚持在 GCOV 组中聚焦运行、坚持用 LCOV 逐行验收。对希望为 ProxySQL 贡献测试覆盖的开发者而言,这套"设计 → 计划 → 聚焦验证 → 注册 → 校验"的流程与 设计文档、实现计划 中的完整命令,可以直接作为后续同类工作的模板。

  • 后端
  • 数据库
  • 负载均衡

【免费下载链接】proxysql

High-performance proxy for MySQL and PostgreSQL

项目地址:https://gitcode.com/gh_mirrors/pr/proxysql
点击查看免费下载
上一篇:终极智能家居安全配置指南:保护Home Assistant系统的10个关键步骤
下一篇:Linux Deploy终极指南:在Android上快速搭建完整Linux系统

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

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

Spring Boot + 微信小程序个人商铺系统:从设计到部署全指南

1. 为什么这个组合值得做&#xff1a;Spring Boot 微信小程序的选型逻辑1.1 从毕设要解决的问题说起最近有不少同学问我&#xff0c;毕设选题到底选什么方向最稳妥。我的回答一直是&#xff1a;不要追那些看起来很炫酷但做不出来的题目&#xff0c;而是找一个需求清晰、技术栈…

作者头像 李华