进程与任务概念
这一章我们来聊聊 Nextflow 里最核心的两个抽象:进程(process)和任务(task)。很多人学了半天,代码能跑,但一问到「进程和任务到底什么关系」就卡壳。我个人习惯先把概念理清楚,再上手,后面调优才不慌。
进程的 inputs 和 outputs
进程是 Nextflow 中的基本执行单元。它接收输入,运行一段脚本,产生输出。定义进程时,input 和 output 块就是它的「接口」。
先看一个最简例子:
process sayHello {
input:
val x
output:
stdout
script:
"""
echo "Hello $x!"
"""
}
input 把通道里的值传进来,output 把结果发回通道。我个人在项目中遇到过不少人把 input 和 output 写反,结果数据流全乱了。其实只需要记住一句话:输入是消耗,输出是产出。
除了 val,还有 path、file、env 等修饰符。我最常用的是 path,因为它能自动处理文件暂存和链接,省很多事。
| 修饰符 | 用途 | 示例 |
|---|---|---|
| val | 传递任意值(字符串、数字、布尔) | input: val id |
| path | 传递文件或目录,自动符号链接 | input: path fastq |
| file | 与 path 类似,但旧版,不推荐 | input: file bam |
| env | 传递环境变量 | input: env HOME |
| stdout | 捕获脚本的标准输出 | output: stdout |
我的一点小技巧:如果你要输出多个文件,用 path "${prefix}_*" 加通配符,比一个个列路径省心。
script 块:真正干活的地方
script 块就是你写 shell 脚本的地方。它支持插值,所有 input 里定义的变量都能直接用。听起来简单,但有个坑:字符串里的美元符号 $ 会被 Nextflow 解析为变量,如果你要输出字面量的 $,得用两个 $$ 转义。
process quote {
input:
val name
script:
"""
echo "Your nickname is \${name}"
"""
这个 \${name} 里用反斜杠转义 $,实际上插值得到了变量 name。如果你写 ${name},它会去取 Groovy 变量,有时会报错。我曾经在写 BWA 命令时忘加反斜杠,结果 $SAMTOOLS 变量一直解析不对,排错花了半天。
script 块还支持 if/else 条件逻辑,结合 params 能根据不同条件生成不同命令。举个例子:
process align {
input:
path reads
output:
path "aligned.bam"
script:
if (params.aligner == "bwa") {
"""
bwa mem ref.fa $reads | samtools sort > aligned.bam
"""
} else if (params.aligner == "bowtie2") {
"""
bowtie2 -x ref -U $reads | samtools sort > aligned.bam
"""
} else {
error "Unknown aligner: ${params.aligner}"
}
}
这样写比硬编码 module 灵活,也方便切换流程。不过要注意,script 块里的每个字符串前缀 """ 必须顶格写,缩进用空格。
executor 与 task:谁在干活?
进程是一个蓝图,实际执行时才产生一个个 task。每个 task 对应一个具体的工作单元,比如对一个样本跑一次比对。执行这些 task 的「引擎」叫 executor。
Nextflow 默认用 local executor,也就是在本机跑。如果你想上集群,就配置 executor 为 sge、lsf、slurm 或 awsbatch。executor 负责把 task 提交到集群,监控状态,收集结果。
任务和进程的关系,我用一张图来总结:
看到这里你应该明白了:我们配置的 cpus、memory 都是对 task 而言的,不是对整个进程。同一个进程的不同 task 可以跑在不同集群节点上,各自分配独立资源。
我曾经踩过的坑:在 process 级别设置 cpus 4,以为每个 task 都用 4 核,结果只用了 1 核——因为没理解 task 的并行度是靠 queueSize 和 each 控制的。记住:cpus 给的是单个 task 的核数,不是总并发数。
工作目录与临时文件
每个 task 运行时,Nextflow 会为它创建一个独立的工作目录。默认是 work/,里面按哈希值命名子目录。你看:
work/
├── 8a/6f1e.../
│ ├── input.fastq → 符号链接
│ ├── output.bam
│ ├── .command.sh
│ ├── .command.out
│ ├── .command.err
│ └── .command.begin / .exitcode
├── 3c/2d9a.../
└── ...
这些隐藏文件记录了脚本内容、标准输出、退出码等信息。你如果中途想查某个 task 到底跑了什么命令,去看看 .command.sh 就知道了。
临时文件怎么清理?Nextflow 不会自动删,所以长时间跑下来 work 目录可能占几百 GB。我一般用 nextflow clean -f 来清理已完成的任务目录。注意 -f 会强制删除,别在跑的时候用。
# 只删除已成功完成的任务目录
nextflow clean -f
# 保留最近的 N 次运行
nextflow clean -n 5
另外,scratch 指令可以让你把 task 工作目录放到高性能临时存储上(比如集群的 $TMPDIR)。我建议在 I/O 密集型流程里开启 scratch true,能明显减少 NFS 负载。
process hasLargeFiles {
scratch true
input:
path bigfile
script:
"""
# 这里 bigfile 已经自动复制到临时目录
mytool $bigfile > result
"""
}
嗯,这里要注意:scratch 会复制输入文件,完成后又把输出文件复制回来。如果输入文件特别大(比如几十 GB),反而更慢。所以要根据场景取舍。
总结一下本章要点:
- 进程是模板,task 是真正执行的工作单元。
- input/output 定义数据流,script 写具体命令。
- executor 负责调度 task,每个 task 有独立的工作目录。
- 工作目录里藏了所有命令行和历史,学会用
.command.sh排查问题。 - 临时文件清理和 scratch 选项是生产环境必备技能。