目录
扩展与并行处理
多线程Step
简述Multi-threaded Step
线程安全的Step
并行Step
远程分块
分区
健壮的Job
重复执行
重试
扩展与并行处理
多线程Step
简述Multi-threaded Step
默认情况下,Job执行时使用单个线程完成;可以通过配置Step时,通过属性task-executor,将单线程Step配置为多线程Step;与此同时,框架还提供了对线程池的支持,所以可以通过属性throttle-limit来指定最大使用线程池的数据。XML和Java配置如下:
<step id="loading">
<tasklet task-executor="taskExecutor" throttle-limit="20">...</tasklet>
</step>
@Bean
public TaskExecutor taskExecutor() {
return new SimpleAsyncTaskExecutor("spring_batch");
}
@Bean
public Step sampleStep(TaskExecutor taskExecutor) {
return this.stepBuilderFactory.get("sampleStep")
.<String, String>chunk(10)
.reader(itemReader())
.writer(itemWriter())
.taskExecutor(taskExecutor)
.throttleLimit(20)
.build();
}
在Step
一些常见的批处理用例中,使用多线程实现存在一些实际限制。Step
的许多参与者(ItemReader和ItemWriter)都是有状态的。如果状态不是按线程隔离的,则这些组件不能在多线程中使用Step
。特别是,Spring Batch的大多数现成的读写器都不是为多线程使用而设计的。但是,可以与无状态或线程安全的读取器和写入器一起使用。
线程安全:如果代码所在进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码,如果每次运行结果和单线程运行结果一致,而且其他变量的值也符合预期,那么这段代码就是线程安全的。在Java领域线程安全问题通常是全局变量或者静态变量引起的,如果每个线程只是对全局变量或者静态变量做只读操作,通常是线程安全的;如果有多个线程同时执行写操作,则需要考虑线程同步,否则会影响线程安全。
线程安全的Step
Spring Batch提供了一些ItemWriter和ItemReader的实现。通常,他们在Javadoc中会说它们是否线程安全,或者在并发环境中必须做些什么来避免问题。如果Javadoc中没有任何信息,可以检查实现,看看是否有任何状态。如果读取器不是线程安全的,可以使用提供的SynchronizedItemStreamReader来装饰它,或者在业务中自己实现ItemReader并使用synchronized关键字同步对read()的调用。如果,业务中只要处理和写入是块中开销最大的部分,那么读操作的同步等待不会造成系统性能瓶颈,而且整个Step可能比单线程运行的快。
在org.springfarmwork.batch.item.support包中,有两个装饰器类:SynchronizedItemStreamReader和SynchronizedItemStreamWriter。在SynchronizedItemStreamReader的javadoc中有么着一句话需要注意:如果使用呢了该装饰器,那么因为某个Item有问题,而重启将成为不可能。在SynchronizedItemStreamWriter的javadoc写到应该注意同步写操作可能会带来一些性能下降,所以应该明智地使用这个装饰器,并且只在必要时使用。
如果要在多线程情况下实现重启的话,在processor或者reader、writer的其中一步中自己记录读取状态标记;在重启时,根据自己业务中的记录去跳过已经成功处理的数据。
【注意:这里笔者没有实践但是理论上可行】
并行Step
在某些步骤可以并行执行的时候,可以将这些步骤设置为并行流,Spring Batch 框架提供了split元素来执行并行作业的能力。在Spring Batch学习与实践(一)中已经简单介绍了并行Step的配置。在这里讲详细介绍一下,如下面代码段所示,假设现在有三个Step,其中(Step1,Step2)可以和Step3并行执行,而Step2需要在Step1执行完后执行。首先需要注册两个Flow,flow1()用于执行Step1,Step2,flow2()用于执行Step执行Step3,然后通过split构造并行流,也就是代码块中祖册的splitFlow。
@Bean
public Job job() {
return jobBuilderFactory.get("job")
.start(splitFlow())
.next(step4())
.build() //builds FlowJobBuilder instance
.build(); //builds Job instance
}
@Bean
public Flow splitFlow() {
return new FlowBuilder<SimpleFlow>("splitFlow")
.split(taskExecutor())
.add(flow1(), flow2())
.build();
}
@Bean
public Flow flow1() {
return new FlowBuilder<SimpleFlow>("flow1")
.start(step1())
.next(step2())
.build();
}
@Bean
public Flow flow2() {
return new FlowBuilder<SimpleFlow>("flow2")
.start(step3())
.build();
}
@Bean
public TaskExecutor taskExecutor() {
return new SimpleAsyncTaskExecutor("spring_batch");
}
Flow接口很简单,只有几个方法;他有两个实现类:JsrFlow, SimpleFlow;在上面的代码示例中,我们使用的是SimpleFlow。
远程分块
远程Step技术本质上是将对Item读、写的处理逻辑进行分离;通常情况下读的逻辑放在一个节点进行操作,将写操作分发到另外的节点执行。如果读操作(Manager节点)不是性能瓶颈,而写操作(Worker)是性能瓶颈时,通过远程分块操作往往是一种比较有效的实践方式。在远程分块中,Step
处理过程分为多个进程,并通过某种中间件相互通信。
如下图所示,远程分块采用典型的并行模式Manager-Worker,Manager负责读取Item(往往只包括ItemReader)并分块后通过消息中间件(如:JSM)传递到Worker中,Worker(往往由ItemProcess和ItemWriter构成)。需要注意两个核心类:
1. ChunkProvider:根据给定的ItemReader操作产生批量的Chunk操作;
2. ChunkProcessor:负责获取ChunkProvider产生的Chunk操作,执行具体的写逻辑。
Master-Worker模式是常用的并行设计模式。核心思想是,系统由两个角色组成,Master和Worker,Master负责接收和分配任务,Worker负责处理子任务。任务处理过程中,Master还负责监督任务进展和Worker的健康状态;Master将接收Client提交的任务,并将任务的进展汇总反馈给Client。
由于笔者没有实践过该功能,仅有理论。所以更详细的示例,可以参考官方文档。在 Spring Batch中对远程Step没有默认地实现,但Spring中提供了另外一个项目,Spring Batch Integration项目,将Spring Batch框架和Spring Integration做了集成,可以通过Spring Integration提供的远程能力实现远程Step。
分区
Spring Batch还提供了一个SPI,用于对一个Step执行进行分区并远程执行它。在这种情况下,远程参与者是可以轻松配置并用于本地处理的Step实例。如下图所示,Job在左侧作为一系列Step实例运行,其中一个Step实例标记为Manager。这幅图中的Worker都是一个Step的相同实例,它实际上可以取代Manager,从而对该Job产生相同的结果。Worker通常是远程服务,但也可以是本地执行线程。在此模式中,由Manager发送给Worker的消息不需要是持久的或有保证的传递。JobRepository中的Spring Batch元数据确保每个Worker执行一次,而且对于每个Job执行只执行一次。
Spring Batch中的SPI由一个特殊的Step实现(称为PartitionStep)和两个需要为特定环境实现的策略接口组成。策略接口是PartitionHandler和StepExecutionSplitter,它们的角色如下面的序列图所示:
需要重点介绍一下,以下两个组件:
1. PartitionHandler是了解远程处理或网格环境结构的组件。它能够将Step执行请求发送到远程Step实例,以某种特定于的格式包装,比如DTO。它不需要知道如何分割输入数据或如何聚合多个Step执行的结果。PartitionHandler接口可以有针对各种fabric类型的专门实现,包括简单的RMI远程处理、EJB远程处理、自定义web服务、JMS、Java空间、共享内存网格(如Terracotta或Coherence)和网格执行网格(如GridGain)。Spring Batch不包含任何专有网格或远程处理结构的实现。然而,Spring Batch确实提供了一个有用的PartitionHandler实现——TaskExecutorPartitionHandler,它使用Spring的TaskExecutor策略,在单独的执行线程中本地执行Step实例。
2. Partitioner有一个更简单的职责:仅为新Step的执行生成作为输入参数的执行上下文(不需要担心重新启动)。
由于笔者没有实践过该功能,仅有理论。所以更详细的示例,可以参考官方文档。
健壮的Job
重复执行
参考文档:https://docs.spring.io/spring-batch/docs/4.3.x/reference/html/index-single.html#repeat
重试
参考文档:https://docs.spring.io/spring-batch/docs/4.3.x/reference/html/index-single.html#retry