ProxySQL Cluster Simulator 中的 jq 过滤器实战:按类型与主机名检索 hostgroup 成员

原创2026-10-08 21:31:221,736 阅读
文章标签:后端数据库负载均衡

ProxySQL Cluster Simulator 中的 jq 过滤器实战:按类型与主机名检索 hostgroup 成员

导读

本文聚焦 ProxySQL Cluster Simulator(位于仓库 test/deps/cluster_simulator/)测试工具链中一个高频实用的 jq 过滤器——hostgroups_by_type_by_hostname。它可以从模拟器生成的 proxysql_init_state / proxysql_final_state 中,按 reader_hostgroup / writer_hostgroup 类型和具体主机名精确筛选出服务器记录,用于快速核对读写分离场景下服务器的 hostgroup 归属与迁移。读完本文,你将掌握该过滤器的完整命令行用法、逐行实现原理、输出解读方法,并能将其与仓库提供的其他 jq 过滤器组合,快速分析 RO 状态切换导致的 reader/writer 流转。

一、过滤器出现的背景:Cluster Simulator 的 Payload 处理

ProxySQL 仓库中的 Cluster Simulator(见 test/deps/cluster_simulator/README.md)利用 ProxySQL 通过 SQLite 伪造后端服务器状态的能力,把 Galera、ReadOnly、Group Replication 等集群场景的测试输入输出做成 JSON payload。payload 往往体积大、结构复杂,因此仓库在 payload 处理文档 中明确推荐使用 jq 对输出做快速分析,并在 jq_filters 目录下集中存放了多个即用型过滤器。

本过滤器 hostgroups_by_type_by_hostname 就是这批过滤器中的一员,它的定位非常聚焦:在给定某个 state(初始或最终)、某个 hostgroup 类型(reader 或 writer)、某个主机名时,找出该服务器出现在哪些 hostgroup 中。

二、核心概念:replication hostgroups 的 writer/reader 映射

要理解过滤器,先要理解它依赖的两个 payload 字段:

  • mysql_replication_hostgroups:数组,每个元素是一组读写映射,形如 { "writer_hostgroup": 2271, "reader_hostgroup": 2272, "check_type": "read_only", "comment": "" },它定义了哪些 hostgroup 是 writer、哪些是配套的 reader。
  • proxysql_init_state / proxysql_final_state:数组,记录模拟启动时 / 集群状态变更后,ProxySQL 中每个 {hostgroup_id, hostname, port, status, comment} 的期望状态。

过滤器的核心思想正是把这两个字段通过 hostgroup_id 关联起来:遍历每个 replication hostgroup 映射,取出 writer_hostgroup 或 reader_hostgroup 的 ID,再去 state 数组中匹配 hostgroup_id == 该 ID 且 hostname == 指定主机 的记录。这样得到的就不再是散乱的服务器条目,而是"这台服务器作为 reader/writer 分布在哪些 hostgroup"的结构化答案。

三、命令行用法与三个关键参数

原文档给出了两个可直接复制的命令示例,均以 read_only_test_multiple_servers_multiple_hostgroups.json 测试 payload 为输入(该 payload 位于 test/deps/cluster_simulator/tests/readonly_tests_payloads/read_only_test_multiple_servers_multiple_hostgroups.json):

jq -c --arg hostname "127.1.1.11" --arg state proxysql_init_state --arg type reader_hostgroup -f docs/payloads_processing/jq_filters/hostgroups_by_type_by_hostname.jq tests/readonly_tests_payloads/read_only_test_multiple_servers_multiple_hostgroups.json

{"hostgroup_id":2274,"hostname":"127.1.1.11","port":3306,"status":"ONLINE","comment":"mysql01"}
{"hostgroup_id":2278,"hostname":"127.1.1.11","port":3306,"status":"ONLINE","comment":"mysql01"}
jq -c --arg hostname "127.1.1.11" --arg state proxysql_final_state --arg type reader_hostgroup -f docs/payloads_processing/jq_filters/hostgroups_by_type_by_hostname.jq tests/readonly_tests_payloads/read_only_test_multiple_servers_multiple_hostgroups.json

{"hostgroup_id":2272,"hostname":"127.1.1.11","port":3306,"status":"ONLINE","comment":"mysql01"}
{"hostgroup_id":2274,"hostname":"127.1.1.11","port":3306,"status":"ONLINE","comment":"mysql01"}
{"hostgroup_id":2276,"hostname":"127.1.1.11","port":3306,"status":"ONLINE","comment":"mysql01"}
{"hostgroup_id":2278,"hostname":"127.1.1.11","port":3306,"status":"ONLINE","comment":"mysql01"}

(以上命令中 docs/...、tests/... 为原文档中相对 cluster_simulator 目录的路径写法;实际过滤器文件位于仓库 test/deps/cluster_simulator/docs/payloads_processing/jq_filters/hostgroups_by_type_by_hostname.jq。)

三个 --arg 变量是过滤器全部的外部输入,含义如下:

参数 取值示例 作用
hostname "127.1.1.11" 目标服务器的主机名,决定筛选哪一台服务器
state proxysql_init_state / proxysql_final_state 从哪个 state 数组中检索,二者对应模拟的前后两个时刻
type reader_hostgroup / writer_hostgroup hostgroup 类型字段名,决定按 reader 还是 writer 映射做关联

注意:type 取的就是 mysql_replication_hostgroups 元素上的字段名(reader_hostgroup 或 writer_hostgroup),而不是自定义标签,这也是该过滤器实现简洁的关键。

四、过滤器实现逐行解析

完整的 jq 过滤器内容如下(源文件):

.[0]
    | {repl_hgs: .mysql_replication_hostgroups, proxysql_state: .[$state]}
    | [.repl_hgs[] as $repl_hg | (.proxysql_state[] | select($repl_hg[$type] == .hostgroup_id and .hostname == $hostname))]
    | .[]

逐行拆解:

  1. .[0]:payload 是数组(可包含多个测试用例),取第一个测试对象。如果只想分析第 N 个用例,改为 .[N-1] 即可。
  2. {repl_hgs: .mysql_replication_hostgroups, proxysql_state: .[$state]}:从对象中抽取两个关键字段打包成一个新对象——repl_hgs 持有全部读写映射,proxysql_state 通过 $state 变量动态取字段,即按参数决定使用 proxysql_init_state 还是 proxysql_final_state。这是 jq 用 --arg 参数化字段名的典型手法。
  3. [.repl_hgs[] as $repl_hg | ...]:遍历每个 replication hostgroup 映射,绑定为 $repl_hg,对每个映射生成一个子结果,整个表达式由外层 [...] 收集成数组。
  4. 内层 (.proxysql_state[] | select($repl_hg[$type] == .hostgroup_id and .hostname == $hostname)):对 state 中的每条服务器记录,保留满足两个条件的记录:
    • $repl_hg[$type] == .hostgroup_id:当前映射的 writer/reader hostgroup ID 与记录中的 hostgroup_id 相等(按需选择是 writer 还是 reader);
    • .hostname == $hostname:记录的主机名等于目标主机。
  5. | .[]:最后把数组摊平成逐条 JSON 对象输出。配合 -c(compact)选项,每条记录单独一行,便于管道继续处理或肉眼比对。

需要说明的是:若同一台服务器同时出现在多个 writer/reader hostgroup 映射中(例如一个主机名注册在多组读写映射里),过滤器会为每个匹配的映射各输出一条记录——这正是该过滤器比"简单 grep hostname"更有价值的地方:它同时告诉你 hostgroup 的类型语义(是 reader 还是 writer)。

五、输出解读:用 init/final 对比观察 RO 切换

原文档给出了本过滤器最有代表性的用法——对比同一台服务器在 RO 切换前后的 reader 归属变化。

测试用例 read_only_test_multiple_servers_multiple_hostgroups.json 的 __comment__ 明确标注:"Multiple servers in multiple 'mysql_replication_hostgroup' - writer_is_also_reader: 1, RO: 0 -> 1",即所有服务器初始 read_only=0(可写),随后全部切换为 read_only=1(只读),且开启 monitor_writer_is_also_reader: 1。

以上文两条命令的输出为例,对服务器 127.1.1.11:

  • 初始状态(RO=0):作为 reader 只出现在 hostgroup 2274、2278 中。
  • 最终状态(RO=1):作为 reader 出现在 hostgroup 2272、2274、2276、2278 中,比初始状态多了 2272、2276。

参考该 payload 中 proxysql_init_state / proxysql_final_state 的完整数据(测试 payload),结合 mysql_replication_hostgroups 的映射 {writer: 2271, reader: 2272}、{writer: 2273, reader: 2274}、{writer: 2275, reader: 2276}、{writer: 2277, reader: 2278},可以推断出:RO=0 时 2271、2273 等 writer hostgroup 中也包含该服务器(writer_is_also_reader 生效),一旦 RO 切换为 1,ProxySQL 把可写 hostgroup 中的服务器整体迁移到对应 reader hostgroup(2271→2272、2273→2274、2275→2276、2277→2278),从而 127.1.1.11 在最终状态下同时成为 2272、2274、2276、2278 的 reader 成员。原文档对此的概括是:"可以很容易检查那些预计在 RO=0 时保持为 readers 的服务器,如何在 RO 变为 1 时全部被迁移为 readers"。

因此,把 --arg state proxysql_init_state 与 --arg state proxysql_final_state 两次运行结果并排对照,就是一条快速验证"读写分离迁移是否符合预期"的检查路径。若要进一步自动化断言,可以结合 payload 处理文档中给出的差异比较过滤器:

jq --compact-output ".results[0] | (.exp_proxysql_state? - .act_proxysql_state?) + (.act_proxysql_state? - .exp_proxysql_state?) | .[]" simulator_output.json

该过滤器(见 test/deps/cluster_simulator/docs/payloads_processing/README.md)用于找出 verify 模式下期望状态与实际状态的差异集合,与本过滤器配合,即可从"指定服务器的 reader/writer 归属"和"整体状态差异"两个维度完成校验。

六、仓库配套:同目录下的其他过滤器与扩展思路

jq_filters 目录还提供了若干与本过滤器互补的即用过滤器,可组合使用:

值得注意的是,过滤器处理的输入不仅限于测试 payload。仓库在 examples 目录 中还提供了"不完整/无效"形态的示例 payload,用于验证校验逻辑;同理,本过滤器对任意具备 mysql_replication_hostgroups + proxysql_init_state / proxysql_final_state 结构的 JSON 都适用,无需依赖真实运行环境。如果单个过滤器的表达力不足,payload 处理文档还提示可以退回到 node 目录 提供的 Node.js 模板做更复杂的原型化处理——JSON 本身就是合法的 JavaScript,这为复杂场景留出了足够的扩展空间。

七、使用前提与注意事项

  • 输入结构要求:过滤器假定 payload 是数组、第一个元素包含上述两个字段;字段缺失时 .[$state] 取到 null,过滤结果为空,属正常行为而非报错。
  • 参数语义:type 只接受 reader_hostgroup 或 writer_hostgroup,且该字段必须存在于 mysql_replication_hostgroups 元素中;state 必须与 payload 实际字段名完全一致。
  • 输出形态:默认逐条 JSON 输出;配合 -c 每条一行,便于 grep、diff 或再次管道进入其他 jq 表达式。
  • 适用范围:本过滤器服务于 Cluster Simulator 的 payload 分析工作流(详见 test/deps/cluster_simulator/README.md 中关于 READONLY / GALERA / GROUP_REPLICATION 各编译模式与 simulate / verify 两种运行模式的说明),是测试开发与结果核验阶段的辅助工具,而非 ProxySQL 运行时功能。

结语

hostgroups_by_type_by_hostname 是一个结构精巧、参数化程度高的小过滤器:三个 --arg 变量让它能覆盖"任意主机 × 任意 state × reader/writer 任选"的全部组合,而实现不过四行。掌握它之后,你可以快速回答"某台服务器在读写分离配置下应该落在哪些 hostgroup",并借此核对 RO 切换引发的 reader/writer 流转是否符合预期——这正是 Cluster Simulator 从"生成 payload"到"核验 payload"闭环中最常用的一环。

登录后查看全文
proxysql