# Spring 批处理集成

# Spring 批处理集成

XMLJavaBoth

# Spring 批处理集成介绍

Spring 批处理的许多用户可能会遇到不在 Spring 批处理范围内的需求,但这些需求可以通过使用 Spring 集成来高效而简洁地实现。相反, Spring 集成用户可能会遇到 Spring 批处理需求,并且需要一种有效地集成这两个框架的方法。在这种情况下,出现了几种模式和用例, Spring 批处理集成解决了这些需求。

Spring 批处理和 Spring 集成之间的界限并不总是清晰的,但有两条建议可以提供帮助:考虑粒度,并应用公共模式。这些常见模式中的一些在本参考手册一节中进行了描述。

将消息传递添加到批处理过程中,可以实现操作的自动化,还可以分离关键关注事项并制定策略。例如,消息可能会触发作业执行,然后消息的发送可以通过多种方式公开。或者,当作业完成或失败时,该事件可能会触发要发送的消息,而这些消息的消费者可能有与应用程序本身无关的操作问题。消息传递也可以嵌入到作业中(例如,通过通道读取或写入用于处理的项)。远程分区和远程分块提供了在多个工作人员上分配工作负载的方法。

本节涵盖以下关键概念:

# 名称空间支持

Spring 自批处理集成 1.3 以来,添加了专用的 XML 命名空间支持,目的是提供更简单的配置体验。为了激活命名空间,请将以下命名空间声明添加到 Spring XML 应用程序上下文文件中:

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:batch-int="http://www.springframework.org/schema/batch-integration"
  xsi:schemaLocation="
    http://www.springframework.org/schema/batch-integration
    https://www.springframework.org/schema/batch-integration/spring-batch-integration.xsd">

    ...

</beans>

用于 Spring 批处理集成的完全配置的 Spring XML 应用程序上下文文件可能如下所示:

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:int="http://www.springframework.org/schema/integration"
  xmlns:batch="http://www.springframework.org/schema/batch"
  xmlns:batch-int="http://www.springframework.org/schema/batch-integration"
  xsi:schemaLocation="
    http://www.springframework.org/schema/batch-integration
    https://www.springframework.org/schema/batch-integration/spring-batch-integration.xsd
    http://www.springframework.org/schema/batch
    https://www.springframework.org/schema/batch/spring-batch.xsd
    http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/integration
    https://www.springframework.org/schema/integration/spring-integration.xsd">

    ...

</beans>

也允许将版本号附加到引用的 XSD 文件中,但是,由于无版本声明总是使用最新的模式,因此我们通常不建议将版本号附加到 XSD 名称中。添加版本号可能会在更新 Spring 批处理集成依赖项时产生问题,因为它们可能需要 XML 模式的最新版本。

# 通过消息启动批处理作业

当通过使用核心 Spring 批处理 API 启动批处理作业时,你基本上有两个选项:

  • 在命令行中,使用CommandLineJobRunner

  • 在编程上,使用JobOperator.start()JobLauncher.run()

例如,当通过使用 shell 脚本调用批处理作业时,你可能希望使用CommandLineJobRunner。或者,你可以直接使用JobOperator(例如,当使用 Spring 批处理作为 Web 应用程序的一部分时)。然而,更复杂的用例呢?也许你需要轮询远程 FTP 服务器来检索批处理作业的数据,或者你的应用程序必须同时支持多个不同的数据源。例如,你不仅可以从 Web 接收数据文件,还可以从 FTP 和其他来源接收数据文件。在调用 Spring 批处理之前,可能需要对输入文件进行额外的转换。

因此,使用 Spring 集成及其众多适配器来执行批处理作业将会强大得多。例如,你可以使用文件入站通道适配器来监视文件系统中的一个目录,并在输入文件到达时立即启动批处理作业。此外,你还可以创建 Spring 集成流,这些集成流使用多个不同的适配器,仅使用配置就可以轻松地从多个源同时为批处理作业摄取数据。使用 Spring 集成实现所有这些场景是很容易的,因为它允许对JobLauncher进行解耦、事件驱动的执行。

Spring 批处理集成提供了JobLaunchingMessageHandler类,你可以使用它来启动批处理作业。JobLaunchingMessageHandler的输入由 Spring 集成消息提供,该消息的有效负载类型为JobLaunchRequest。这个类是围绕需要启动的Job和启动批处理作业所必需的JobParameters的包装器。

下面的图像演示了典型的 Spring 集成消息流,以便启动批处理作业。Enterprise 集成模式网站 (opens new window)提供了消息传递图标及其描述的完整概述。

启动批处理作业

图 1。启动批处理作业

# 将文件转换为 joblaunchrequest
package io.spring.sbi;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.integration.launch.JobLaunchRequest;
import org.springframework.integration.annotation.Transformer;
import org.springframework.messaging.Message;

import java.io.File;

public class FileMessageToJobRequest {
    private Job job;
    private String fileParameterName;

    public void setFileParameterName(String fileParameterName) {
        this.fileParameterName = fileParameterName;
    }

    public void setJob(Job job) {
        this.job = job;
    }

    @Transformer
    public JobLaunchRequest toRequest(Message<File> message) {
        JobParametersBuilder jobParametersBuilder =
            new JobParametersBuilder();

        jobParametersBuilder.addString(fileParameterName,
            message.getPayload().getAbsolutePath());

        return new JobLaunchRequest(job, jobParametersBuilder.toJobParameters());
    }
}
# theJobExecution响应

当执行批处理作业时,将返回一个JobExecution实例。此实例可用于确定执行的状态。如果JobExecution能够成功创建,则无论实际执行是否成功,它总是被返回。

如何返回JobExecution实例的确切行为取决于所提供的TaskExecutor。如果使用synchronous(单线程)TaskExecutor实现,则只返回JobExecution响应after作业完成。当使用asynchronous``TaskExecutor时,将立即返回JobExecution实例。然后,用户可以使用JobExecutionid实例(带有JobExecution.getJobId()),并使用JobExplorer查询JobRepository中的作业更新状态。有关更多信息,请参阅关于查询存储库的 Spring 批参考文档。

# Spring 批处理集成配置

考虑这样一种情况:需要创建一个文件inbound-channel-adapter来监听所提供的目录中的 CSV 文件,将它们交给转换器(FileMessageToJobRequest),通过工作启动网关启动作业,然后用logging-channel-adapter记录JobExecution的输出。

下面的示例展示了如何在 XML 中配置这种常见的情况:

XML 配置

<int:channel id="inboundFileChannel"/>
<int:channel id="outboundJobRequestChannel"/>
<int:channel id="jobLaunchReplyChannel"/>

<int-file:inbound-channel-adapter id="filePoller"
    channel="inboundFileChannel"
    directory="file:/tmp/myfiles/"
    filename-pattern="*.csv">
  <int:poller fixed-rate="1000"/>
</int-file:inbound-channel-adapter>

<int:transformer input-channel="inboundFileChannel"
    output-channel="outboundJobRequestChannel">
  <bean class="io.spring.sbi.FileMessageToJobRequest">
    <property name="job" ref="personJob"/>
    <property name="fileParameterName" value="input.file.name"/>
  </bean>
</int:transformer>

<batch-int:job-launching-gateway request-channel="outboundJobRequestChannel"
    reply-channel="jobLaunchReplyChannel"/>

<int:logging-channel-adapter channel="jobLaunchReplyChannel"/>

下面的示例展示了如何在 Java 中配置这种常见情况:

Java 配置

@Bean
public FileMessageToJobRequest fileMessageToJobRequest() {
    FileMessageToJobRequest fileMessageToJobRequest = new FileMessageToJobRequest();
    fileMessageToJobRequest.setFileParameterName("input.file.name");
    fileMessageToJobRequest.setJob(personJob());
    return fileMessageToJobRequest;
}

@Bean
public JobLaunchingGateway jobLaunchingGateway() {
    SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
    simpleJobLauncher.setJobRepository(jobRepository);
    simpleJobLauncher.setTaskExecutor(new SyncTaskExecutor());
    JobLaunchingGateway jobLaunchingGateway = new JobLaunchingGateway(simpleJobLauncher);

    return jobLaunchingGateway;
}

@Bean
public IntegrationFlow integrationFlow(JobLaunchingGateway jobLaunchingGateway) {
    return IntegrationFlows.from(Files.inboundAdapter(new File("/tmp/myfiles")).
                    filter(new SimplePatternFileListFilter("*.csv")),
            c -> c.poller(Pollers.fixedRate(1000).maxMessagesPerPoll(1))).
            transform(fileMessageToJobRequest()).
            handle(jobLaunchingGateway).
            log(LoggingHandler.Level.WARN, "headers.id + ': ' + payload").
            get();
}
# 示例 itemreader 配置

现在我们正在轮询文件和启动作业,我们需要配置我们的 Spring 批处理ItemReader(例如),以使用在名为“input.file.name”的作业参数所定义的位置找到的文件,如下面的 Bean 配置所示:

下面的 XML 示例展示了必要的配置 Bean:

XML 配置

<bean id="itemReader" class="org.springframework.batch.item.file.FlatFileItemReader"
    scope="step">
  <property name="resource" value="file://#{jobParameters['input.file.name']}"/>
    ...
</bean>

下面的 Java 示例展示了必要的配置 Bean:

Java 配置

@Bean
@StepScope
public ItemReader sampleReader(@Value("#{jobParameters[input.file.name]}") String resource) {
...
    FlatFileItemReader flatFileItemReader = new FlatFileItemReader();
    flatFileItemReader.setResource(new FileSystemResource(resource));
...
    return flatFileItemReader;
}

在前面的示例中,主要的关注点是注入#{jobParameters['input.file.name']}的值作为资源属性值,并将ItemReader Bean 设置为具有步骤范围。将 Bean 设置为具有步骤作用域利用了后期绑定支持,这允许访问jobParameters变量。

# 作业启动网关的可用属性

作业启动网关具有以下属性,你可以设置这些属性来控制作业:

  • id:标识底层的 Spring Bean 定义,它是以下两种定义之一的实例:

    • EventDrivenConsumer

    • PollingConsumer(准确的实现取决于组件的输入通道是SubscribableChannel还是PollableChannel。)

  • auto-startup:布尔标志,指示端点在启动时应自动启动。默认值为true

  • request-channel:此端点的输入MessageChannel

  • reply-channel:MessageChannel将结果JobExecution的有效载荷发送到该负载。

  • reply-timeout:允许你指定此网关在抛出异常之前等待多长时间(以毫秒为单位)以将答复消息成功发送到答复通道。此属性仅在通道可能阻塞时才应用(例如,当使用当前已满的有界队列通道时)。另外,请记住,当发送到DirectChannel时,调用发生在发送方的线程中。因此,发送操作的失败可能是由更下游的其他组件引起的。reply-timeout属性映射到底层sendTimeout实例的sendTimeout属性。如果没有指定,则属性默认为 <emphasis>-1</emphasis>,这意味着,默认情况下,Gateway无限期地等待。

  • job-launcher:可选。接受自定义JobLauncher Bean 引用。如果没有指定适配器,则重新使用在jobLauncherid下注册的实例。如果不存在缺省实例,则抛出一个异常。

  • order:指定当此端点作为订阅服务器连接到SubscribableChannel时的调用顺序。

# 子元素

Gateway接收来自PollableChannel的消息时,你必须为Poller提供一个全局默认值Poller,或者为Job Launching Gateway提供一个子元素。

下面的示例展示了如何用 XML 提供一个 Poller:

XML 配置

<batch-int:job-launching-gateway request-channel="queueChannel"
    reply-channel="replyChannel" job-launcher="jobLauncher">
  <int:poller fixed-rate="1000">
</batch-int:job-launching-gateway>

下面的示例展示了如何用 Java 提供一个 Poller:

Java 配置

@Bean
@ServiceActivator(inputChannel = "queueChannel", poller = @Poller(fixedRate="1000"))
public JobLaunchingGateway sampleJobLaunchingGateway() {
    JobLaunchingGateway jobLaunchingGateway = new JobLaunchingGateway(jobLauncher());
    jobLaunchingGateway.setOutputChannel(replyChannel());
    return jobLaunchingGateway;
}

# 提供反馈信息

Spring 由于批处理作业可以运行很长时间,因此提供进度信息通常是至关重要的。例如,如果批处理作业的某些部分或所有部分都失败了,利益相关者可能希望得到通知。 Spring 批处理为正在通过以下方式收集的此信息提供支持:

  • 活动轮询

  • 事件驱动侦听器

在异步启动 Spring 批处理作业时(例如,通过使用Job Launching Gateway),将返回一个JobExecution实例。因此,通过使用JobExplorerJobRepository检索JobExecution的更新实例,JobExecution.getJobId()可用于连续轮询状态更新。然而,这被认为是次优的,事件驱动的方法应该是首选的。

因此, Spring 批提供了侦听器,包括三个最常用的侦听器:

  • StepListener

  • ChunkListener

  • JobExecutionListener

在下图所示的示例中, Spring 批处理作业已配置为StepExecutionListener。因此, Spring 集成接收并处理事件之前或之后的任何步骤。例如,接收到的StepExecution可以通过使用Router进行检查。基于该检查的结果,可以发生各种事情(例如将消息路由到邮件出站通道适配器),以便可以基于某些条件发送出电子邮件通知。

处理信息消息

图 2。处理信息消息

下面由两部分组成的示例展示了侦听器如何配置为向Gateway事件发送消息到StepExecution,并将其输出记录到logging-channel-adapter

首先,创建通知集成 bean。

下面的示例展示了如何在 XML 中创建通知集成 bean:

XML 配置

<int:channel id="stepExecutionsChannel"/>

<int:gateway id="notificationExecutionsListener"
    service-interface="org.springframework.batch.core.StepExecutionListener"
    default-request-channel="stepExecutionsChannel"/>

<int:logging-channel-adapter channel="stepExecutionsChannel"/>

下面的示例展示了如何在 Java 中创建通知集成 bean:

Java 配置

@Bean
@ServiceActivator(inputChannel = "stepExecutionsChannel")
public LoggingHandler loggingHandler() {
    LoggingHandler adapter = new LoggingHandler(LoggingHandler.Level.WARN);
    adapter.setLoggerName("TEST_LOGGER");
    adapter.setLogExpressionString("headers.id + ': ' + payload");
    return adapter;
}

@MessagingGateway(name = "notificationExecutionsListener", defaultRequestChannel = "stepExecutionsChannel")
public interface NotificationExecutionListener extends StepExecutionListener {}
你需要将@IntegrationComponentScan注释添加到配置中。

其次,修改工作以添加一个步骤级侦听器。

下面的示例展示了如何在 XML 中添加一个步骤级侦听器:

XML 配置

<job id="importPayments">
    <step id="step1">
        <tasklet ../>
            <chunk ../>
            <listeners>
                <listener ref="notificationExecutionsListener"/>
            </listeners>
        </tasklet>
        ...
    </step>
</job>

下面的示例展示了如何在 Java 中添加一个步骤级侦听器:

Java 配置

public Job importPaymentsJob() {
    return jobBuilderFactory.get("importPayments")
        .start(stepBuilderFactory.get("step1")
                .chunk(200)
                .listener(notificationExecutionsListener())
                ...
}

# 异步处理器

异步处理器帮助你扩展项目的处理。在异步处理器用例中,AsyncItemProcessor充当调度器,为新线程上的项执行ItemProcessor的逻辑。项目完成后,将Future传递给要写入的AsynchItemWriter

因此,你可以通过使用异步项目处理来提高性能,基本上允许你实现*fork-join *场景。AsyncItemWriter收集结果,并在所有结果可用时立即写回块。

下面的示例展示了如何在 XML 中配置AsyncItemProcessor:

XML 配置

<bean id="processor"
    class="org.springframework.batch.integration.async.AsyncItemProcessor">
  <property name="delegate">
    <bean class="your.ItemProcessor"/>
  </property>
  <property name="taskExecutor">
    <bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
  </property>
</bean>

下面的示例展示了如何在 XML 中配置AsyncItemProcessor:

Java 配置

@Bean
public AsyncItemProcessor processor(ItemProcessor itemProcessor, TaskExecutor taskExecutor) {
    AsyncItemProcessor asyncItemProcessor = new AsyncItemProcessor();
    asyncItemProcessor.setTaskExecutor(taskExecutor);
    asyncItemProcessor.setDelegate(itemProcessor);
    return asyncItemProcessor;
}

delegate属性是指你的ItemProcessor Bean,而taskExecutor属性是指你选择的TaskExecutor

下面的示例展示了如何在 XML 中配置AsyncItemWriter:

XML 配置

<bean id="itemWriter"
    class="org.springframework.batch.integration.async.AsyncItemWriter">
  <property name="delegate">
    <bean id="itemWriter" class="your.ItemWriter"/>
  </property>
</bean>

下面的示例展示了如何在 Java 中配置AsyncItemWriter:

Java 配置

@Bean
public AsyncItemWriter writer(ItemWriter itemWriter) {
    AsyncItemWriter asyncItemWriter = new AsyncItemWriter();
    asyncItemWriter.setDelegate(itemWriter);
    return asyncItemWriter;
}

同样,delegate属性实际上是对你的ItemWriter Bean 的引用。

# 外部化批处理过程执行

到目前为止讨论的集成方法建议使用 Spring 集成像外壳一样包装 Spring 批处理的用例。然而, Spring 批处理也可以在内部使用 Spring 集成。 Spring 使用这种方法,批处理用户可以将项目甚至块的处理委托给外部进程。这允许你卸载复杂的处理。 Spring 批处理集成为以下方面提供了专门的支持:

  • 远程分块

  • 远程分区

# 远程分块

远程分块

图 3。远程分块

更进一步,还可以使用ChunkMessageChannelItemWriter(由 Spring Batch Integration 提供)将块处理外部化,它将项发送出去并收集结果。一旦发送, Spring 批处理继续读取和分组项的过程,而无需等待结果。相反,收集结果并将其集成回 Spring 批处理过程是ChunkMessageChannelItemWriter的责任。

通过 Spring 集成,你可以完全控制进程的并发性(例如,通过使用QueueChannel而不是DirectChannel)。此外,通过依赖 Spring Integration 的通道适配器(例如 JMS 和 AMQP)的丰富集合,你可以将批处理作业的块分配给外部系统进行处理。

带有要远程分块的步骤的作业可能具有类似于 XML 中的以下配置:

XML 配置

<job id="personJob">
  <step id="step1">
    <tasklet>
      <chunk reader="itemReader" writer="itemWriter" commit-interval="200"/>
    </tasklet>
    ...
  </step>
</job>

带有要远程分块的步骤的作业可能具有类似于 Java 中的以下配置:

Java 配置

public Job chunkJob() {
     return jobBuilderFactory.get("personJob")
             .start(stepBuilderFactory.get("step1")
                     .<Person, Person>chunk(200)
                     .reader(itemReader())
                     .writer(itemWriter())
                     .build())
             .build();
 }

ItemReader引用指向要用于读取 Manager 上的数据的 Bean。正如上面所描述的,ItemWriter引用指向一个特殊的ItemWriter(称为ChunkMessageChannelItemWriter)。处理器(如果有的话)不在 Manager 配置中,因为它是在 Worker 上配置的。在实现用例时,你应该检查任何附加的组件属性,例如油门限制等。

以下 XML 配置提供了基本的 Manager 设置:

XML 配置

<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
  <property name="brokerURL" value="tcp://localhost:61616"/>
</bean>

<int-jms:outbound-channel-adapter id="jmsRequests" destination-name="requests"/>

<bean id="messagingTemplate"
    class="org.springframework.integration.core.MessagingTemplate">
  <property name="defaultChannel" ref="requests"/>
  <property name="receiveTimeout" value="2000"/>
</bean>

<bean id="itemWriter"
    class="org.springframework.batch.integration.chunk.ChunkMessageChannelItemWriter"
    scope="step">
  <property name="messagingOperations" ref="messagingTemplate"/>
  <property name="replyChannel" ref="replies"/>
</bean>

<int:channel id="replies">
  <int:queue/>
</int:channel>

<int-jms:message-driven-channel-adapter id="jmsReplies"
    destination-name="replies"
    channel="replies"/>

下面的 Java 配置提供了一个基本的 Manager 设置:

Java 配置

@Bean
public org.apache.activemq.ActiveMQConnectionFactory connectionFactory() {
    ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
    factory.setBrokerURL("tcp://localhost:61616");
    return factory;
}

/*
 * Configure outbound flow (requests going to workers)
 */
@Bean
public DirectChannel requests() {
    return new DirectChannel();
}

@Bean
public IntegrationFlow outboundFlow(ActiveMQConnectionFactory connectionFactory) {
    return IntegrationFlows
            .from(requests())
            .handle(Jms.outboundAdapter(connectionFactory).destination("requests"))
            .get();
}

/*
 * Configure inbound flow (replies coming from workers)
 */
@Bean
public QueueChannel replies() {
    return new QueueChannel();
}

@Bean
public IntegrationFlow inboundFlow(ActiveMQConnectionFactory connectionFactory) {
    return IntegrationFlows
            .from(Jms.messageDrivenChannelAdapter(connectionFactory).destination("replies"))
            .channel(replies())
            .get();
}

/*
 * Configure the ChunkMessageChannelItemWriter
 */
@Bean
public ItemWriter<Integer> itemWriter() {
    MessagingTemplate messagingTemplate = new MessagingTemplate();
    messagingTemplate.setDefaultChannel(requests());
    messagingTemplate.setReceiveTimeout(2000);
    ChunkMessageChannelItemWriter<Integer> chunkMessageChannelItemWriter
            = new ChunkMessageChannelItemWriter<>();
    chunkMessageChannelItemWriter.setMessagingOperations(messagingTemplate);
    chunkMessageChannelItemWriter.setReplyChannel(replies());
    return chunkMessageChannelItemWriter;
}

前面的配置为我们提供了许多 bean。我们使用 ActiveMQ 和 Spring Integration 提供的入站/出站 JMS 适配器配置消息传递中间件。如图所示,我们的作业步骤引用的itemWriter Bean 使用ChunkMessageChannelItemWriter在配置的中间件上写块。

现在我们可以转到 Worker 配置,如下面的示例所示:

下面的示例显示了 XML 中的工作配置:

XML 配置

<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
  <property name="brokerURL" value="tcp://localhost:61616"/>
</bean>

<int:channel id="requests"/>
<int:channel id="replies"/>

<int-jms:message-driven-channel-adapter id="incomingRequests"
    destination-name="requests"
    channel="requests"/>

<int-jms:outbound-channel-adapter id="outgoingReplies"
    destination-name="replies"
    channel="replies">
</int-jms:outbound-channel-adapter>

<int:service-activator id="serviceActivator"
    input-channel="requests"
    output-channel="replies"
    ref="chunkProcessorChunkHandler"
    method="handleChunk"/>

<bean id="chunkProcessorChunkHandler"
    class="org.springframework.batch.integration.chunk.ChunkProcessorChunkHandler">
  <property name="chunkProcessor">
    <bean class="org.springframework.batch.core.step.item.SimpleChunkProcessor">
      <property name="itemWriter">
        <bean class="io.spring.sbi.PersonItemWriter"/>
      </property>
      <property name="itemProcessor">
        <bean class="io.spring.sbi.PersonItemProcessor"/>
      </property>
    </bean>
  </property>
</bean>

下面的示例展示了 Java 中的 worker 配置:

Java 配置

@Bean
public org.apache.activemq.ActiveMQConnectionFactory connectionFactory() {
    ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
    factory.setBrokerURL("tcp://localhost:61616");
    return factory;
}

/*
 * Configure inbound flow (requests coming from the manager)
 */
@Bean
public DirectChannel requests() {
    return new DirectChannel();
}

@Bean
public IntegrationFlow inboundFlow(ActiveMQConnectionFactory connectionFactory) {
    return IntegrationFlows
            .from(Jms.messageDrivenChannelAdapter(connectionFactory).destination("requests"))
            .channel(requests())
            .get();
}

/*
 * Configure outbound flow (replies going to the manager)
 */
@Bean
public DirectChannel replies() {
    return new DirectChannel();
}

@Bean
public IntegrationFlow outboundFlow(ActiveMQConnectionFactory connectionFactory) {
    return IntegrationFlows
            .from(replies())
            .handle(Jms.outboundAdapter(connectionFactory).destination("replies"))
            .get();
}

/*
 * Configure the ChunkProcessorChunkHandler
 */
@Bean
@ServiceActivator(inputChannel = "requests", outputChannel = "replies")
public ChunkProcessorChunkHandler<Integer> chunkProcessorChunkHandler() {
    ChunkProcessor<Integer> chunkProcessor
            = new SimpleChunkProcessor<>(itemProcessor(), itemWriter());
    ChunkProcessorChunkHandler<Integer> chunkProcessorChunkHandler
            = new ChunkProcessorChunkHandler<>();
    chunkProcessorChunkHandler.setChunkProcessor(chunkProcessor);
    return chunkProcessorChunkHandler;
}

这些配置项中的大多数应该在 Manager 配置中看起来很熟悉。工作人员不需要访问 Spring 批JobRepository,也不需要访问实际的作业配置文件。主要的 Bean 兴趣是chunkProcessorChunkHandlerChunkProcessorChunkHandlerchunkProcessor属性接受一个已配置的SimpleChunkProcessor,在该属性中,你将提供对你的ItemWriter(以及你的ItemProcessor)的引用,该引用将在 worker 从 Manager 接收块时在其上运行。

有关更多信息,请参见远程分块 (opens new window)的“可伸缩性”章节。

从版本 4.1 开始, Spring 批处理集成引入了@EnableBatchIntegration注释,该注释可用于简化远程分块设置。这个注释提供了两个可以在应用程序上下文中自动连接的 bean:

  • RemoteChunkingManagerStepBuilderFactory:用于配置 Manager 步骤

  • RemoteChunkingWorkerBuilder:用于配置远程工作者集成流

这些 API 负责配置一些组件,如下图所示:

远程组块配置

图 4。远程组块配置

在 Manager 方面,RemoteChunkingManagerStepBuilderFactory允许你通过声明以下内容来配置 Manager:

  • 项目阅读器读取项目并将其发送给工人

  • 将请求发送给工作人员的输出通道(“传出请求”)

  • 接收工作人员回复的输入通道(“传入回复”)

aChunkMessageChannelItemWriterMessagingTemplate不需要显式配置(如果需要,仍然可以显式配置这些参数)。

在 worker 方面,RemoteChunkingWorkerBuilder允许你将 worker 配置为:

  • 监听 Manager 在输入通道上发送的请求(“传入请求”)

  • 对于配置了ItemProcessorItemWriter的每个请求,调用handleChunkChunkProcessorChunkHandler方法

  • 将输出通道上的回复(“输出回复”)发送给 Manager

不需要显式地配置SimpleChunkProcessorChunkProcessorChunkHandler(如果需要,可以显式地配置这些参数)。

下面的示例展示了如何使用这些 API:

@EnableBatchIntegration
@EnableBatchProcessing
public class RemoteChunkingJobConfiguration {

    @Configuration
    public static class ManagerConfiguration {

        @Autowired
        private RemoteChunkingManagerStepBuilderFactory managerStepBuilderFactory;

        @Bean
        public TaskletStep managerStep() {
            return this.managerStepBuilderFactory.get("managerStep")
                       .chunk(100)
                       .reader(itemReader())
                       .outputChannel(requests()) // requests sent to workers
                       .inputChannel(replies())   // replies received from workers
                       .build();
        }

        // Middleware beans setup omitted

    }

    @Configuration
    public static class WorkerConfiguration {

        @Autowired
        private RemoteChunkingWorkerBuilder workerBuilder;

        @Bean
        public IntegrationFlow workerFlow() {
            return this.workerBuilder
                       .itemProcessor(itemProcessor())
                       .itemWriter(itemWriter())
                       .inputChannel(requests()) // requests received from the manager
                       .outputChannel(replies()) // replies sent to the manager
                       .build();
        }

        // Middleware beans setup omitted

    }

}

你可以找到远程分块作业here (opens new window)的完整示例。

# 远程分区

远程分区

图 5。远程分区

另一方面,当导致瓶颈的不是项目的处理,而是相关的 I/O 时,远程分区是有用的。使用远程分区,可以将工作分配给执行完整 Spring 批处理步骤的工作人员。因此,每个工作者都有自己的ItemReaderItemProcessorItemWriter。为此, Spring 批处理集成提供了MessageChannelPartitionHandler

这个PartitionHandler接口的实现使用MessageChannel实例向远程工作者发送指令并接收他们的响应。这从用于与远程工作者通信的传输(例如 JMS 和 AMQP)中提供了一个很好的抽象。

“可伸缩性”章节中涉及远程分区的部分概述了配置远程分区所需的概念和组件,并展示了使用默认TaskExecutorPartitionHandler在单独的本地执行线程中进行分区的示例。要对多个 JVM 进行远程分区,还需要另外两个组件:

  • 一种远程的织物或网格环境

  • 支持所需的远程架构或网格环境的PartitionHandler实现

与远程组块类似,JMS 可以用作“远程组块结构”。在这种情况下,使用MessageChannelPartitionHandler实例作为PartitionHandler实现,如前面所述。

下面的示例假定存在一个分区作业,并重点关注 XML 中的MessageChannelPartitionHandler和 JMS 配置:

XML 配置

<bean id="partitionHandler"
   class="org.springframework.batch.integration.partition.MessageChannelPartitionHandler">
  <property name="stepName" value="step1"/>
  <property name="gridSize" value="3"/>
  <property name="replyChannel" ref="outbound-replies"/>
  <property name="messagingOperations">
    <bean class="org.springframework.integration.core.MessagingTemplate">
      <property name="defaultChannel" ref="outbound-requests"/>
      <property name="receiveTimeout" value="100000"/>
    </bean>
  </property>
</bean>

<int:channel id="outbound-requests"/>
<int-jms:outbound-channel-adapter destination="requestsQueue"
    channel="outbound-requests"/>

<int:channel id="inbound-requests"/>
<int-jms:message-driven-channel-adapter destination="requestsQueue"
    channel="inbound-requests"/>

<bean id="stepExecutionRequestHandler"
    class="org.springframework.batch.integration.partition.StepExecutionRequestHandler">
  <property name="jobExplorer" ref="jobExplorer"/>
  <property name="stepLocator" ref="stepLocator"/>
</bean>

<int:service-activator ref="stepExecutionRequestHandler" input-channel="inbound-requests"
    output-channel="outbound-staging"/>

<int:channel id="outbound-staging"/>
<int-jms:outbound-channel-adapter destination="stagingQueue"
    channel="outbound-staging"/>

<int:channel id="inbound-staging"/>
<int-jms:message-driven-channel-adapter destination="stagingQueue"
    channel="inbound-staging"/>

<int:aggregator ref="partitionHandler" input-channel="inbound-staging"
    output-channel="outbound-replies"/>

<int:channel id="outbound-replies">
  <int:queue/>
</int:channel>

<bean id="stepLocator"
    class="org.springframework.batch.integration.partition.BeanFactoryStepLocator" />

下面的示例假定存在一个分区作业,并重点关注 Java 中的MessageChannelPartitionHandler和 JMS 配置:

Java 配置

/*
 * Configuration of the manager side
 */
@Bean
public PartitionHandler partitionHandler() {
    MessageChannelPartitionHandler partitionHandler = new MessageChannelPartitionHandler();
    partitionHandler.setStepName("step1");
    partitionHandler.setGridSize(3);
    partitionHandler.setReplyChannel(outboundReplies());
    MessagingTemplate template = new MessagingTemplate();
    template.setDefaultChannel(outboundRequests());
    template.setReceiveTimeout(100000);
    partitionHandler.setMessagingOperations(template);
    return partitionHandler;
}

@Bean
public QueueChannel outboundReplies() {
    return new QueueChannel();
}

@Bean
public DirectChannel outboundRequests() {
    return new DirectChannel();
}

@Bean
public IntegrationFlow outboundJmsRequests() {
    return IntegrationFlows.from("outboundRequests")
            .handle(Jms.outboundGateway(connectionFactory())
                    .requestDestination("requestsQueue"))
            .get();
}

@Bean
@ServiceActivator(inputChannel = "inboundStaging")
public AggregatorFactoryBean partitioningMessageHandler() throws Exception {
    AggregatorFactoryBean aggregatorFactoryBean = new AggregatorFactoryBean();
    aggregatorFactoryBean.setProcessorBean(partitionHandler());
    aggregatorFactoryBean.setOutputChannel(outboundReplies());
    // configure other propeties of the aggregatorFactoryBean
    return aggregatorFactoryBean;
}

@Bean
public DirectChannel inboundStaging() {
    return new DirectChannel();
}

@Bean
public IntegrationFlow inboundJmsStaging() {
    return IntegrationFlows
            .from(Jms.messageDrivenChannelAdapter(connectionFactory())
                    .configureListenerContainer(c -> c.subscriptionDurable(false))
                    .destination("stagingQueue"))
            .channel(inboundStaging())
            .get();
}

/*
 * Configuration of the worker side
 */
@Bean
public StepExecutionRequestHandler stepExecutionRequestHandler() {
    StepExecutionRequestHandler stepExecutionRequestHandler = new StepExecutionRequestHandler();
    stepExecutionRequestHandler.setJobExplorer(jobExplorer);
    stepExecutionRequestHandler.setStepLocator(stepLocator());
    return stepExecutionRequestHandler;
}

@Bean
@ServiceActivator(inputChannel = "inboundRequests", outputChannel = "outboundStaging")
public StepExecutionRequestHandler serviceActivator() throws Exception {
    return stepExecutionRequestHandler();
}

@Bean
public DirectChannel inboundRequests() {
    return new DirectChannel();
}

public IntegrationFlow inboundJmsRequests() {
    return IntegrationFlows
            .from(Jms.messageDrivenChannelAdapter(connectionFactory())
                    .configureListenerContainer(c -> c.subscriptionDurable(false))
                    .destination("requestsQueue"))
            .channel(inboundRequests())
            .get();
}

@Bean
public DirectChannel outboundStaging() {
    return new DirectChannel();
}

@Bean
public IntegrationFlow outboundJmsStaging() {
    return IntegrationFlows.from("outboundStaging")
            .handle(Jms.outboundGateway(connectionFactory())
                    .requestDestination("stagingQueue"))
            .get();
}

还必须确保分区handler属性映射到partitionHandler Bean。

下面的示例将分区handler属性映射到 XML 中的partitionHandler:

XML 配置

<job id="personJob">
  <step id="step1.manager">
    <partition partitioner="partitioner" handler="partitionHandler"/>
    ...
  </step>
</job>

下面的示例将分区handler属性映射到 Java 中的partitionHandler:

Java 配置

	public Job personJob() {
		return jobBuilderFactory.get("personJob")
				.start(stepBuilderFactory.get("step1.manager")
						.partitioner("step1.worker", partitioner())
						.partitionHandler(partitionHandler())
						.build())
				.build();
	}

你可以找到远程分区作业here (opens new window)的完整示例。

可以用来简化远程分区设置的@EnableBatchIntegration注释。这个注释为远程分区提供了两个有用的 bean:

  • RemotePartitioningManagerStepBuilderFactory:用于配置 Manager 步骤

  • RemotePartitioningWorkerStepBuilderFactory:用于配置工作步骤

这些 API 负责配置一些组件,如下图所示:

远程分区配置(使用作业存储库轮询)

图 6。远程分区配置(使用作业存储库轮询)

远程分区配置(带有回复聚合)

图 7。远程分区配置(带有回复聚合)

在 Manager 方面,RemotePartitioningManagerStepBuilderFactory允许你通过声明以下内容来配置 Manager:

  • 用于划分数据的Partitioner

  • 将请求发送给工作人员的输出通道(“传出请求”)

  • 输入通道(“传入回复”)以接收来自工作人员的回复(在配置回复聚合时)

  • 轮询间隔和超时参数(在配置作业存储库轮询时)

不需要显式配置MessageChannelPartitionHandlerMessagingTemplate(如果需要,仍然可以显式配置这些参数)。

在 worker 方面,RemotePartitioningWorkerStepBuilderFactory允许你将 worker 配置为:

  • 监听 Manager 在输入通道上发送的请求(“传入请求”)

  • 对于每个请求调用StepExecutionRequestHandlerhandle方法

  • 将输出通道上的回复(“输出回复”)发送给 Manager

不需要显式配置StepExecutionRequestHandler(如果需要,可以显式配置)。

下面的示例展示了如何使用这些 API:

@Configuration
@EnableBatchProcessing
@EnableBatchIntegration
public class RemotePartitioningJobConfiguration {

    @Configuration
    public static class ManagerConfiguration {

        @Autowired
        private RemotePartitioningManagerStepBuilderFactory managerStepBuilderFactory;

        @Bean
        public Step managerStep() {
                 return this.managerStepBuilderFactory
                    .get("managerStep")
                    .partitioner("workerStep", partitioner())
                    .gridSize(10)
                    .outputChannel(outgoingRequestsToWorkers())
                    .inputChannel(incomingRepliesFromWorkers())
                    .build();
        }

        // Middleware beans setup omitted

    }

    @Configuration
    public static class WorkerConfiguration {

        @Autowired
        private RemotePartitioningWorkerStepBuilderFactory workerStepBuilderFactory;

        @Bean
        public Step workerStep() {
                 return this.workerStepBuilderFactory
                    .get("workerStep")
                    .inputChannel(incomingRequestsFromManager())
                    .outputChannel(outgoingRepliesToManager())
                    .chunk(100)
                    .reader(itemReader())
                    .processor(itemProcessor())
                    .writer(itemWriter())
                    .build();
        }

        // Middleware beans setup omitted

    }

}