Nextflow 的三板斧:进程、通道、工作流与配置文件

说实话,刚接触 Nextflow 的时候,我也被那一堆概念搞得有点懵。什么进程啊、通道啊,听着跟操作系统似的。但用久了你会发现,其实核心就四个东西:进程(process)、通道(channel)、工作流(workflow),外加一个配置文件(配置文件)。

我个人习惯把这四个东西比作一条流水线:

  • 进程就是流水线上的工人,每个工人只干一件事。
  • 通道是传送带,把半成品从一个工位送到下一个。
  • 工作流是车间主任,决定谁先干、谁后干、干完了交给谁。
  • 配置文件是车间管理手册,记着所有工位的参数和设置。

嗯,这么一想是不是就清晰多了?下面我们一个一个说。

进程(process)—— 干活的基本单元

进程是 Nextflow 里最小的执行单位。你想想看,每个进程就做一件事:接收输入、执行命令、产生输出。我在做空间转录组质控时,最常用的进程就是跑 FastQC、跑 STARsolo、跑 EmptyDrops 这些。每个进程都是一个独立的“黑盒子”。

来,看一个最简单的进程定义:

process sayHello {
    input:
    path x

    script:
    """
    echo "Hello, $x!"
    """
}

这里有几个关键点:

  • input 声明这个进程要吃什么。上面这个进程吃一个文件路径。
  • script 里写实际要运行的命令。支持 Shell、Python、R 等。
  • 进程名是驼峰式命名,我习惯用动词开头,比如 runFastQC、mergeReports。
我的经验:进程里的 script 块尽量保持简单。我在项目中遇到过把几百行 Python 直接塞进进程的情况,调试起来简直灾难。最好只写核心命令,复杂逻辑单独写成脚本文件,用 path 传进去。

通道(channel)—— 数据的“传送带”

通道是 Nextflow 里最让我觉得巧妙的设计。它本质上是一个异步的、先进先出的数据队列。进程从通道里读数据,处理完再往通道里写数据。

两种最常用的通道创建方式:

// 从列表创建通道
ch_fastq = Channel.fromPath("/data/samples/*.fastq.gz")

// 从值创建通道
ch_params = Channel.value(params.outdir)

为什么会这样设计?因为 Nextflow 里的进程是并行执行的。你用通道把数据喂进去,Nextflow 会自动决定怎么分片、怎么调度。我在处理几十个空间切片时,就是一个通道把所有 FASTQ 路径传进去,Nextflow 自动并行跑,省心得很。

通道类型 创建方法 典型用途
值通道 Channel.value() 传递一个固定的配置或参数
队列通道 Channel.fromPath() 传递文件列表,自动并行分发
绑定通道 channel.bind() 手动向通道发送数据
注意:通道只能被消费一次。我以前犯过这个错:把一个通道传给了两个进程,结果第二个进程永远拿不到数据。解决方案是用 .buffer() 或者 .into() 来复制通道。

工作流(workflow)—— 把一切串起来

工作流就是你告诉 Nextflow “先跑 A,再跑 B,最后跑 C”。在 Nextflow 里,进程调用的顺序由数据依赖决定,不是靠你写顺序。

举个例子:

workflow {
    // 读取输入
    input_ch = Channel.fromPath(params.fastq)

    // 第一步:质控
    fastqc_ch = runFastQC(input_ch)

    // 第二步:比对
    bam_ch = runSTARsolo(fastqc_ch)

    // 第三步:计数
    count_ch = runCellRanger(bam_ch)
}

你看,我并没有写“先执行 runFastQC,再执行 runSTARsolo”。Nextflow 自己会分析:runSTARsolo 等着 fastqc_ch 的输出,所以它必须等 runFastQC 跑完才会启动。

我个人很喜欢这种隐式的依赖管理。它让代码看起来更干净,也更容易调整流程顺序。你想想看,要是我突然想在比对之前加一步修剪,只需要在中间插一个进程,改改通道名就行。

配置文件(nextflow.config)—— 别把参数写死在代码里

我曾经犯过一个错:把所有参数都写在工作流脚本里。结果每次换数据集,或者要在不同集群上跑,就得改代码。后来我学乖了,用配置文件把参数抽出来。

一个典型的配置文件长这样:

// nextflow.config
params {
    outdir = "./results"
    fastq_dir = "/data/samples"
    genome = "/ref/hg38.fa"
    threads = 4
}

process {
    executor = 'slurm'
    queue = 'normal'
    memory = '8 GB'
    time = '2h'
}

在工作流里用 params.outdir、params.fastq_dir 来引用这些参数。你想换参数?改配置文件就行,代码不用动。

配置文件的加载顺序也很重要:

  1. 项目根目录下的 nextflow.config
  2. 用户家目录下的 ~/.nextflow/config
  3. 命令行用 -c 指定的配置文件

后面加载的会覆盖前面的。我一般在项目里放一个默认配置,在命令行里用 -c my_slurm.config 覆盖执行器设置。

到这里,四个核心概念就讲完了。让我用一张图总结一下它们的关系:

Nextflow 核心概念关系图 输入数据 (FASTQ / 切片图像) 通道(channel) 异步数据传送带 Process A Process B Process C 进程(独立执行单元) 工作流(workflow) 编排执行顺序 管理依赖关系 自动并行调度 数据流追踪 错误重试 配置文件(.config) 参数 / 执行器 / 资源设置 说明:输入数据通过通道进入进程组,工作流编排调度,配置文件提供全局参数。

这张图里,数据从左往右流。输入数据被包装成通道,通道把数据分发给各个进程。工作流在背后管理依赖关系,确保进程按正确顺序执行。配置文件则为整个过程提供全局参数和执行环境配置。

这四个概念搞清楚了,Nextflow 的基本框架就通了。后面的章节我们会在这个基础上,一步步搭建空间质控的实际流程。