2. Cromwell 架构概述
说实话,我第一次接触 Cromwell 的时候,也被它这套架构整得有点懵。后来用久了才发现,它的设计其实很清晰。我个人习惯把 Cromwell 理解成一个「翻译官」——你把 WDL 写好的工作流扔给它,它负责跟底层的计算资源打交道,不管你是用本地 Docker、HPC 集群还是云上的批量计算服务。
这一章我们就把 Cromwell 的架构拆开,看看它到底是怎么工作的。
2.1 整体架构:Cromwell 把活分成几块?
Cromwell 的核心模块其实就三个:入口服务、作业调度器、后端执行器。给你举个具体例子——你提交一个 WDL 脚本,入口服务先收下,然后调度器把 WDL 里的每个 task 拆成独立作业,再交给后端去跑。后端可能是你的本地机器,也可能是 Google Cloud Life Sciences API。
一句话记住:Cromwell = 入口 + 调度 + 后端。后端决定了你的工作流跑在哪儿。
| 模块 | 职责 | 我遇到过的问题 |
|---|---|---|
| 入口服务 | 接收 WDL / CWL 脚本、管理 HTTP API、处理用户请求 | 接口超时多半是并行数太高 |
| 作业调度器 | 拆解工作流、管理依赖关系、生成任务队列 | 依赖写错了会死循环,别问我怎么知道的 |
| 后端执行器 | 真正执行任务,写中间文件,返回结果 | 本地后端最稳,云端后端配置容易漏 |
2.2 两种运行模式:Server vs. Single
Cromwell 有两种启动方式,这一点你最好一开始就搞清楚。为什么?因为在项目里选错了模式,后面切换环境会很痛苦。
- Server 模式:Cromwell 以 HTTP 服务形式常驻后台,你可以持续提交作业,查询状态,管理多个工作流。适合生产环境。
- Single 模式(也叫 Run 模式):一次性提交一个工作流,跑完就退出。适合调试或单次小任务。
我在项目中最常用的就是 Server 模式。举个例子,我们团队的 WDL 脚本调试阶段先用 Single 模式跑个小样本,确认无误后切到 Server 模式批量提交全基因组数据。
# Server 模式启动(后台常驻)
java -jar cromwell-85.jar server --config cromwell.conf
# Single 模式启动(跑完即止)
java -jar cromwell-85.jar run my_workflow.wdl --inputs my_inputs.json
2.3 核心模型:Workflow → Task → Command → Call
Cromwell 的执行单元是层级嵌套的。我从上到下给你捋一遍:
- Workflow:你写的 WDL 文件描述了一个完整的工作流流程。
- Task:工作流里的每个独立步骤,比如比对、排序、变异检测。
- Command:Task 内部的 shell 命令——这就是真正要运行的代码。
- Call:一次实际的执行实例。同一个 Task 可以产生多个 Call,比如对多个样本分别跑比对。
嗯,这里要注意:同一个 Task 被并行调用多个 Call 时,每个 Call 都是独立的运行环境。数据隔离靠输入文件路径来保证。
# 一个简单的工作流结构示意
workflow my_pipeline {
call align
call sort { input: bam = align.out_bam }
}
task align {
command {
bwa mem ref.fa read1.fq read2.fq > aligned.sam
}
output {
File out_bam = "aligned.sam"
}
}
我个人经验:写 Task 时尽量把 command 短一点。太长不但难调试,而且一旦报错,定位具体行数很麻烦。我习惯把一个复杂命令拆成 3 ~ 5 行的脚本。
2.4 工作流执行的生命周期
Cromwell 执行一个工作流时会经历以下几个阶段:
- 提交:入口服务收到 WDL + 输入 JSON。
- 解析与校验:检查 WDL 语法、变量类型、输入文件是否存在。
- 调度:调度器根据依赖关系,决定哪些 Call 可以并行,哪些必须等前序。
- 执行:将每个 Call 的 command 发给后端执行器。
- 监控:不断轮询任务状态,直到完成或失败。
- 输出收集:把任务输出文件路径记录到 metadata 中。
为什么会分成这么细?因为每个阶段都可能出错。我以前在解析阶段就踩过坑——输入 JSON 里写错一个变量名,Cromwell 会直接报错退出,而不会继续执行后续步骤。
2.5 架构核心逻辑:一张图看懂
讲到这里,我觉得有必要给你看一张整体的架构逻辑图。你可以把它当成学习 Cromwell 的思维导图。
图中你可以看到一条清晰的主线:用户提交请求 → 入口服务 → 调度器 → 后端 → 执行器 → 数据管理。每个箭头都代表数据或指令的流动。说实话,我当初学 Cromwell 时自己画了这张图,此后遇到问题都按这个思路排查,很少再卡住。
2.6 本地 vs 云端:架构上的区别
搞清楚通用架构后,我们聊聊本地和云端在架构层面的核心差异。
| 维度 | 本地后端 | 云端后端 |
|---|---|---|
| 调度器 | 直接在本机调用系统命令 | 调用云服务 API(如 Batch / Life Sciences) |
| 数据存取 | 读取本地文件系统 | 读写对象存储(GCS / S3) |
| 资源限制 | 受本机 CPU / 内存 / 磁盘限制 | 按需申请,吞吐量大 |
| Call Cache | 基于本地文件哈希 | 基于云端对象哈希 |
| 成本 | 设备折旧 + 电费 | 按用量付费 |
避坑提醒:我曾经在本地调试时一切正常,切到云端后端后所有任务都失败。查了半天才发现是云端的执行环境没有安装 bwa。所以,云端后端的镜像和软件依赖一定要提前确认。
2.7 数据库的角色:Cromwell 的「记忆」
Cromwell 默认使用 H2 数据库(嵌入式),也支持 MySQL / PostgreSQL。它的作用是什么呢?说白了就是 Cromwell 的「记事本」——记录每个工作流、每个 Call 的状态、输入输出、日志路径。
为什么要提这个?因为如果在生产环境里长时间运行,H2 会撑不住。我有个朋友的项目跑了一个月,H2 文件涨到 50G,启动一次要 10 分钟。后面换成 MySQL,整个世界清静了。
# MySQL 配置示例
database {
driver = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/cromwell?useSSL=false"
user = "cromwell_user"
password = "your_password"
db {
connectionTimeout = 5000
numRetries = 3
}
}
2.8 本章小结
Cromwell 的架构不复杂,但理解它是用好 Cromwell 的前提。记住三点:
- 入口 + 调度 + 后端,三层各司其职。
- 本地和云端的主要差异在后端和数据存取方式。
- 数据库选型影响长期稳定性,别忽视。