从运维CLI到Ruby库:Jetpants Topology对象模型与map/reduce式分片并行查询实践

原创2026-10-17 19:10:141,885 阅读
文章标签:运维

从运维CLI到Ruby库:Jetpants Topology对象模型与map/reduce式分片并行查询实践

Jetpants 是 Tumblr 开源的 MySQL 自动化工具包,既能作为运维 CLI 管理上百台数据库机器、数百亿行数据,也能作为完整的 Ruby 库供你编写自定义迁移脚本。本文带你理解 Jetpants 的 Topology 对象模型,以及它如何用 map/reduce 式的并行查询快速统计全拓扑数据。

🧭 一张拓扑 = 一组可遍历的对象

Jetpants 把复杂的 MySQL 拓扑抽象成五层对象,从大到小依次是:

Topology → ShardPool → Shard / Pool → DB → Host

  • Topology:整个拓扑的总入口,维护所有池和分片的列表。它是一个单例,通过 Jetpants.topology 全局访问(见 lib/jetpants.rb 中 @topology = Topology.new 的初始化)。
  • ShardPool:一个分片命名空间,包含许多共享同一套分片键空间的 Shard(lib/jetpants/shardpool.rb)。
  • Shard:区间型(range-based)分片,本质是带 min_id 到 max_id(或 INFINITY)范围的 Pool,表示"这个池只存这段 ID 区间的数据"(lib/jetpants/shard.rb)。
  • Pool:1 个 master + 若干 slave 的数据库组,slave 分为 active / standby / backup 三种角色(lib/jetpants/pool.rb)。
  • DB / Host:单台数据库实例及其所在主机。

这种分层带来一个直观的好处:你不需要关心"哪台机器在哪个机房",只要遍历对象就能操作拓扑。Topology 还内置了懒加载与同步机制——首次访问 pools 时才触发 load_pools,且由插件接管真正的数据加载(lib/jetpants/topology.rb),核心代码只留"桩",具体实现交给 jetpants_collins、simple_tracker 等资产跟踪插件(详见 doc/plugins.rdoc)。

常用查询接口

Topology 上最常用的几个方法:

方法 作用
pools 全部池(含分片池)
shards(name) 指定 ShardPool 内的所有 Shard
shard_for_id(id) 根据分片键 ID 找到负责的数据分片
functional_partitions 非分片的"全局池"
claim_spares(n) 从备用机器池申领 N 台新机器

其中 shard_for_id 的分发逻辑很有意思:分片分裂过程中如果子分片还在构建(未 in_config?),会优先返回已上线的子分片,否则返回父分片,保证分裂期间查询路由始终正确(lib/jetpants/topology.rb)。

💻 在 console 里把玩 Topology

入门最快的方式是 jetpants console,它打开一个已进入 Jetpants 模块命名空间的 irb 会话。由于模块会把未知方法代理给 Topology 单例(lib/jetpants.rb),你可以直接写:

> pools.each &:summary                      # 打印所有池的摘要
> shards.each {|s| puts s.min_id, s.max_id } # 查看各分片的 ID 区间

Shard 对象还带有丰富的 state 状态机(:ready、:exporting、:importing、:child 等十余种状态),一眼就能看出每个分片正处在分裂流程的哪一步(lib/jetpants/shard.rb)。拓扑的角色假设、分片键要求等背景知识可参考 doc/requirements.rdoc。

🚀 map/reduce 式分片并行查询

真正让 Jetpants 作为"库"好用的,是它对 Enumerable 的并发扩展。核心在 lib/jetpants/monkeypatch.rb 里给标准库"打补丁",提供了三种武器:

  1. concurrent_each / concurrent_map:像 each/map 一样遍历,但每个元素在独立线程中执行(monkeypatch.rb)。
  2. limited_concurrent_map:带线程上限的并发 map,上限取 Jetpants.max_concurrency 与传入参数较小值,避免打爆数据库连接池(monkeypatch.rb)。
  3. Range#in_chunks:把一个 ID 区间切成 N 块并行处理,块内回调拿到 min_id, max_id——这是分片内再做"细粒度 map"的关键(monkeypatch.rb)。

由此得到一个天然的两级 map/reduce 模式:

  • map(分片级):用 limited_concurrent_map 并发遍历所有 Shard,每个分片挑一台 standby slave 执行统计;
  • map(区间级):单个分片内再用 in_chunks 把 ID 区间切成多块并行 COUNT;
  • reduce(汇总):把各分片返回的 {表名 => 行数} 哈希累加成全局总数。

📊 官方示例:一行代码数全库

仓库里的 scripts/global_rowcount.rb 就是这套模式的教科书实现,脚本注释也明说了它"可以当作用 Jetpants 库写脚本的示例"。骨架只有 3 步:

# 1. map:8 个线程并发遍历所有分片,在 standby slave 上做分块统计
sharded_counts = Jetpants.shards.limited_concurrent_map(8) do |p|
  node = p.standby_slaves.last
  node.stop_query_killer
  counts = node.row_counts(sharded_tables, p.min_id, my_max)
  node.start_query_killer
  counts
end

# 2. reduce:把每个分片的计数按表名累加
total_counts[table_name] ||= 0
total_counts[table_name] += row_count

内部细节同样讲究:row_counts 会对每张表按 chunks 配置发起 10 路并发 COUNT,比单条大 COUNT 快得多(lib/jetpants/db/import_export.rb),所以脚本特意叮嘱"别在 master 或 active slave 上跑"。功能池(非分片池)则走同样的 map/reduce 流程再汇总,最终 puts total_counts 输出全拓扑的总行数。

这套模式可以轻松移植到更多场景:全拓扑备份校验、数据倾斜分析、批量清理等——只要把"每个分片做什么"封进 block,剩下的并发与汇总都由对象模型帮你兜底。

✅ 小结

  • Jetpants 的 Topology 对象模型把"几百台机器"压缩成五层可遍历的 Ruby 对象,配合 jetpants console 可即时探查拓扑。
  • 插件机制负责"从哪读拓扑、往哪写配置",核心模型保持精简,simple_tracker / jetpants_collins 是两种典型实现(doc/plugins.rdoc)。
  • 并发三件套 concurrent_map、limited_concurrent_map、in_chunks 让"分片级 + 区间级"两级 map/reduce 成为写自动化脚本的肌肉记忆,scripts/global_rowcount.rb 值得逐行精读。

想动手实践?先配置好 etc/jetpants.yaml.sample 示例中的必填项,打开 doc/commands.rdoc 对照 CLI 与库的对应关系,就能开始写属于你自己的拓扑自动化脚本了。

登录后查看全文
jetpants