任务

最近更新时间: 2026-06-30 15:06:00

任务类型

Java 任务

TCT 执行器默认支持的任务类型,Java 任务只支持预置在执行器中的任务。预置任务既可以是声明成 Bean 的任务,也可以是没有声明成 Bean 的任务,唯一的区别是,声明成 Bean 的任务会被 TCT SDK 自动发现并上报到 TCT,创建任务时可以从下拉列表中直接选择任务类,否则需要手动输入完成的任务类路径。
Java 任务的开发请参考后续【Java 开发】章节。

Shell 任务

Shell 任务用于执行 Shell 脚本(使用 Bash),既可以将 Shell 脚本预置在执行器能访问到的特定目录中(详见后续的【内置任务】章节),也可以在创建任务时直接在线编辑脚本。脚本中使用到的工具需要用户自己正确地安装到执行器中。

任务参数

在创建任务时,我们可以配置任务参数,任务参数将会被用作脚本的命令行参数,例如以下的任务参数,在执行任务脚本时相当于:

bash script.sh --sleep=3 --fail

任务元数据

TCT 会自动将任务的元数据以环境变量的方式注入到 Shell 脚本中,用户在编写 Shell 脚本时可以直接使用以下的元数据:

环境变量说明
TCT_TASK_ID任务 ID
TCT_TASK_LOG_ID任务流水 ID,可以认为是任务详情版本的 ID
TCT_BATCH_ID任务批次 ID
TCT_BATCH_LOG_ID任务批次流水 ID
TCT_EXECUTE_ID执行子任务 ID
TCT_EXECUTE_LOG_ID执行子任务流水 ID
TCT_SHARD_KEY分片任务中该分片子任务的分片 KEY
TCT_SHARD_VALUE分片任务中该分片子任务的分片值
TCT_SHARD_TOTAL分片任务中总分片数

因此,在编写 Shell 脚本时,可以这种引用相关的元数据:

echo "Task ID: ${TCT_TASK_ID}"

Python 任务

Python 任务用于执行 Python 脚本,既可以将 Python 脚本预置在执行器能访问到的特定目录中(详见后续的【内置任务】章节),也可以在创建任务时直接在线编辑脚本,脚本中使用到的 Python 包需要用户提前在执行器示例中准备好。

任务参数

在创建任务时,我们可以配置任务参数,任务参数将会被用作脚本的命令行参数,例如以下的任务参数,在执行任务脚本时相当于:

python script.py --sleep=3 --fail

任务元数据

TCT 会自动将任务的元数据以环境变量的方式注入到 Python 脚本中,用户在编写 Python 脚本时可以直接使用以下的元数据:

环境变量说明
TCT_TASK_ID任务 ID
TCT_TASK_LOG_ID任务流水 ID,可以认为是任务详情版本的 ID
TCT_BATCH_ID任务批次 ID
TCT_BATCH_LOG_ID任务批次流水 ID
TCT_EXECUTE_ID执行子任务 ID
TCT_EXECUTE_LOG_ID执行子任务流水 ID
TCT_SHARD_KEY分片任务中该分片子任务的分片 KEY
TCT_SHARD_VALUE分片任务中该分片子任务的分片值
TCT_SHARD_TOTAL分片任务中总分片数

因此,在编写 Shell 脚本时,可以这种引用相关的元数据:

import os
envs = os.environ
print "Task ID: %s" % (envs["TCT_TASK_ID"])

外部任务

顾名思义,外部任务的任务执行由 TCT 外的其他任务调度系统完成,TCT 只是把外部系统任务的执行封装成 TCT 的任务。典型的使用场景是由 TCT 任务编排多个外部系统任务的执行,例如金融场景下,不同的业务系统可能都有自己的任务调度系统,TCT 可以通过工作流将不同系统的任务进行编排按照给定的顺序以及依赖关系进行执行。

外部任务创建


创建外部任务时,需要配置外部任务调度系统的 API 用于和外部系统交互,包括:

任务触发 API

该 API 用于触发外部调度系统执行给定的任务,当外部任务在 TCT 里被触发时,执行器会调用该 API 触发任务在外部系统的执行。
TCT 对该接口没有具体的限制,只要求返回 JSON 类型的返回体,同时返回体中包含字段用于标识该次触发的任务,用于后续终止或者查询任务进度。

任务状态查询 API

该 API 用于查询任务在外部系统中执行的状态,并且收集任务执行的日志。TCT 在执行外部任务时会定期通过该 API 获取任务的执行进度以及执行日志,当查询到任务执行结束时结束外部系统的执行。

请求体

该API 的请求体可以由外部系统自由设计,一般来说请求体里需要包含任务触发(或者任务重试)接口返回的某一个 ID 字段用于标识具体的一次触发(对于 TCT 来说就是任务批次 ID)。
在创建外部任务时,该接口请求体中 可以引用之前任务触发(或者任务重试)接口返回体里的字段,采用 ${...} 的格式,例如任务触发返回体为以下内容,可以通过 ${$.Response.BatchID} 来引用 BatchID 字段。

{
  "Response": {
    "BatchID": "282390945848967"
     . . . 
  }
  . . .
}

因此任务查询请求体可以如下引用之前任务触发(或者任务重试) API 返回的字段:

{
  "BatchID": "${$.Response.BatchID}"
  . . .
}
返回体

TCT 对任务状态查询 API 的返回体有以下要求,以便能够正确提取任务进度以及日志信息。返回体中必须包含以下几个字段:

  • State(字符串枚举值 RUNNING、SUCCESS、FAILED、TIMEOUT、TERMINATED)。
  • Progress(1-100 整型)表示百分比进度,如果无法衡量可以不设置。
  • ExecuteLog(字符串) 表示上报的关键日志,注意对于一个任务的执行日志总长度不能超过 64KB(超过会被截断)。
  • DetailUrl(字符串)外部系统中查看任务执行详情的地址,在 TCT 任务执行记录中会显示该地址,点击跳转到外部系统中查看该任务内部执行详情。如果该字段多次上报,TCT平台只显示最新上报内容。
{
  "State": "RUNNING",
  "Progress": 50,
  "ExecuteLog": "Subtask A finished\n Start subtask B",
  "DetailUrl": "http://external.com/task?batchID=xxx"
}

任务终止 API

该接口用于终止任务在外部系统中的执行,当我们在 TCT 中终止一个外部任务时,执行器需要调用该 API 终止任务的执行。
TCT 未对该接口做具体的限制,请求体里如果需要引用之前任务触发(或者任务重试)接口返回体里的字段,请参考任务状态查询 API 里的说明。
如果外部调度系统不支持终止任务,也可以不配置该接口。

任务重试 API

该接口和【任务触发】类似,需要在接口返回体里包含新的任务执行的 ID,用于进行后续的任务状态查询或者终止等操作,并且该字段名称必须和任务触发 API 中的保持一致。
请求体里如果需要引用之前任务触发(或者任务重试)接口返回体里的字段,请参考任务状态查询 API 里的说明。

API 配置

  • 请求地址:API 的地址,支持 GET/PUT/POST 方法。
  • 请求 Header:可以配置一个或多个 Header,例如 token 或鉴权 key。
  • 请求 Body:请求体限定使用 JSON 格式,配置 Body 时可以通过 JSON Path 方式引用之前接口返回的字段,例如编写状态查询 API 时我们可以引用任务触发 API 返回体力的字段。JSON Path 规范可以参考后续【JSON Path 规范】章节。
  • 成功判定:用于指定如何判定 API 调用成功,支持返回码的检查和返回体字段(通过 JSON Path 引用)的检查。

获取任务元数据

TCT 在调用外部 API 的时候,会自动注入一个 http header TASK-META,它包含了本次任务触发的批次历史 ID,目前外部系统一般不需要使用到该信息。具体格式如下:

{
  "TaskLogId": "197667124972617728",
  "BatchLogId": "198114389551595568"
}

具体设置到 http Header 中的值是该 JSON 对象 base64 编码后的值,例如:

$ echo '{
  "TaskLogId": "197667124972617728", 
  "BatchLogId": "198114389551595568"
}' | jq -c | base64

eyJUYXNrTG9nSWQiOiIxOTc2NjcxMjQ5NzI2MTc3MjgiLCJCYXRjaExvZ0lkIjoiMTk4MTE0Mzg5NTUxNTk1NTY4In0K

JSON Path 规范

在外部任务中,编写请求体和指定成功判定条件时都可以通过 JSON Path 引用 JSON 字段,这里的 JSON Path $ 表示根对象,并且用点号 . 引用字段,例如 $.store.book[0].title。
参考链接 https://github.com/json-path/JsonPath

外部任务执行

外部任务的调度在 TCT 里看来和其他类型任务没有任何区别,区别只在执行器如何执行外部任务,外部任务执行的过程可以简单的理解为:

  • 开始执行:调用任务触发 API。
  • 执行中:定期调用任务状态查询 API,获取执行进度,收集执行日志。
  • 执行结束:任务状态查询 API 返回任务结束状态,任务执行结束。
    外部任务执行信息里会包含一个链接,链接到外部系统中执行详情页中,如下图所示:

任务来源

预置任务

预置任务是已经包含在执行器是中的任务,Java、Shell、Python 任务类型都支持预置任务,预置任务时 TCT 任务最常用的形态。

Java 预置任务发现

Java 预置任务即那些实现了 ExecutableTask 接口的 Java 类,这些都是预置任务,也都可以被 TCT 调度执行。
但是只有注册成 Bean 的那些预置任务会被 TCT SDK 自动发现并上报到 TCT 中,其他的预置任务需要在创建的时候手动输入完成的类名。在下图中,下拉列表显示的任务是注册成 Bean 的两个预置任务,可以直接选择,其他的任务需要在输入框中输入完整的类名来使用。

Shell/Python 预置任务发现

预置的脚本任务以脚本文件的方式放在执行器特定的目录中(称为TCT任务目录),任务目录下的文件结构如下所示:

<任务目录>
|__ Shell
       |__ SimpleTask.sh 
       |__ ...
|__ Python
       |__ SimpleTask.py 
       |__ ...

TCT SDK 启动的时候会扫描 TCT 任务目录来收集执行器上的预置任务,任务目录可以在执行器启动参数中配置:

tct:
  taskDirs:
    - /tct/tasks 

TCT 会扫描以下的任务目录:

  • 扫描 Jar 包里的 tct-tasks 这个 resources 目录,即执行器代码中 resources 目录下的 tct-tasks 目录:
  • 扫描工作目录下的 tct-tasks 目录
    • System.getProperty("user.dir") + "/tct-tasks"
  • 扫描执行器启动参数中的目录
    • tct.taskDirs

在线编辑任务

Shell、Python 任务类型支持在线编辑任务内容,创建任务时可以打开在线编辑器编辑 Shell 或者 Python 脚本。

任务优先级

TCT 中任务目前支持一般和重要两个不同的优先级级别,不同优先级的任务在以下几方面体现出不同:

  • 任务调度和执行时,如果存在任务的排队现象,重要的任务会进行"插队",即插队到一般任务的前面,但是同优先级的任务还是先到先出的顺序。
  • 如果系统健康度下降,处理能力不足,这时候一般的任务触发后会被概率性地限制调度,而直接进入被限流的终态(详见后续【调度限流】章节),而重要的任务不会被限流。

触发规则

定时触发

通过 Cron 表达式指定的定时触发,Cron 采用标准的 Quartz 语法,例如 "0 * * * * ?" 表示每分钟触发一次。

周期触发

周期触发指定固定的触发周期,例如每隔 5 分钟触发一次。注意这里的触发间隔衡量的是上次触发的时间到这次触发的时间,而不是上次任务执行结束的时间到这次触发的时间。

工作流触发

工作流触发表示该任务将用于构建工作流,由工作流执行去触发。

对于工作流触发的任务,我们还可以选择配置一个允许触发的时间,指定自然日的几点之后才允许触发该任务,该参数在金融场景中比较常用,这里的 T 日表示的是工作流开始执行的日期,T+1 日表示第二天。如下图所示我们配置了 T 日的 20:00,那么该任务在工作流中要触发执行需要同时满足:

  • 该任务的前置依赖任务都完成。
  • 时间是 T 日的 20:00 之后,否则要等到 20:00 才允许触发。

特定时间触发

特定时间触发用于指定任务只在特定的时间点触发一次,例如 2024/01/01 00:00:00。

手动触发

手动触发用于手动触发任务执行一次,无论任务配置的何种触发规则以及是否启用,都可以手动触发执行。

触发限制

对于定时触发和周期触发的任务,可以限制任务的触发,包括:

  • 开始触发时间:只有该时间后才允许按照触发规则自动触发任务执行。
  • 结束触发时间:该时间后就不再触发任务执行,该时间之后任务会自动进入禁用状态。
  • 最多触发次数:任务最多触发的次数,注意手动触发任务也统计在次数中。

    **注意:**这里最多触发次数是以当前能够查询到的执行记录数来统计的,所以会受执行记录清理的影响。如果时间超过结束触发时间或者触发次数超过最多触发次数,任务会自动被设置为停用。

执行方式

TCT 支持以下三种执行方式:

  • 单点执行:在可用的执行器实例中选择一个实例用于执行该任务,选择的策略目前为随机选择。
  • 广播执行:将任务调度到所有的执行器实例中执行,因此在任务调度的时候有多少个可用的执行器实例就会产生多少个子任务。
  • 分片执行:分片执行是将任务拆分成多个分片(子任务),然后在所有可用执行器实例上调度这些分片子任务。分片数量由用户在创建任务的时候指定,每个分片都会有一个自己的分片编号(1、2、3...),一般来说用户需要设置每个分片的分片参数,TCT 在调度分片的时候会将分片参数传递给执行器。

调度策略

调度策略用于控制如何将任务调度到执行器上,例如:

  • 选择哪些执行器实例用来调度给定的任务。
  • 任务 A 和任务 B 不允许同时在同一个执行器上执行。
  • 任务 C 和任务 D 尽量在同一个执行器上执行。
    TCT 提供了以下两方面的调度控制:

执行器选择

所有

为默认行为,任务会按照指定的执行方式(单点执行、广播执行、分片执行)在部署组内所有可用的执行器实例中调度执行。

标签过滤

通过指定标签选择器筛选匹配的执行器实例执行,而不是部署组下所有可用的执行器实例。

标签分组

将执行器实例按照指定的标签进行分组,标签值相同的放在一组,如果执行器没有给定的标签,则排除在外。执行器分组后任务会按照指定的执行方式(单点执行、广播执行、分片执行)在每个分组上进行调度执行。

任务启用

自动触发(定时触发、周期触发、特定时间触发)的任务只有启用了才会被自动触发执行,在创建任务的时候可以选择是否启用任务。
任务创建好后也可以在控制台上修改任务启用状态,注意工作流触发的任务由工作流触发,任务本身没有启用、禁用一说,也因此工作流触发的任务在控制台上无法进行启用或禁用的操作。

任务超时

创建任务的时候可以给任务配置超时时间,任务调度和执行超过配置的超时时间后,TCT 会将触发的任务批次设置为超时状态。超时的时间会传递到执行器上,由执行器控制任务执行时的超时,超时后终止任务的执行。

任务重试

创建任务的时候可以给任务配置任务运行失败自动重试的次数,以及每次失败重试的间隔,单位:秒。

任务参数

TCT 可以给任务配置执行参数,执行参数的格式 TCT 不做限制,只负责按原样字符串透传,参数的解析由任务逻辑自己负责。

Java 任务参数

例如下图所示,我们设置了 "--sleep=30 --fail" 任务参数,在 Java 中,可以通过 ExecutableTaskData::getTaskArgument 获取到原始参数字符串,然后进行参数的解析,例如使用 org.apache.commons.cli.CommandLineParser。

public ProcessResult execute(ExecutableTaskData taskData) {
    // Output: Raw task argument: --sleep=30 --fail
    LOG.debug("Raw task argument: {}", taskData.getTaskArgument());
    . . .
}

Shell 任务参数

Shell 脚本类型任务执行的时候会将任务参数作为脚本执行时的命令行参数进行传递,因此上图中的任务参数 "--sleep=3 --fail" 相当于执行脚本时的如下参数:

bash script.sh --sleep=3 --fail

Python 任务参数

Python 脚本类型任务执行的时候会将任务参数作为脚本执行时的命令行参数进行传递,因此上图中的任务参数 "--sleep=3 --fail" 相当于执行脚本时的如下参数:

python script.py --sleep=3 --fail

分片参数

对于分片执行的任务,我们可以在高级设置里设置任务的分片参数,分片参数以每个分片的 Key(即分片编号 1,2,3....)为 Key,参数值类型不做限制,具体参数值的解析由任务逻辑自己负责,TCT 会将参数值作为字符串进行透传。
如下图所示,任务包含 3 个分片,我们可以给每个分片(1,2,3)设置分片参数:

  • 1:0
  • 2:100
  • 3:200

    在 Java 中,可以通过 ExecutableTaskData::getShardingArgs 获取到分片参数(包括分片 Key、分片参数值、分片总数),而在 Python 和 Shell 任务类型中,分片参数通过环境变量注入到脚本中:TCT_SHARD_KEY、TCT_SHARD_VALUE、TCT_SHARD_TOTAL。

执行记录保留策略

TCT 支持给每个任务指定单独的执行记录保留策略,用户可以指定执行记录保留的天数和执行记录保留的数量,其中保留天数最多支持 30 天,保留数量 0 代表不限制(相当于无限大),最多支持保留 100000 条。

如果配置了两个条件,那么 TCT 会按照每个条件分别去清理执行记录,假设配置保留 3 天,最多保留 1000 条记录,那么 TCT 会清理掉该任务 3 天前的所有执行记录,同时判断执行记录数是否超过了 1000,超过的话保留最新的 1000 条清理掉其他的记录。

状态停留超时配置

在任务的高级配置中,有一个状态停留超时配置,该配置用于指定任务执行时在某个状态停留超过指定时间后的处理措施。例如任务一直卡在下发中,我们可以选择将它直接置为失败,避免一直重试,同时也可以发出告警通知用户。

在这里配置的状态,触发停留超时后都会在监控指标中体现停留超时,用户可以通过配置告警规则用于告警发现。如下图所示虽然我们对执行中停留操过 5 分钟的情况没有采取额外措施,但是执行超过 5 分钟会有监控告警体现这个情况。

并发控制

TCT 中涉及三个方面的并发控制:

  • 部署组中配置的执行器实例的并发限制,允许多少个子任务同时在一个执行器上执行。
  • 分片任务配置的子任务单机并发数,同时允许该任务的多少分片子任务在同一个执行器上执行。
  • 任务是否允许并发执行,控制一个任务上一次执行还未结束,是否允许触发新的执行。

如果任务配置为不允许并发执行,那么上一个任务执行没有结束,任务不允许再次执行,到了触发时间后,新的触发记录会直接被置为被拒绝状态。设想一下,如果上一次执行因为异常的原因一直没有结束,那么会一直影响后续的触发执行。例如,任务调度到执行器上执行,执行过程中执行器实例突然下线了,那么 TCT 就无法知道该任务的执行状态,因为无法判断执行器实例是网络隔离了还是已经下线了,这时候任务在 TCT 里会一直处于执行中状态,导致后续的触发执行直接被拒绝。

为了解决这个问题,TCT 做了两方面的优化,一方面如果执行器实例恢复了(例如网络隔离解除、执行器重启上线),TCT 能够知道任务是否还在该执行器上执行,如果已经不存在了,那么 TCT 就可以将任务状态置为一个未知的状态,它是一个终态。之所以不是成功或者失败的状态,是因为我们无法判断任务在业务层面上是否已经完成了。另一方面我们给任务增加了一个失联等待的功能,如果任务不允许并发执行,那么可以配置一个失联等待的时间,如果任务调度到执行器上执行后失联了(例如上面的执行器突然下线了,导致我们无法判断任务状态),失联超过配置的时间后 TCT 会将任务状态置为未知状态。

注意:任务配置了失联等待后,如果任务失联被置为未知状态,任务的下一次触发会正常执行,由于上一次执行是未知状态,可能会出现任务并行执行的情况,因此需要严格串行执行的任务需要根据自己的情况谨慎使用这个功能。

聚合输出

TCT 任务执行完后可以以键值对的方式输出执行结果,例如对于 Java 使用 ProcessResult.withOutput(String key, String value) 输出执行结果。
在任务中,我们可以给输出结果配置聚合规则,用于聚合分片任务、广播任务的多个输出值,如求取平均值、求和。

这里待聚合字段必须是任务输出的一个字段,而聚合结果字段是我们为聚合后的值指定的字段名称。

调度限流

调度限流是 TCT 在系统健康性出现问题或者处理能力不足时采用的一种服务降级的策略。它根据系统当前的健康程度,按照任务的优先级,对低优先级的任务进行选择性地放弃,从而确保高优先级任务的正常调度。触发限流的条件是比较苛刻的,不用担心一般优先级的任务经常性地被限流,并且限流发生时也会触发 TCT 的告警。

健康度

系统健康度用来评估系统当前的健康状况,取值 0-1,值越高表示系统越健康,处理能力越强。低于 1 表示系统出现不同程度的拥塞,正常情况下健康度都应该是 1。
健康度的计算考虑了任务调度队列、任务分发队列的当前排队长度以及队列当前的增长趋势。

限流算法

对于重要级别的任务,不会进行限流,限流只针对一般的任务。下述示例代码中,score.get() 用于获取当前系统实时的健康度,正常情况下返回 1,rand.nextDouble 用于获取一个 0-1 范围内的随机小数,因此该方法正常情况下永远返回 false,即不限流。

当系统健康度下降,系统出现一定程度的拥塞,例如 score.get() 返回 0.9,这时候 rand.nextDouble 随机返回 0-1 中的小数,只有返回 0.9-1.0 区间的小数时该方法才返回 true,即限制该任务的调度,而 rand.nextDouble 返回的随机数落在 0.9-1.0 范围内的概率为 0.1,即该任务的此次调度有 10% 的概率会被限流放弃,直接进入被限流状态。

换句话说,如果系统健康度为 x,则一般任务的一次调度有 1.0-x 的概率会被限流。

public boolean throttling(int priority) {
    if (priority == Priority.IMPORTANT.getValue()) {
        return false;
    }

    return rand.nextDouble() > score.get();
}

执行日志

TCT 支持收集和显示任务的执行日志,如下图所示:

对于 Java 类任务,TCT 收集的日志是通过 SDK 中的 LogReporter::log 上报的日志,而对于 Python/Shell 脚本类任务,TCT 收集的是脚本执行产生的标准输出和错误输出日志,对于外部任务,日志收集的是外部系统通过任务状态 API 上报的执行日志。

断点续跑

断点续跑是针对分片任务的续跑功能,分片任务的一个分片子任务需要处理多个数据,可能出现部分数据处理成功部分处理失败或者未处理的情况,这时候任务可以将任务执行的断点信息(哪些数据处理了,哪些没处理)上报给 TCT,后续从 TCT 控制台触发断点续跑的时候,TCT 会将断点信息下发到执行器,任务执行的时候根据断点信息只执行未处理的数据。

任务停止

执行中的任务可以手动停止,任务停止时 TCT 会将任务批次状态设置为终止中,并通知执行器终止任务的执行,所有子任务终止后会将任务批次状态置为已终止。

任务删除

TCT 支持单个任务的删除以及批量任务的删除,注意已经用于构建工作流的任务不允许删除。任务删除后会立刻进入删除中状态,删除中状态的任务在 TCT 控制台上不可见,TCT 通过 GC(Garbage Collector 垃圾回收)处理任务的删除,GC 时会清理任务相关的历史版本、任务执行批次(包括批次流水)、子任务执行记录(Execute)等。如果任务未完成清理,后端服务故障重启了,GC 机制在启动的时候也会扫描数据库中处于删除中的任务,尝试继续清理任务相关资源,确保任务最终被清理干净。