功能特性

本节更详细地介绍了 Spring Cloud Task,包括如何使用它、如何配置它以及适当的扩展点。

Spring Cloud Task 的生命周期

在大多数情况下,现代云环境围绕着那些预期不会终止的进程而设计。如果它们确实终止了,通常会重新启动。虽然大多数平台都有某种方式运行一个终止时不会被重新启动的进程,但其运行结果通常无法以可消费的方式维护。Spring Cloud Task 提供了在环境中执行短期进程并记录结果的能力。这样做允许围绕短期进程构建微服务架构,并通过消息集成任务,实现更长时间运行的服务。

虽然此功能在云环境中很有用,但在传统部署模型中也可能出现同样的问题。当使用 cron 等调度器运行 Spring Boot 应用程序时,能够在应用程序完成后监控其结果会很有用。

Spring Cloud Task 采用这样一种方法:Spring Boot 应用程序可以有开始和结束,并且仍然是成功的。批处理应用程序就是一种如何实现预期会终止(且通常是短期)的进程并使其发挥作用的示例。

Spring Cloud Task 会记录给定任务的生命周期事件。大多数长时间运行的进程(以大多数 Web 应用程序为代表)不保存它们的生命周期事件。而 Spring Cloud Task 的核心任务则会保存。

生命周期由一次任务执行组成。这是一次 Spring Boot 应用程序的物理执行,该应用程序被配置为一个任务(即,它具有 Spring Cloud Task 依赖项)。

在任务开始时,在任何 CommandLineRunnerApplicationRunner 实现运行之前,会在 TaskRepository 中创建一个记录开始事件的条目。此事件通过 Spring Framework 触发 SmartLifecycle#start 来触发。这向系统表明所有 bean 都已准备就绪,并且在此之前会运行 Spring Boot 提供的任何 CommandLineRunnerApplicationRunner 实现。

任务的记录仅在 ApplicationContext 成功引导时发生。如果上下文完全无法引导,则不记录该任务的运行。

在 Spring Boot 的所有 *Runner#run 调用完成后,或者 ApplicationContext 失败(由 ApplicationFailedEvent 指示)时,任务执行的结果会在仓库中更新。

如果应用程序要求在任务完成时关闭 ApplicationContext(所有 *Runner#run 方法都已调用且任务仓库已更新),则将属性 spring.cloud.task.closecontextEnabled 设置为 true。

TaskExecution

存储在 TaskRepository 中的信息在 TaskExecution 类中建模,包含以下信息

字段 描述

executionid

任务运行的唯一 ID。

exitCode

ExitCodeExceptionMapper 实现生成的退出码。如果没有生成退出码但抛出了 ApplicationFailedEvent,则设置为 1。否则,假定为 0。

taskName

任务的名称,由配置的 TaskNameResolver 决定。

startTime

任务启动的时间,由 SmartLifecycle#start 调用指示。

endTime

任务完成的时间,由 ApplicationReadyEvent 指示。

exitMessage

退出时可用的任何信息。这可以通过 TaskExecutionListener 编程设置。

errorMessage

如果异常是任务结束的原因(由 ApplicationFailedEvent 指示),则该异常的堆栈跟踪会存储在此处。

arguments

传递给可执行 boot 应用程序的字符串命令行参数的 List

映射退出码

当任务完成时,它会尝试向操作系统返回一个退出码。如果我们看看我们的原始示例,可以看到我们没有控制应用程序的这一方面。因此,如果抛出异常,JVM 返回的代码可能对你的调试有用,也可能没有用。

因此,Spring Boot 提供了一个接口 ExitCodeExceptionMapper,允许你将未捕获的异常映射到退出码。这样做可以让你在退出码级别指示出了什么问题。此外,通过以这种方式映射退出码,Spring Cloud Task 会记录返回的退出码。

如果任务因 SIG-INT 或 SIG-TERM 而终止,退出码为零,除非代码中另有指定。

任务运行时,退出码在仓库中存储为 null。任务完成后,根据本节前面描述的指南存储相应的退出码。

配置

Spring Cloud Task 提供了开箱即用的配置,如 DefaultTaskConfigurerSimpleTaskConfiguration 类中定义的那样。本节将介绍默认配置以及如何根据需要自定义 Spring Cloud Task。

DataSource

Spring Cloud Task 使用数据源来存储任务执行结果。默认情况下,我们提供了一个内存中的 H2 实例,以提供简单的开发引导方法。然而,在生产环境中,你可能希望配置自己的 DataSource

如果你的应用程序只使用一个 DataSource,并且它既作为你的业务 schema 又作为任务仓库,你只需提供任何 DataSource(最简单的方法是通过 Spring Boot 的配置约定)。Spring Cloud Task 会自动将此 DataSource 用于仓库。

如果你的应用程序使用多个 DataSource,你需要使用适当的 DataSource 配置任务仓库。此自定义可以通过 TaskConfigurer 的实现来完成。

表前缀

TaskRepository 的一个可修改属性是任务表的表前缀。默认情况下,它们都以 TASK_ 开头。TASK_EXECUTIONTASK_EXECUTION_PARAMS 是两个示例。然而,修改此前缀可能有潜在的原因。如果 schema 名称需要添加到表名称前面,或者在同一个 schema 中需要多组任务表,则必须更改表前缀。你可以通过将 spring.cloud.task.tablePrefix 设置为你需要的前缀来完成此操作,如下所示

spring.cloud.task.tablePrefix=yourPrefix

通过使用 spring.cloud.task.tablePrefix,用户承担创建任务表的责任,这些任务表需要满足任务表 schema 的标准,但要进行用户业务所需的修改。在创建自己的任务 DDL 时,你可以利用 Spring Cloud Task Schema DDL 作为指南,此处提供了示例。

启用/禁用表初始化

在你自己创建任务表,并且不希望 Spring Cloud Task 在任务启动时创建它们的情况下,请将 spring.cloud.task.initialize-enabled 属性设置为 false,如下所示

spring.cloud.task.initialize-enabled=false

默认为 true

属性 spring.cloud.task.initialize.enable 已弃用。

外部生成的任务 ID

在某些情况下,你可能希望考虑任务被请求与基础设施实际启动它之间的时间差。Spring Cloud Task 允许你在任务被请求时创建一个 TaskExecution。然后将生成的 TaskExecution 的执行 ID 传递给任务,以便它可以在任务的生命周期中更新 TaskExecution

可以通过在引用存储 TaskExecution 对象的数据库的 TaskRepository 实现上调用 createTaskExecution 方法来创建 TaskExecution

为了配置你的任务使用生成的 TaskExecutionId,添加以下属性

spring.cloud.task.executionid=yourtaskId

外部任务 Id

Spring Cloud Task 允许你为每个 TaskExecution 存储一个外部任务 ID。为了配置你的任务使用生成的 TaskExecutionId,添加以下属性

spring.cloud.task.external-execution-id=<externalTaskId>

父任务 Id

Spring Cloud Task 允许你为每个 TaskExecution 存储一个父任务 ID。一个例子是某个任务执行另一个或多个任务,并且你想要记录是哪个任务启动了每个子任务。为了配置你的任务设置父 TaskExecutionId,在子任务上添加以下属性

spring.cloud.task.parent-execution-id=<parentExecutionTaskId>

TaskConfigurer

TaskConfigurer 是一个策略接口,允许你自定义 Spring Cloud Task 组件的配置方式。默认情况下,我们提供了 DefaultTaskConfigurer,它提供了逻辑默认值:基于 Map 的内存组件(如果没有提供 DataSource,这对于开发很有用)和基于 JDBC 的组件(如果提供了 DataSource,这很有用)。

TaskConfigurer 允许你配置三个主要组件

组件 描述 默认值(由 DefaultTaskConfigurer 提供)

TaskRepository

要使用的 TaskRepository 实现。

SimpleTaskRepository

TaskExplorer

要使用的 TaskExplorer 实现(用于对任务仓库进行只读访问的组件)。

SimpleTaskExplorer

PlatformTransactionManager

执行任务更新时使用的事务管理器。

如果使用 DataSource,则为 JdbcTransactionManager。如果未使用,则为 ResourcelessTransactionManager

你可以通过创建 TaskConfigurer 接口的自定义实现来定制前面表格中描述的任何组件。通常,继承 DefaultTaskConfigurer(如果找不到 TaskConfigurer 则提供)并重写所需的 getter 方法就足够了。然而,可能需要从头开始实现自己的组件。

用户不应直接使用 TaskConfigurer 的 getter 方法,除非他们使用它来提供实现以暴露为 Spring Bean。

任务执行监听器

TaskExecutionListener 允许你注册任务生命周期中发生的特定事件的监听器。为此,创建一个实现 TaskExecutionListener 接口的类。实现 TaskExecutionListener 接口的类会收到以下事件通知

  • onTaskStartup:在将 TaskExecution 存储到 TaskRepository 之前。

  • onTaskEnd:在更新 TaskRepository 中的 TaskExecution 条目并标记任务的最终状态之前。

  • onTaskFailed:在任务抛出未处理异常时调用 onTaskEnd 方法之前。

Spring Cloud Task 还允许你使用以下方法注解将 TaskExecution 监听器添加到 bean 中的方法

  • @BeforeTask:在将 TaskExecution 存储到 TaskRepository 之前

  • @AfterTask:在更新 TaskExecution 中的 TaskExecution 条目并标记任务最终状态之前。

  • @FailedTask:在任务抛出未处理异常时调用 @AfterTask 方法之前。

以下示例显示了这三个注解的使用

 public class MyBean {

	@BeforeTask
	public void methodA(TaskExecution taskExecution) {
	}

	@AfterTask
	public void methodB(TaskExecution taskExecution) {
	}

	@FailedTask
	public void methodC(TaskExecution taskExecution, Throwable throwable) {
	}
}
在链中早于 TaskLifecycleListener 插入 ApplicationListener 可能会导致意外的影响。

任务执行监听器抛出的异常

如果 TaskExecutionListener 事件处理程序抛出异常,则该事件处理程序的所有监听器处理都将停止。例如,如果三个 onTaskStartup 监听器已经启动,并且第一个 onTaskStartup 事件处理程序抛出异常,则不会调用另外两个 onTaskStartup 方法。然而,TaskExecutionListener 的其他事件处理程序(onTaskEndonTaskFailed)会被调用。

TaskExecutionListener 事件处理程序抛出异常时返回的退出码是 ExitCodeEvent 报告的退出码。如果没有发出 ExitCodeEvent,则会评估抛出的异常,看它是否是 ExitCodeGenerator 类型。如果是,则返回来自 ExitCodeGenerator 的退出码。否则,返回 1

如果在 onTaskStartup 方法中抛出异常,应用程序的退出码将为 1。如果在 onTaskEndonTaskFailed 方法中抛出异常,应用程序的退出码将是根据上述规则确定的值。

如果在 onTaskStartuponTaskEndonTaskFailed 中抛出异常,你无法使用 ExitCodeExceptionMapper 覆盖应用程序的退出码。

退出消息

你可以通过使用 TaskExecutionListener 以编程方式设置任务的退出消息。这是通过设置 TaskExecutionexitMessage 来完成的,然后它会传递给 TaskExecutionListener。以下示例显示了一个使用 @AfterTask ExecutionListener 注解的方法

@AfterTask
public void afterMe(TaskExecution taskExecution) {
    taskExecution.setExitMessage("AFTER EXIT MESSAGE");
}

可以在任何监听器事件(onTaskStartuponTaskFailedonTaskEnd)中设置 ExitMessage。这三个监听器的优先顺序如下

  1. onTaskEnd

  2. onTaskFailed

  3. onTaskStartup

例如,如果你为 onTaskStartuponTaskFailed 监听器设置了 exitMessage,并且任务结束时没有失败,则 onTaskStartupexitMessage 会存储在仓库中。否则,如果发生失败,则存储 onTaskFailedexitMessage。此外,如果你使用 onTaskEnd 监听器设置 exitMessage,则 onTaskEndexitMessage 会覆盖 onTaskStartuponTaskFailed 的退出消息。

限制 Spring Cloud Task 实例

Spring Cloud Task 允许你设定具有给定任务名称的任务只能同时运行一个实例。为此,你需要确定任务名称,并为每次任务执行设置 spring.cloud.task.single-instance-enabled=true。当第一个任务执行正在运行时,任何时候你尝试运行具有相同任务名称且设置了 spring.cloud.task.single-instance-enabled=true 的任务,该任务都会失败,并显示以下错误消息:Task with name "application" is already running. spring.cloud.task.single-instance-enabled 的默认值为 false。以下示例显示了如何将 spring.cloud.task.single-instance-enabled 设置为 true

spring.cloud.task.single-instance-enabled=true or false

要使用此功能,你必须向应用程序添加以下 Spring Integration 依赖项

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-core</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-jdbc</artifactId>
</dependency>
如果任务因启用此功能且另一个具有相同任务名称的任务正在运行而失败,则应用程序的退出码将为 1。

Spring AOT 和 Native Compilation 的单实例用法

在使用 Spring Cloud Task 的单实例功能创建原生编译应用程序时,你需要在构建时启用此功能。为此,添加 process-aot 执行,并设置 spring.cloud.task.single-step-instance-enabled=true 作为 JVM 参数,如下所示

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>process-aot</id>
            <goals>
                <goal>process-aot</goal>
            </goals>
            <configuration>
                <jvmArguments>
                    -Dspring.cloud.task.single-instance-enabled=true
                </jvmArguments>
            </configuration>
        </execution>
    </executions>
</plugin>

为 ApplicationRunner 和 CommandLineRunner 启用可观测性

要为 ApplicationRunnerCommandLineRunner 启用任务可观测性,请将 spring.cloud.task.observation.enabled 设置为 true。

一个启用了可观测性并使用 SimpleMeterRegistry 的任务应用程序示例可以在此处找到。

禁用 Spring Cloud Task 自动配置

在某些实现中不应自动配置 Spring Cloud Task 的情况下,你可以禁用 Task 的自动配置。这可以通过向你的 Task 应用程序添加以下注解来完成

@EnableAutoConfiguration(exclude={SimpleTaskAutoConfiguration.class})

你也可以通过将 spring.cloud.task.autoconfiguration.enabled 属性设置为 false 来禁用 Task 自动配置。

关闭上下文

如果应用程序要求在任务完成时关闭 ApplicationContext(所有 *Runner#run 方法都已调用且任务仓库已更新),则将属性 spring.cloud.task.closecontextEnabled 设置为 true

另一种需要关闭上下文的情况是,任务执行完成后,应用程序没有终止。在这种情况下,上下文会保持打开状态,因为分配了线程(例如:如果你正在使用 TaskExecutor)。在这些情况下,在启动任务时将 spring.cloud.task.closecontextEnabled 属性设置为 true。这会在任务完成后关闭应用程序的上下文。从而允许应用程序终止。

启用任务指标

Spring Cloud Task 与 Micrometer 集成,并为其执行的任务创建可观测性数据。要启用任务可观测性集成,你必须向你的任务应用程序添加 spring-boot-starter-actuator、你偏好的注册表实现(如果你想发布指标)以及 micrometer-tracing(如果你想发布追踪数据)。一个使用 Influx 启用任务可观测性和指标的 Maven 依赖示例如下

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-influx</artifactId>
    <scope>runtime</scope>
</dependency>

Spring Task 和 Spring Cloud Task 属性

术语 task 在行业中是一个常用词。例如,Spring Boot 提供了 spring.task 属性,而 Spring Cloud Task 提供了 spring.cloud.task 属性。过去曾因此引起一些困惑,认为这两组属性直接相关。然而,它们代表了 Spring 生态系统中提供的两组不同的功能特性。

  • spring.task 指的是配置 ThreadPoolTaskScheduler 的属性。

  • spring.cloud.task 指的是配置 Spring Cloud Task 功能特性的属性。