ProxySQL Cluster Simulator 中的 jq 过滤器实战:按类型与主机名检索 hostgroup 成员
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))]
| .[]
逐行拆解:
.[0]:payload 是数组(可包含多个测试用例),取第一个测试对象。如果只想分析第 N 个用例,改为.[N-1]即可。{repl_hgs: .mysql_replication_hostgroups, proxysql_state: .[$state]}:从对象中抽取两个关键字段打包成一个新对象——repl_hgs持有全部读写映射,proxysql_state通过$state变量动态取字段,即按参数决定使用proxysql_init_state还是proxysql_final_state。这是 jq 用--arg参数化字段名的典型手法。[.repl_hgs[] as $repl_hg | ...]:遍历每个 replication hostgroup 映射,绑定为$repl_hg,对每个映射生成一个子结果,整个表达式由外层[...]收集成数组。- 内层
(.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:记录的主机名等于目标主机。
| .[]:最后把数组摊平成逐条 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 目录还提供了若干与本过滤器互补的即用过滤器,可组合使用:
- servers_in_reader_hostgroups.md:直接展示
mysql_servers中所有被配置为 reader 的服务器,适合先看全局再钻取单机。 - mv_readers_to_writers.md:利用 hostgroup_id 顺序性,将
proxysql_init_state中的 readers 就地转换为 writers——当 writer/reader hostgroup ID 是相邻整数时,这种批量变换能快速构造对照数据。 - init_state_writers_only.md、init_state_rm_invalid_readers.md 等,分别承担初始状态裁剪、非法 reader 清理等任务。
值得注意的是,过滤器处理的输入不仅限于测试 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"闭环中最常用的一环。