从运维CLI到Ruby库:Jetpants Topology对象模型与map/reduce式分片并行查询实践
从运维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 里给标准库"打补丁",提供了三种武器:
concurrent_each/concurrent_map:像each/map一样遍历,但每个元素在独立线程中执行(monkeypatch.rb)。limited_concurrent_map:带线程上限的并发 map,上限取Jetpants.max_concurrency与传入参数较小值,避免打爆数据库连接池(monkeypatch.rb)。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 与库的对应关系,就能开始写属于你自己的拓扑自动化脚本了。