7. 缩放和 Parallel 处理

使用单线程,单个 process 作业可以解决许多批处理问题,因此在考虑更复杂的 implementation 之前,正确检查是否满足您的需求始终是一个很好的选择。测量现实 job 的 performance 并查看最简单的 implementation 是否满足您的需求:即使使用标准硬件,您也可以在一分钟内读取和写入几百兆字节的文件。

当您准备开始实现带有 parallel 处理的 job 时,Spring Batch 提供了一系列选项,本章将介绍这些选项,尽管其他地方也有一些 features。在高级别,有两种 parallel 处理模式:single process,multi-threaded;和 multi-process。这些 break 也分为几类,如下:

  • Multi-threaded Step(单个 process)

  • Parallel Steps(单个 process)

  • Remote Step 的分块(多 process)

  • 分区 Step(单个或多个 process)

接下来,我们首先查看 single-process 选项,然后查看 multi-process 选项。

7.1 Multi-threaded Step

启动 parallel 处理的最简单方法是在 Step configuration e.g 中添加TaskExecutor。作为tasklet的属性:

<step id="loading">
    <tasklet task-executor="taskExecutor">...</tasklet>
</step>

在此 example 中,taskExecutor 是另一个 bean 定义的 reference,实现了TaskExecutor接口。 TaskExecutor是标准的 Spring 接口,因此请参阅 Spring 用户指南以获取可用_implement 的详细信息。最简单的 multi-threaded TaskExecutorSimpleAsyncTaskExecutor

上述 configuration 的结果将是 Step 通过在单独的执行线程中读取,处理和写入每个块(每个提交间隔)来执行。请注意,这意味着要处理的项目没有固定的 order,并且块可能包含与 single-threaded case 相比较的项目。除了任务执行程序提供的任何限制(e.g. 如果它由线程池支持),tasklet configuration 中有一个限制,默认为 4.您可能需要增加此限制以确保线程池是充分利用,e.g.

<step id="loading"> <tasklet
    task-executor="taskExecutor"
    throttle-limit="20">...</tasklet>
</step>

另请注意,step 中使用的任何池化资源可能会限制并发性,例如DataSource。确保使用这些资源中的池至少与 step 中所需的并发线程数一样大。

对于某些 common Batch 用例,使用 multi-threaded 步骤存在一些实际限制。 Step(e.g .readers 和 writers)中的许多参与者都是有状态的,如果 state 没有被线程隔离,那么这些组件在 multi-threaded Step 中不可用。特别是 Spring Batch 中的大多数 off-the-shelf readers 和 writers 都不是为 multi-threaded 使用而设计的。但是,可以使用 stateless 或线程安全的 readers 和 writers,并且 Spring Batch Samples 中有一个 sample(parallelJob),它显示使用 process 指示符(请参阅第 6.12 节,“防止 State 持久性”)来跟踪已经存在的项目在数据库输入 table 处理。

Spring Batch 提供ItemWriterItemReader的一些 implementations。通常他们在 Javadocs 中说是否是线程安全的,或者你需要做些什么来避免并发环境中的问题。如果 Javadocs 中没有信息,您可以检查 implementation 以查看是否有任何 state。如果 reader 不是线程安全的,那么在您自己的同步委托器中使用它仍然是有效的。您可以将调用同步到read()和 long 作为 long,因为处理和写入是块中最昂贵的部分,您的 step 可能仍然比单线程 configuration 快得多。

7.2 Parallel Steps

作为 long,需要并行化的 application 逻辑可以分成不同的职责,并分配给各个步骤,然后可以在单个 process 中并行化。 Parallel Step 执行很容易配置和使用,例如,与并行执行步骤(step1,step2),您可以配置如下的流程:

<job id="job1">
    <split id="split1" task-executor="taskExecutor" next="step4">
        <flow>
            <step id="step1" parent="s1" next="step2"/>
            <step id="step2" parent="s2"/>
        </flow>
        <flow>
            <step id="step3" parent="s3"/>
        </flow>
    </split>
    <step id="step4" parent="s4"/>
</job>

<beans:bean id="taskExecutor" class="org.spr...SimpleAsyncTaskExecutor"/>

可配置的“task-executor”属性用于指定应该使用哪个 TaskExecutor implementation 来执行各个流。默认值为SyncTaskExecutor,但需要异步的 TaskExecutor 来运行 parallel 中的步骤。请注意,job 将确保分割中的每个流在聚合退出状态和转换之前完成。

有关详细信息,请参阅第 5.3.5 节,“分流”部分。

7.3 Remote Chunking

在 Remote Chunking 中,Step 处理分为多个进程,通过一些中间件相互通信。这是一个行为中的 pattern 的图片:

Master component 是单个 process,Slaves 是多个 remote 进程。显然,如果 Master 不是瓶颈,这种 pattern 效果最好,因此处理必须比读取项目更昂贵(实际情况通常如此)。

Master 只是 Spring Batch Step的 implementation,ItemWriter 替换为通用 version,它知道如何将消息块作为消息发送到中间件。 Slaves 是标准 listeners,无论使用什么中间件(e.g. 使用 JMS,它们都是MesssageListeners),它们的作用是使用标准ItemWriterItemProcessorItemWriter通过ChunkProcessor接口处理项目块。使用此 pattern 的一个优点是 reader,processor 和 writer 组件是 off-the-shelf(与用于本地执行 step 的组件相同)。这些项目是动态划分的,工作是通过中间件共享的,所以如果 listeners 都是渴望的消费者,那么负载平衡就是自动的。

中间件必须经久耐用,保证交付,并为每条消息提供单个 consumer。 JMS 显然是候选者,但网格计算和共享 memory 产品空间(e.g. Java Spaces)中存在其他选项。

7.4 分区

Spring Batch 还提供了一个 SPI,用于分区 Step 执行并远程执行。在这种情况下,remote 参与者只是 Step 实例,可以很容易地配置并用于本地处理。这是一个行为中的 pattern 的图片:

Job 在左侧执行为一系列步骤,其中一个步骤标记为 Master。这张照片中的 Slaves 都是 Step 的相同实例,实际上它可以取代 Master,从而导致 Job 的结果相同。 Slaves 通常是 remote 服务,但也可能是本地执行线程。 Master 在此 pattern 中发送给 Slaves 的消息不需要是持久的,或者保证交付:JobRepository中的 Spring Batch meta-data 将确保每个 Slave 执行一次,每次执行 Job 只执行一次。

Spring Batch 中的 SPI 由 Step(PartitionStep)的特殊 implementation 和两个需要针对特定环境实现的策略接口组成。策略接口是PartitionHandlerStepExecutionSplitter,它们的作用显示在下面的序列图中:

在这种情况下右侧的 Step 是“remote”Slave,因此可能有许多 objects 和/或进程正在扮演这个角色,并且 PartitionStep 显示为驱动执行。 PartitionStep configuration 看起来像这样:

<step id="step1.master">
    <partition step="step1" partitioner="partitioner">
        <handler grid-size="10" task-executor="taskExecutor"/>
    </partition>
</step>

与 multi-threaded step 的 throttle-limit 属性类似,grid-size 属性可防止任务执行程序被单个 step 的请求所饱和。

有一个简单的 example 可以在单元测试套件中复制和扩展 Spring Batch Samples(参见*PartitionJob.xml configuration)。

Spring Batch 为名为“step1:partition0”等的分区创建 step 执行,因此很多人更喜欢调用 master step“step1:master”来保持一致性。使用 Spring 3.0,您可以使用 step 的别名(指定name属性而不是id)来执行此操作。

7.4.1 PartitionHandler

PartitionHandler是知道远程或网格环境结构的 component。它能够向 remote 步骤发送StepExecution请求,以一些 fabric-specific 格式包装,就像 DTO 一样。它不必知道如何分割输入数据,或者如何聚合多个 Step 执行的结果。一般来说,它可能也不需要了解弹性或故障转移,因为在许多情况下这些都是结构的 features,无论如何 Spring Batch 始终提供独立于结构的可重启性:失败的 Job 总是可以重新启动而只有失败步骤将是 re-executed。

The PartitionHandler接口可以为各种结构类型提供专门的 implementation:e.g. 简单的 RMI 远程处理,EJB 远程处理,自定义 web service,JMS,Java Spaces,共享 memory 网格(如 Terracotta 或 Coherence),网格执行结构(如 GridGain)。 Spring Batch 不包含任何专有网格或远程结构的 implementations。

然而,Spring Batch 提供了一个有用的PartitionHandler实现,它使用 Spring 中的TaskExecutor策略在不同的执行线程中本地执行 Steps。 implementation 称为TaskExecutorPartitionHandler,它是使用上述 XML 命名空间配置的 step 的默认值。它也可以像这样明确配置:

<step id="step1.master">
    <partition step="step1" handler="handler"/>
</step>

<bean class="org.spr...TaskExecutorPartitionHandler">
    <property name="taskExecutor" ref="taskExecutor"/>
    <property name="step" ref="step1" />
    <property name="gridSize" value="10" />
</bean>

gridSize确定要创建的单独 step 执行的数量,因此它可以与TaskExecutor中的线程池的大小匹配,或者可以将其设置为大于可用线程的数量,在这种情况下块的工作量较小。

TaskExecutorPartitionHandler对于 IO 密集型步骤非常有用,例如复制 files 的大 numbers 或将文件系统复制到内容管理系统。它还可以用于 remote 执行,方法是提供 Step implementation,它是 remote 调用的代理(e.g. 使用 Spring Remoting)。

7.4.2 分区员

分区程序有一个更简单的职责:仅为新的 step 执行生成执行上下文作为输入参数(无需担心重新启动)。它有一个方法:

public interface Partitioner {
    Map<String, ExecutionContext> partition(int gridSize);
}

此方法的 return value 将每个 step 执行(String)的唯一 name 与ExecutionContext形式的输入参数相关联。这些名称稍后会在批处理元数据中显示为分区StepExecutions中的 step name。 ExecutionContext只是一包 name-value 对,因此它可能包含一系列主键,line numbers 或输入文件的位置。然后 remote Step通常使用#{...}占位符(step 范围内的_binding)绑定到 context 输入,如下一节所示。

step 执行的名称(由Partitioner返回的Map中的键)在 Job 的 step 执行中必须是唯一的,但没有任何其他特定要求。执行此操作以及使名称对用户有意义的最简单方法是使用前缀后缀命名约定,其中前缀是正在执行的 step 的 name(在Job中它本身是唯一的),以及后缀只是一个柜台。 framework 中有SimplePartitioner使用此约定。

可选接口PartitioneNameProvider可用于提供与分区本身分开的分区名称。如果Partitioner实现此接口,则在重新启动时仅查询名称。如果分区很昂贵,这可能是一个有用的优化。显然PartitioneNameProvider提供的名称必须匹配Partitioner提供的名称。

7.4.3 将输入数据绑定到步骤

对于 PartitionHandler 执行的具有相同 configuration 的步骤以及它们的输入参数在运行时从 ExecutionContext 进行绑定非常有效。使用 Spring Batch 的 StepScope feature 很容易做到(在晚 Binding部分有更详细的介绍)。例如,如果Partitioner创建带有 key fileName属性的ExecutionContext实例,指向每个 step 调用的不同文件(或目录),则Partitioner输出可能如下所示:

表格 1_.Example step execution name to execution context 由 Partitioner 提供目标处理

步骤执行 Name(key)ExecutionContext(value)
filecopy:partition0fileName=/home/data/one
filecopy:partition1fileName=/home/data/two
filecopy:partition2fileName=/home/data/three

然后,文件 name 可以使用 late binding 绑定到 step 到执行 context:

<bean id="itemReader" scope="step"
      class="org.spr...MultiResourceItemReader">
    <property name="resource" value="#{stepExecutionContext[fileName]}/*"/>
</bean>
Updated at: 9 months ago
使 ItemWriter 可重新启动Table of content8. 重复