4、进程(process)与executor:进程定义、输入输出声明、executor选择
说实话,我刚开始接触 Nextflow 那会儿,觉得它就是个能写循环的 shell 脚本工具。但真正用起来才发现,process 才是整个流水线的灵魂。每个 process 就是一个独立的任务单元,你能控制它跑什么、怎么跑、跑在哪里。而 executor,说白了就是告诉你“这活儿该交给谁干”。这一节我就把这两个核心概念掰开揉碎了讲清楚。
4.1 进程(process)长什么样?
一个 process 的基本框架很简单:
process myProcess {
// 指令区(directives)
executor 'local'
cpus 2
memory '4 GB'
// 输入声明
input:
path input_file
// 输出声明
output:
path output_file
// 执行脚本
script:
"""
your_command --input ${input_file} --output ${output_file}
"""
}
我个人的习惯是先写指令区,再写输入输出,最后写脚本。这样逻辑顺一些。不然你脚本写到一半发现忘了声明资源,还得回头补,挺打断思路的。
4.2 输入输出声明——别小看这两行
输入输出声明是 Nextflow 的“契约机制”。你声明了 input,Nextflow 就会自动帮你组织数据、处理通道;你声明了 output,它就能把产物传递给下游。这是个典型的声明式编程思想——你只说“我要什么”,不用写复杂的文件传递逻辑。
4.2.1 常用的输入类型
| 类型 | 说明 | 示例 |
|---|---|---|
path |
文件或目录路径 | path input_file |
val |
值类型(字符串、数字等) | val species_id |
env |
环境变量 | env PATH |
stdin |
标准输入 | stdin |
tuple |
组合多种类型 | tuple val(id), path(file) |
曾经有个项目,我光顾着声明 path 却忘了处理目录结构,结果下游 process 拿到的文件路径全乱了。后来我养成了一个习惯:在输入声明里加上 .sort() 或者用 .map{ it -> ... } 做一次快速校验,确保文件列表的顺序和内容是对的。
4.2.2 输出声明与多输出
一个 process 可以输出一个文件、一个值,也可以是多个。用 emit 给输出打标签是个好做法:
process alignReads {
input:
path reads
output:
path "*.bam" into bam_output
path "*.bai" into bai_output
script:
"""
bwa mem reference.fa ${reads} | samtools sort > sorted.bam
samtools index sorted.bam
"""
}
这里我用了 into 把不同输出送入不同的通道。这样下游的 process variantCalling 就能单独拿到 bam 文件和 index 文件,不用自己再去解析目录。
path("${id}/*") 配合通配符一次性捕获。但注意,这样会丢失文件的结构信息,我一般只在调试阶段这么用。
4.3 executor——你的任务交给谁?
executor 决定了 process 的脚本会被提交到哪个计算环境运行。Nextflow 支持多种 executor,我列一下最常用的:
| Executor | 适用场景 | 特点 |
|---|---|---|
local |
单机调试、小数据量 | 直接在本地 shell 执行,无需额外配置 |
slurm |
HPC 集群 | 使用 SLURM 调度器,适合大批量并行作业 |
lsf |
IBM LSF 集群 | 与 SLURM 类似,但某些老集群还在用 |
sge |
Sun Grid Engine | 很多高校机房还在用 |
awsbatch |
AWS 云 | 配合 AWS Batch,资源弹性伸缩 |
k8s |
Kubernetes 集群 | 容器化部署,适合 DevOps 团队 |
google-lifesciences |
Google Cloud | 专为生信设计的云 executor |
4.3.1 怎么选 executor?
我的经验就一句话:开发测试用 local,上线生产用集群。但这里面有几个坑,我给你列一下:
- local executor 不检查资源限制。你在指令里写
cpus 8,local 模式并不会真的帮你绑定 CPU,所有进程共享宿主机资源。我曾经在测试时写了个 64 核的并行任务,结果笔记本风扇直接起飞。嗯,从那以后我每次 local 测试都会先加maxForks 2限制并发数。 - slurm executor 需要你正确配置分区和队列。很多初学者只改了 executor 名称,忘了写
queue或者clusterOptions,结果任务一直排队。 - 云 executor(awsbatch / google-lifesciences)要额外配置 IAM 权限和存储挂载。这个相对麻烦,但一旦配好了,弹性伸缩真的爽。
nextflow.config 里全局设置,也可以在每个 process 指令区单独覆盖。我建议:全局用 HPC 的 executor,个别需要特殊资源(比如 GPU 加速)的 process 单独指定。这样既统一,又灵活。
// nextflow.config 示例
process {
executor = 'slurm'
queue = 'normal'
// 针对 gpu 进程单独覆盖
withName: gpuProcess {
executor = 'local'
cpus = 1
memory = '8 GB'
}
}
4.4 进程与 executor 的关系图
下面这张图帮你理清 process 和 executor 在流水线中的位置:
这张图里,executor 位于 process 下方,像个“底座”一样支撑着 process 的运行。你想想看,不指定 executor,系统就会用默认的 local,而且 Nextflow 会自动查找环境中的调度器(比如你机器上装了 SLURM 的话,它可能会自动切换)。我个人的建议是:显式写死在 nextflow.config 里,避免因环境差异造成意外。
4.5 实战中的避坑指南
- 路径问题:process 默认的工作目录是
work/下的一个散列目录。你如果直接写绝对路径,很可能会出权限问题。用path类型,Nextflow 会自动帮你 staging 文件。 - 资源声明不等同于硬限制:在 local executor 下,你写
cpus 4只是给调度器一个提示,并不会真的限制 CPU 使用。在 SLURM 下,它会自动转成#SBATCH --cpus-per-task=4。务必确认你的集群配置。 - 输出文件要显式捕获:如果脚本产生了临时文件但你没写在 output 里,这些文件会在 process 结束后被清理掉。我曾经掉过这个坑,调试了半天才发现中间产物被删了。
- executor 切换时的资源匹配:从 local 切到 slurm,可能你的
memory写的是'4 GB',但集群默认单位是 MB,Nextflow 会自动换算,不过最好显式写单位。
script: 里加一行 echo "Starting $(date)",这样既能方便查看日志,也能确认 process 是否真的跑起来了。
好了,这一节的内容其实很硬核,但理解了 process 的结构和 executor 的选型,你的流水线才真正有“骨架”。你想想看,没有 process 通道就到不了下一个步骤,没有 executor 代码就无处执行。这两个概念搞定了,后面学 channel、operator 这些就要轻松多了。