Appearance
分布式调度操作手册
版本:V2.0.0
目标
分布式调度(ujob) 的设计目标为分布式任务调度组件,为任务调度中间件。应用通过依赖 ujob-worker 即可接入调度组件获取任务调度与分布式计算能力。
功能概述
使用简单:提供前端Web界面,允许开发者可视化地完成调度任务的管理(增、删、改、查)、任务运行状态监控和运行日志查看等功能。
定时策略完善:支持 CRON 表达式、固定频率、固定延迟和API四种定时调度策略。
执行模式丰富:支持单机、广播、Map、MapReduce 四种执行模式,其中 Map/MapReduce 处理器能使开发者寥寥数行代码便获得集群分布式计算的能力。
执行器支持广泛:支持 Spring Bean、内置/外置 Java 类,另外可以通过引入官方提供的依赖包,一键集成 Shell、Python、HTTP、SQL 等处理器,应用范围广。
运维便捷:支持在线日志功能,执行器产生的日志可以在前端控制台页面实时显示,降低 debug 成本,极大地提高开发效率。
依赖精简:最小仅依赖关系型数据库(MySQL/PostgreSQL/Oracle/MS SQLServer...)。
故障转移与恢复:任务执行失败后,可根据配置的重试策略完成重试,只要执行器集群有足够的计算节点,任务就能顺利完成。
适用场景
有定时执行需求的业务场景:如每天凌晨全量同步数据、生成业务报表、未支付订单超时取消等。
有需要全部机器一同执行的业务场景:如使用广播执行模式清理集群日志。
有需要分布式处理的业务场景:比如需要更新一大批数据,单机执行耗时非常长,可以使用Map/MapReduce 处理器完成任务的分发,调动整个集群加速计算。
有需要延迟执行某些任务的业务场景:比如订单过期处理等。
相关术语及定义
分组概念
appName:应用名称,建议与用户实际接入 ujob的应用名称保持一致,用于业务分组与隔离。一个 appName 等于一个业务集群,也就是实际的一个 Java 项目。
核心概念
任务(Job):描述了需要被 ujob调度的任务信息,包括任务名称、调度时间、处理器信息等。
任务实例( JobInstance,简称 Instance):任务(Job)被调度执行后会生成任务实例(Instance),任务实例记录了任务的运行时信息(任务与任务实例的关系类似于类与对象的关系)。
作业(Task):任务实例的执行单元,一个 JobInstance 存在至少一个 Task,具体规则如下: 单机任务(STANDALONE):一个 JobInstance 对应一个 Task 广播任务(BROADCAST):一个 JobInstance 对应 N 个 Task,N为集群机器数量,即每一台机器都会生成一个 Task Map/MapReduce任务:一个 JobInstance 对应若干个 Task,由开发者手动 map 产生
扩展概念
JVM 容器:以 Maven 工程项目的维度组织一堆 Java 文件(开发者开发的众多 Java 处理器),可以通过前端网页动态发布并被执行器加载,具有极强的扩展能力和灵活性。
OpenAPI:允许开发者通过接口来完成手工的操作,让系统整体变得更加灵活。开发者可以基于 API 便捷地扩展 UJob 原有的功能。
轻量级任务:单机执行且不需要以固定频率或者固定延迟执行的任务
重量级任务:非单机执行或者以固定频率/延迟执行的任务
定时任务类型
API -> 不需要填写任何参数,表明该任务由 OpenAPI 触发
CRON -> 填写 CRON 表达式
固定频率 -> 填写整数,单位毫秒
固定延迟 -> 填写整数,单位毫秒
接入配置
pom.xml 配置
xml
<dependency>
<groupId>com.cpit.ujob</groupId>
<artifactId>ujob-worker-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
application.yml 配置
properties
ujob:
worker:
enabled: true
# 工作端口,可选,默认 27777
port: 27777
# 接入应用名称,用于分组隔离,推荐填写 本 Java 项目名称
app-name: app1
# 调度服务器地址,IP:Port 或 域名,多值逗号分隔
server-address: 127.0.0.1:7700
# 协议
protocol: http
# 持久化方式
store-strategy: disk
# 任务返回结果信息的最大长度,超过这个长度的信息会被截断
max-result-length: 4096
# 单个任务追加的工作流上下文最大长度,超过这个长度的会被直接丢弃
max-appended-wf-context-length: 4096
docker-deploy: false
注册
应用注册
进入分布式调度登录页面(推荐使用谷歌浏览器)
点击“执行注册应用”进行应用注册。
注册的应用名称要与application.yml 中的 ujob.worker.app-name名称保持一致。
报警用户录入
点击“报警用户录入”进行报警用户注册。
填写报警用户信息,点击“注册”。
登录
在登录页面,输入应用名称、密码,点击“登录”按钮进入系统工作台主界面。
工作台
顶部统计任务总数、当前运行实例数、近期失败任务数及集群机器数。
表格中是连接到调度服务器的执行器信息。
右侧现在应用名称、文档地址、调度服务器时间及本地时间等信息。调度服务器时间与本地时间不一致可能导致调度错误。
任务管理
任务列表
点击左侧菜单“任务管理”,打开任务管理页面。
创建任务
点击“新增任务”按钮,打开新增任务弹窗,填写任务信息,点“保存”。
编辑任务
点击“任务列表-编辑”按钮,打开编辑任务弹窗,填写任务信息,点“保存”。
任务信息字段说明
任务名称:任务名称
任务描述:任务描述
任务参数:任务处理时能够获取到的参数
定时信息:该任务的触发方式(API、CRON、固定频率、固定延迟)
API -> 不需要填写任何参数,表明该任务由 OpenAPI 触发
CRON -> 填写 CRON 表达式
固定频率 -> 填写整数,单位毫秒
固定延迟 -> 填写整数,单位毫秒
生命周期:定时策略生效的时间段
执行配置:任务执行器核心配置(执行类型、处理器类型、类名路径)
运行配置:包括最大实例数、单机线程并发数、运行时间限制
重试配置:包括Instance 重试次数、Task 重试次数
机器配置:包括最低 CPU 核心数、最低内存(GB)、最低磁盘(GB)
集群配置:包括执行机器地址、最大执行机器数量
报警配置:包括报警通知人员、错误阈值、统计窗口、沉默窗口
单机处理器示例
简单示例
java
package com.cpit.ujob.samples.processors;
import com.cpit.ujob.worker.core.processor.ProcessResult;
import com.cpit.ujob.worker.core.processor.TaskContext;
import com.cpit.ujob.worker.core.processor.sdk.BasicProcessor;
import com.cpit.ujob.worker.log.OmsLogger;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Slf4j
@Component
public class SimpleProcessor implements BasicProcessor {
@Override
public ProcessResult process(TaskContext context) throws Exception {
OmsLogger logger = context.getOmsLogger();
String jobParams = Optional.ofNullable(context.getJobParams()).orElse("S");
logger.info("Current context:{}", context.getWorkflowContext());
logger.info("Current job params:{}", jobParams);
return jobParams.contains("F") ? new ProcessResult(false) : new ProcessResult(true, "yeah!");
}
}
执行配置:执行类型选择单机处理器,处理器类型选择内建,类名路径填写com.cpit.ujob.samples.processors.SimpleProcessor
延时示例
java
package com.cpit.ujob.samples.processors;
import lombok.extern.slf4j.Slf4j;
import com.cpit.ujob.worker.core.processor.ProcessResult;
import com.cpit.ujob.worker.core.processor.TaskContext;
import com.cpit.ujob.worker.core.processor.sdk.BasicProcessor;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class TimeoutProcessor implements BasicProcessor {
@Override
public ProcessResult process(TaskContext context) throws Exception {
long sleepTime = Long.parseLong(context.getJobParams());
log.info("TaskInstance({}) will sleep {} ms", context.getInstanceId(), sleepTime);
Thread.sleep(Long.parseLong(context.getJobParams()));
return new ProcessResult(true, "impossible~~~~QAQ~");
}
}
执行配置:执行类型选择单机处理器,处理器类型选择内建,类名路径填写com.cpit.ujob.samples.processors.TimeoutProcessor
注意:任务参数填写毫秒数。
广播处理器示例
java
package com.cpit.ujob.samples.processors;
import com.cpit.ujob.common.utils.NetUtils;
import com.cpit.ujob.worker.core.processor.ProcessResult;
import com.cpit.ujob.worker.core.processor.TaskContext;
import com.cpit.ujob.worker.core.processor.TaskResult;
import com.cpit.ujob.worker.core.processor.sdk.BroadcastProcessor;
import com.cpit.ujob.worker.log.OmsLogger;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
public class BroadcastProcessorDemo implements BroadcastProcessor {
@Override
public ProcessResult preProcess(TaskContext context) {
System.out.println("===== BroadcastProcessorDemo#preProcess ======");
context.getOmsLogger().info("BroadcastProcessorDemo#preProcess, current host: {}", NetUtils.getLocalHost());
if ("rootFailed".equals(context.getJobParams())) {
return new ProcessResult(false, "console need failed");
} else {
return new ProcessResult(true);
}
}
@Override
public ProcessResult process(TaskContext taskContext) throws Exception {
OmsLogger logger = taskContext.getOmsLogger();
System.out.println("===== BroadcastProcessorDemo#process ======");
logger.info("BroadcastProcessorDemo#process, current host: {}", NetUtils.getLocalHost());
long sleepTime = 1000;
try {
sleepTime = Long.parseLong(taskContext.getJobParams());
} catch (Exception e) {
logger.warn("[BroadcastProcessor] parse sleep time failed!", e);
}
Thread.sleep(Math.max(sleepTime, 1000));
return new ProcessResult(true);
}
@Override
public ProcessResult postProcess(TaskContext context, List<TaskResult> taskResults) {
System.out.println("===== BroadcastProcessorDemo#postProcess ======");
context.getOmsLogger().info("BroadcastProcessorDemo#postProcess, current host: {}, taskResult: {}", NetUtils.getLocalHost(), taskResults);
return new ProcessResult(true, "success");
}
}
执行配置:执行类型选择单机处理器,处理器类型选择内建,类名路径填写com.cpit.ujob.samples.processors.BroadcastProcessorDemo
Map处理器示例
java
package com.cpit.ujob.samples.processors;
import com.cpit.ujob.common.serialize.JsonUtils;
import com.cpit.ujob.samples.MysteryService;
import com.cpit.ujob.worker.core.processor.ProcessResult;
import com.cpit.ujob.worker.core.processor.TaskContext;
import com.cpit.ujob.worker.core.processor.sdk.MapProcessor;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
@Component
public class MapProcessorDemo implements MapProcessor {
@Resource
private MysteryService mysteryService;
/**
* 每一批发送任务大小
*/
private static final int BATCH_SIZE = 100;
/**
* 发送的批次
*/
private static final int BATCH_NUM = 5;
@Override
public ProcessResult process(TaskContext context) throws Exception {
log.info("============== MapProcessorDemo#process ==============");
log.info("isRootTask:{}", isRootTask());
log.info("taskContext:{}", JsonUtils.toJSONString(context));
log.info("{}", mysteryService.hasaki());
if (isRootTask()) {
log.info("==== MAP ====");
List<SubTask> subTasks = Lists.newLinkedList();
for (int j = 0; j < BATCH_NUM; j++) {
SubTask subTask = new SubTask();
subTask.siteId = j;
subTask.itemIds = Lists.newLinkedList();
subTasks.add(subTask);
for (int i = 0; i < BATCH_SIZE; i++) {
subTask.itemIds.add(i + j * 100);
}
}
map(subTasks, "MAP_TEST_TASK");
return new ProcessResult(true, "map successfully");
} else {
log.info("==== PROCESS ====");
SubTask subTask = (SubTask) context.getSubTask();
for (Integer itemId : subTask.getItemIds()) {
if (Thread.interrupted()) {
// 任务被中断
log.info("job has been stop! so stop to process subTask: {} => {}", subTask.getSiteId(), itemId);
break;
}
log.info("processing subTask: {} => {}", subTask.getSiteId(), itemId);
int max = Integer.MAX_VALUE >> 7;
for (int i = 0; ; i++) {
// 模拟耗时操作
if (i > max) {
break;
}
}
}
// 测试在 Map 任务中追加上下文
context.getWorkflowContext().appendData2WfContext("Yasuo", "A sword's poor company for a long road.");
boolean b = ThreadLocalRandom.current().nextBoolean();
if (context.getCurrentRetryTimes() >= 1) {
// 重试的话一定会成功
b = true;
}
return new ProcessResult(b, "RESULT:" + b);
}
}
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class SubTask {
private Integer siteId;
private List<Integer> itemIds;
}
}
执行配置:执行类型选择单机处理器,处理器类型选择内建,类名路径填写com.cpit.ujob.samples.processors.MapProcessorDemo
MapReduce处理器示例
java
package com.cpit.ujob.samples.processors;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cpit.ujob.common.serialize.JsonUtils;
import com.cpit.ujob.worker.core.processor.ProcessResult;
import com.cpit.ujob.worker.core.processor.TaskContext;
import com.cpit.ujob.worker.core.processor.TaskResult;
import com.cpit.ujob.worker.core.processor.sdk.MapReduceProcessor;
import com.cpit.ujob.worker.log.OmsLogger;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
@Slf4j
@Component
public class MapReduceProcessorDemo implements MapReduceProcessor {
@Override
public ProcessResult process(TaskContext context) throws Exception {
OmsLogger omsLogger = context.getOmsLogger();
log.info("============== TestMapReduceProcessor#process ==============");
log.info("isRootTask:{}", isRootTask());
log.info("taskContext:{}", JsonUtils.toJSONString(context));
// 根据控制台参数获取MR批次及子任务大小
final JSONObject jobParams = JSONObject.parseObject(context.getJobParams());
Integer batchSize = (Integer) jobParams.getOrDefault("batchSize", 100);
Integer batchNum = (Integer) jobParams.getOrDefault("batchNum", 10);
if (isRootTask()) {
log.info("==== MAP ====");
omsLogger.info("[DemoMRProcessor] start root task~");
List<TestSubTask> subTasks = Lists.newLinkedList();
for (int j = 0; j < batchNum; j++) {
for (int i = 0; i < batchSize; i++) {
int x = j * batchSize + i;
subTasks.add(new TestSubTask("name" + x, x));
}
map(subTasks, "MAP_TEST_TASK");
subTasks.clear();
}
omsLogger.info("[DemoMRProcessor] map success~");
return new ProcessResult(true, "MAP_SUCCESS");
} else {
log.info("==== NORMAL_PROCESS ====");
omsLogger.info("[DemoMRProcessor] process subTask: {}.", JSON.toJSONString(context.getSubTask()));
log.info("subTask: {}", JsonUtils.toJSONString(context.getSubTask()));
Thread.sleep(1000);
if (context.getCurrentRetryTimes() == 0) {
return new ProcessResult(false, "FIRST_FAILED");
} else {
return new ProcessResult(true, "PROCESS_SUCCESS");
}
}
}
@Override
public ProcessResult reduce(TaskContext context, List<TaskResult> taskResults) {
log.info("================ MapReduceProcessorDemo#reduce ================");
log.info("TaskContext: {}", JSONObject.toJSONString(context));
log.info("List<TaskResult>: {}", JSONObject.toJSONString(taskResults));
context.getOmsLogger().info("MapReduce job finished, result is {}.", taskResults);
boolean success = ThreadLocalRandom.current().nextBoolean();
return new ProcessResult(success, context + ": " + success);
}
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public static class TestSubTask {
private String name;
private int age;
}
}
执行配置:执行类型选择单机处理器,处理器类型选择内建,类名路径填写com.cpit.ujob.samples.processors.MapProcessorDemo
OpenApi
定时信息-时间表达式选择API类型
java
package com.cpit.ujob.samples.controller;
import com.cpit.ujob.client.UJobClient;
import com.cpit.ujob.common.enums.DispatchStrategy;
import com.cpit.ujob.common.enums.ExecuteType;
import com.cpit.ujob.common.enums.ProcessorType;
import com.cpit.ujob.common.enums.TimeExpressionType;
import com.cpit.ujob.common.model.AlarmConfig;
import com.cpit.ujob.common.model.LogConfig;
import com.cpit.ujob.common.request.http.SaveJobInfoRequest;
import com.cpit.ujob.common.response.InstanceInfoDTO;
import com.cpit.ujob.common.response.JobInfoDTO;
import com.cpit.ujob.common.response.ResultDTO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/demo")
public class DemoController {
// 根据jobId查询任务详情
@GetMapping(value = "/detailJob")
public ResultDTO jobdetails(){
// 初始化 client,需要server地址和应用名称作为参数
UJobClient client = new UJobClient("127.0.0.1:7700", "app1", "123456");
// 参数任务id
ResultDTO<JobInfoDTO> jobInfoDTOResultDTO = client.fetchJob(351L);
return jobInfoDTOResultDTO;
}
// 创建/修改任务
@GetMapping(value = "/editJob")
public void editJob(){
// 初始化 client,需要server地址和应用名称作为参数
UJobClient client = new UJobClient("127.0.0.1:7700", "app1", "123456");
SaveJobInfoRequest request =new SaveJobInfoRequest();
AlarmConfig config = new AlarmConfig();
config.setAlertThreshold(0);
config.setSilenceWindowLen(0);
config.setStatisticWindowLen(0);
request.setAlarmConfig(config);
request.setAppId(347L);
request.setDesignatedWorkers("");
request.setConcurrency(5);
request.setDispatchStrategy(DispatchStrategy.HEALTH_FIRST);
request.setEnable(false);
request.setExecuteType(ExecuteType.STANDALONE);
request.setExtra(null);
request.setInstanceRetryNum(0);
request.setInstanceTimeLimit(0L);
request.setJobDescription("修改定时任务修改定时任务修改定时任务修改定时任务");
request.setJobName("修改定时任务");
request.setJobParams("10000");
request.setLifeCycle(null);
LogConfig logconfig = new LogConfig();
logconfig.setType(null);
logconfig.setLevel(null);
logconfig.setLoggerName(null);
request.setLogConfig(logconfig);
request.setMaxInstanceNum(0);
request.setMaxWorkerCount(0);
request.setMinCpuCores(0);
request.setMinDiskSpace(0);
request.setMinCpuCores(0);
request.setProcessorInfo("com.cpit.ujob.samples.processors.TimeoutProcessor");
request.setProcessorType(ProcessorType.BUILT_IN);
request.setTag(null);
request.setTaskRetryNum(1);
request.setTimeExpression("3/20 * * * * ? ");
request.setTimeExpressionType(TimeExpressionType.CRON);
request.setId(355L);
client.saveJob(request);
}
@GetMapping(value = "/addJob")
public void addJob(){
// 初始化 client,需要server地址和应用名称作为参数
UJobClient client = new UJobClient("127.0.0.1:7700", "app1", "123456");
SaveJobInfoRequest request =new SaveJobInfoRequest();
AlarmConfig config = new AlarmConfig();
config.setAlertThreshold(0);
config.setSilenceWindowLen(0);
config.setStatisticWindowLen(0);
request.setAlarmConfig(config);
request.setAppId(347L);
request.setDesignatedWorkers("");
request.setConcurrency(5);
request.setDispatchStrategy(DispatchStrategy.HEALTH_FIRST);
request.setEnable(false);
request.setExecuteType(ExecuteType.STANDALONE);
request.setExtra(null);
request.setInstanceRetryNum(0);
request.setInstanceTimeLimit(0L);
request.setJobDescription("新增定时任务新增定时任务新增定时任务新增定时任务");
request.setJobName("新增定时任务");
request.setJobParams("10000");
request.setLifeCycle(null);
LogConfig logconfig = new LogConfig();
logconfig.setType(null);
logconfig.setLevel(null);
logconfig.setLoggerName(null);
request.setLogConfig(logconfig);
request.setMaxInstanceNum(0);
request.setMaxWorkerCount(0);
request.setMinCpuCores(0);
request.setMinDiskSpace(0);
request.setMinCpuCores(0);
request.setProcessorInfo("com.cpit.ujob.samples.processors.TimeoutProcessor");
request.setProcessorType(ProcessorType.BUILT_IN);
request.setTag(null);
request.setTaskRetryNum(1);
request.setTimeExpression("3/20 * * * * ? ");
request.setTimeExpressionType(TimeExpressionType.CRON);
client.saveJob(request);
}
// 启用任务 参数任务id
@GetMapping(value = "/enableJob")
public void enableJob(){
// 初始化 client,需要server地址和应用名称作为参数
UJobClient client = new UJobClient("127.0.0.1:7700", "app1", "123456");
// 参数任务id
client.enableJob(351L);
}
// 禁用任务
@GetMapping(value = "/disableJob")
public void disableJob(){
// 初始化 client,需要server地址和应用名称作为参数
UJobClient client = new UJobClient("127.0.0.1:7700", "app1", "123456");
// 参数任务id
client.disableJob(351L);
}
// 删除任务
@GetMapping(value = "/deleteJob")
public void deleteJob(){
// 初始化 client,需要server地址和应用名称作为参数
UJobClient client = new UJobClient("127.0.0.1:7700", "app1", "123456");
// 参数任务id
client.deleteJob(351L);
}
// 运行任务
@GetMapping(value = "/runJob")
public void runJob(){
// 初始化 client,需要server地址和应用名称作为参数
UJobClient client = new UJobClient("127.0.0.1:7700", "app1", "123456");
// 参数任务id 任务参数 延迟时间
client.runJob(351L,"{code:0}",1000);
}
// 取消某个定时任务实例
@GetMapping(value = "/cancelInstance")
public void cancelInstance(){
// 初始化 client,需要server地址和应用名称作为参数
UJobClient client = new UJobClient("127.0.0.1:7700", "app1", "123456");
// 任务实例id
client.cancelInstance(690855948855552704L);
}
// 停止某个任务实例
@GetMapping(value = "/stopInstance")
public void stopInstance(){
// 初始化 client,需要server地址和应用名称作为参数
UJobClient client = new UJobClient("127.0.0.1:7700", "app1", "123456");
// 任务实例id
client.stopInstance(690855948855552704L);
}
// 查询某个任务实例
@GetMapping(value = "/fetchInstanceInfo")
public ResultDTO fetchInstanceInfo(){
// 初始化 client,需要server地址和应用名称作为参数
UJobClient client = new UJobClient("127.0.0.1:7700", "app1", "123456");
// 任务实例id
ResultDTO<InstanceInfoDTO> instanceInfoDTOResultDTO = client.fetchInstanceInfo(690855948855552704L);
return instanceInfoDTOResultDTO;
}
// 查询某个任务实例的状态
@GetMapping(value = "/fetchInstanceStatus")
public ResultDTO fetchInstanceStatus(){
// 初始化 client,需要server地址和应用名称作为参数
UJobClient client = new UJobClient("127.0.0.1:7700", "app1", "123456");
// 任务实例id
return client.fetchInstanceStatus(690916403582085824L);
}
}
运行任务
点击任务列表中“运行”按钮,立即运行本任务。
运行状态
任务被关闭后不再被调度。
参数运行
点击任务列表中”更多-运行参数”按钮,填写参数运行,点击“保存”。
在“任务实例列表-详情”中可以查看任务实例参数。
运行记录
点击任务列表中”更多-运行记录”按钮,跳转到任务实例列表中查看当前的任务实例信息。
复制
点击任务列表中”更多-复制”按钮,复制当前任务。
删除
点击任务列表中”更多-删除”按钮,删除当前任务。
任务实例
任务实例列表
点击左侧菜单“任务实例”,打开任务实例页面。
详情
点击任务实例列表中“详情”按钮,查看当前任务详细运行信息。
日志
点击任务实例列表中“日志”按钮,查看当前任务实例日志。
重试
点击任务实例列表中“重试”按钮,对任务实例进行重试。
停止
点击任务实例列表中“停止”按钮,对正在运行的任务实例进行停止。