ProxySQL Cluster Simulator 载荷处理:mv_readers_to_writers jq 过滤器将 writers 一键转换为 readers
ProxySQL Cluster Simulator 载荷处理:mv_readers_to_writers jq 过滤器将 writers 一键转换为 readers
本文讲解 ProxySQL 仓库内置的 Cluster Simulator(集群模拟器)中一个短小精悍的 jq 过滤器 —— mv_readers_to_writers,它利用测试载荷里 hostgroup_id 顺序编号的约定,将 proxysql_init_state 中的 writer 条目批量转换为对应的 reader 条目。读完本文,你将掌握该过滤器的一行 jq 语义、在 READ_ONLY 读写分离测试载荷中的实际用法、可验证的运行结果,以及它的适用前提与边界。
背景:Cluster Simulator 载荷中的读写角色建模
ProxySQL 仓库的 cluster_simulator 是一个集群模拟器:它以 SQLite 伪造后端服务器的状态(如 Galera 的 wsrep_* 变量、READONLY_STATUS 表的 read_only 标志),让 ProxySQL 的监控线程在本地即可感知集群状态变化,从而验证 ProxySQL 对 Galera、Group Replication、Read-Only 复制集群的自动发现、故障切换与读写分流行为。模拟器以 JSON payload(载荷) 为输入,每个 payload 是一个 JSON 数组,数组成员即一个个测试用例。
以 READ_ONLY 类型载荷为例,一个测试对象包含以下关键成员(字段定义见 cluster_simulator README 的 Payload Format 章节):
mysql_servers:待配置到 ProxySQL 的后端服务器列表,形如{hostgroup_id, hostname, port};mysql_replication_hostgroups:读写复制组配置,每个对象形如{writer_hostgroup, reader_hostgroup, check_type, comment},含义与 ProxySQL 的mysql_replication_hostgroups表完全一致;readonly_servers_init_state/readonly_servers_new_state:模拟服务器read_only标志的初始值与变更值(0/1);proxysql_init_state/proxysql_final_state:ProxySQL 应用初始配置后、以及集群状态发生变化并完成重配置后,mysql_servers表中的预期服务器状态(含hostgroup_id, hostname, port, status)。
在 READ_ONLY 模式下,ProxySQL 监控器根据 read_only 标志的翻转,把服务器在 writer_hostgroup 与 reader_hostgroup 之间搬移。而构造这类测试载荷时有一个非常显著的约定:hostgroup_id 采用顺序编号,且 reader 组号恰好是 writer 组号 + 1。这一约定在仓库的真实测试载荷中大量可见,例如 read_only_test_single_server_two_hostgroups.json 中定义了:
"mysql_replication_hostgroups": [
{ "writer_hostgroup": 2271, "reader_hostgroup": 2272, "check_type": "read_only", "comment": "" },
{ "writer_hostgroup": 2273, "reader_hostgroup": 2274, "check_type": "read_only", "comment": "" }
]
即 2271 → 2272、2273 → 2274。正因为有 reader_hostgroup = writer_hostgroup + 1 的顺序编号约定,mv_readers_to_writers 过滤器才能用一行代码完成角色转换。
过滤器本体:一行 jq 完成 writers → readers
mv_readers_to_writers 过滤器的完整代码位于仓库的 mv_readers_to_writers.jq,全文只有一行:
.[0] | .proxysql_init_state[] | .hostgroup_id += 1
其配套的说明文档 mv_readers_to_writers.md 指出:由于 hostgroup_id 被选为顺序编号,这个过滤器可以轻松地将 proxysql_init_state 中出现的 writers 转换为 readers。逐段拆解这行 jq 的语义:
| 片段 | 语义 |
|---|---|
.[0] |
取出载荷数组的第一个测试对象。payload 顶层是 [ {...}, {...}, ... ] 数组,.[0] 定位到第一个用例 |
.proxysql_init_state[] |
遍历该对象的 proxysql_init_state 数组,产出服务器对象流 |
.hostgroup_id += 1 |
对对象流中的每个服务器执行更新赋值:hostgroup_id = hostgroup_id + 1,并输出更新后的对象 |
关键点在于:更新赋值只会修改 hostgroup_id 字段,其余字段(hostname、port、status、comment 等)原样保留。因此输出是"原 writer 条目整体平移一个组号"后的对象流——在顺序编号约定下,N 号 writer 组恰好被换算成 N+1 号 reader 组。
实战验证:用真实测试载荷运行
以 read_only_test_single_server_two_hostgroups.json 中 writer_is_also_reader: 0, RO: 0 -> 1 的用例(第三个数组元素)为例。该用例的初始与最终状态如下:
"proxysql_init_state": [
{ "hostgroup_id":2271,"hostname":"127.1.1.11","port":3306,"status":"ONLINE" },
{ "hostgroup_id":2273,"hostname":"127.1.1.11","port":3306,"status":"ONLINE" }
],
"proxysql_final_state": [
{ "hostgroup_id":2272,"hostname":"127.1.1.11","port":3306,"status":"ONLINE" },
{ "hostgroup_id":2274,"hostname":"127.1.1.11","port":3306,"status":"ONLINE" }
]
初始状态下服务器位于 writer 组 2271、2273;当 read_only 从 0 变为 1 后,最终状态中服务器只出现在对应的 reader 组 2272、2274。对 proxysql_init_state 施加过滤器:
jq -c -f test/deps/cluster_simulator/docs/payloads_processing/jq_filters/mv_readers_to_writers.jq \
test/deps/cluster_simulator/tests/readonly_tests_payloads/read_only_test_single_server_two_hostgroups.json
输出为:
{"hostgroup_id":2272,"hostname":"127.1.1.11","port":3306,"status":"ONLINE"}
{"hostgroup_id":2274,"hostname":"127.1.1.11","port":3306,"status":"ONLINE"}
可以看到,过滤后的对象流与 proxysql_final_state 中的 reader 条目逐字段一致。这验证了该过滤器在"writer 因 read_only 置位而降级为 reader"这一典型 READ_ONLY 场景中的准确映射:由 proxysql_init_state 派生最终状态中的 reader 侧条目,无需手工重写 JSON。从模拟器库的实现看,这种 writer/reader 组号映射正是 READ_ONLY 状态处理的核心:在 readonly_utils.cpp 中,模拟器从 mysql_replication_hostgroups 逐一提取 writer_hostgroup 与 reader_hostgroup 并执行对应的 SQL 更新,组号配对关系与过滤器假设的 +1 约定完全一致。
与其他 jq 过滤器的协同使用
mv_readers_to_writers.jq 位于 jq_filters 目录中,与一组功能互补的过滤器并存,共同构成载荷处理工具箱:
- all_init_state_readers.jq:筛选出
proxysql_init_state中预期为 reader 的服务器; - init_state_rm_invalid_readers.jq:剔除被错误放置在 reader 组中的无效条目,用于修复历史载荷,其文档 init_state_rm_invalid_readers.md 给出了完整用法示例:
jq -c -f jq_filters/init_state_rm_invalid_readers.jq examples/read_only_test_multiple_servers_multiple_hostgroups_invalid.json - init_state_writers_only.jq:只保留被保留为 writer 的服务器;
- servers_in_reader_hostgroups.jq 与 servers_in_writer_hostgroups.jq:分别列出位于 reader / writer 组的服务器;
- hostgroups_by_type_by_hostname.jq 与 add_servers_comment.jq:按类型/主机名归类 hostgroup、批量添加注释。
此外,payloads_processing README 还推荐了一类用于结果比对的差集过滤器,例如对比 exp_proxysql_state 与 act_proxysql_state 差异:
jq --compact-output ".results[0] | (.exp_proxysql_state? - .act_proxysql_state?) + (.act_proxysql_state? - .exp_proxysql_state?) | .[]" simulator_output.json
当需要在多个载荷间批量修改逻辑、推理失败场景或按逻辑规则生成全覆盖测试用例时,这些过滤器可以组合成完整的载荷加工流水线:先用 mv_readers_to_writers 派生 reader 侧状态,再用 init_state_rm_invalid_readers 清理无效条目,最后用差集过滤器对模拟结果做回归校验。
适用前提与注意事项
使用 mv_readers_to_writers 时,有几个边界必须清楚:
- 强依赖顺序编号约定。过滤器无条件执行
+1,仅在reader_hostgroup == writer_hostgroup + 1时才成立。若某载荷打破了该约定(例如 Galera 载荷中 reader 组号与 writer 组号无等差关系),输出就会指向未配置的组号。这一点在 READ_ONLY 载荷中成立,但不要盲目套用到其他 cluster_type 的载荷。 - 只处理第一个测试对象。
.[0]固定取数组首元素。若载荷包含多个测试用例(仓库中的 payload 文件通常都是多用例数组),需要先拆分或用映射表达式对每个元素分别处理。 - 只作用于
proxysql_init_state。过滤器不会改动mysql_servers、mysql_replication_hostgroups或proxysql_final_state,它只负责把初始状态中的 writer 条目换算成 reader 条目。 writer_is_also_reader: 1时直接 +1 会失真。同文件第二个用例(writer_is_also_reader: 1, RO: 1 -> 0)中,proxysql_init_state已包含 reader 组条目(2272、2274),若对其施加+1会得到 2273、2275——这些组号并不存在于该测试的 hostgroup 定义中。因此该过滤器仅适用于初始状态全部由 writer 条目构成的场景(即writer_is_also_reader: 0的测试)。- 字段保真。更新赋值仅触碰
hostgroup_id,status、comment等字段原样透传;若需要同时调整状态字段(例如把status改为SHUNNED),需另行追加映射表达式。
顺带一提,过滤器文件名写作 mv_readers_to_writers,但按配套文档 mv_readers_to_writers.md 的正式描述,其实际功能是将 proxysql_init_state 中的 writers 转换为 readers,阅读代码与文档时应以功能描述为准。
在仿真测试工作流中的位置
将过滤器融入 cluster_simulator 的完整工作流时,典型链路是:编写含空 proxysql_init_state / proxysql_final_state 的模板载荷 → 以 --mode simulate 运行模拟器,从 act_proxysql_state 取回实际初始状态 → 用 jq 过滤器(包括 mv_readers_to_writers)批量加工、派生或修复状态数组 → 回填载荷 → 以 --mode verify 校验,通过后投入 simulator_wrapper-t 测试集自动执行。对应的 ProxySQL 运行配置(如 readonly_sim_conf.cnf)中 monitor_read_only_interval、monitor_read_only_timeout 等变量决定了监控线程发现 read_only 翻转并搬运服务器的节奏,这也是构造"writer 降级为 reader"测试载荷时需与之匹配的现实条件。
总而言之,mv_readers_to_writers 用一行 jq 把顺序 hostgroup 编号约定转化为生产力:无论是手工推导预期状态、批量生成 READ_ONLY 回归用例,还是快速修复历史载荷,它都能以可复现、可审计的方式完成 writers → readers 的角色换算。