mirror of
https://github.com/PowerJob/PowerJob.git
synced 2025-07-17 00:00:04 +08:00
[dev] develop the dag converter
This commit is contained in:
parent
5a7af55cef
commit
897a493a5e
@ -7,7 +7,7 @@ import lombok.NoArgsConstructor;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Point & Line DAG 表示法
|
* Point & Edge DAG 表示法
|
||||||
* 点 + 线,易于表达和传播
|
* 点 + 线,易于表达和传播
|
||||||
*
|
*
|
||||||
* @author tjq
|
* @author tjq
|
||||||
@ -16,7 +16,7 @@ import java.util.List;
|
|||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class PLWorkflowDAG {
|
public class PEWorkflowDAG {
|
||||||
|
|
||||||
// DAG 图(点线表示法)
|
// DAG 图(点线表示法)
|
||||||
private List<Node> nodes;
|
private List<Node> nodes;
|
@ -1,5 +1,6 @@
|
|||||||
package com.github.kfcfans.oms.common.model;
|
package com.github.kfcfans.oms.common.model;
|
||||||
|
|
||||||
|
import com.github.kfcfans.oms.common.InstanceStatus;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@ -28,5 +29,10 @@ public class WorkflowDAG {
|
|||||||
private List<Node> successors;
|
private List<Node> successors;
|
||||||
private Long jobId;
|
private Long jobId;
|
||||||
private String jobName;
|
private String jobName;
|
||||||
|
|
||||||
|
// 运行时参数
|
||||||
|
private Long instanceId;
|
||||||
|
private InstanceStatus status;
|
||||||
|
private String result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package com.github.kfcfans.oms.common.request.http;
|
package com.github.kfcfans.oms.common.request.http;
|
||||||
|
|
||||||
import com.github.kfcfans.oms.common.model.PLWorkflowDAG;
|
import com.github.kfcfans.oms.common.model.PEWorkflowDAG;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,7 +21,7 @@ public class SaveWorkflowRequest {
|
|||||||
private Long appId;
|
private Long appId;
|
||||||
|
|
||||||
// 点线表示法
|
// 点线表示法
|
||||||
private PLWorkflowDAG plWorkflowDAG;
|
private PEWorkflowDAG pEWorkflowDAG;
|
||||||
|
|
||||||
/* ************************** 定时参数 ************************** */
|
/* ************************** 定时参数 ************************** */
|
||||||
// 时间表达式类型(CRON/API/FIX_RATE/FIX_DELAY)
|
// 时间表达式类型(CRON/API/FIX_RATE/FIX_DELAY)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package com.github.kfcfans.oms.common.utils;
|
package com.github.kfcfans.oms.common.utils;
|
||||||
|
|
||||||
import com.github.kfcfans.oms.common.OmsException;
|
import com.github.kfcfans.oms.common.OmsException;
|
||||||
import com.github.kfcfans.oms.common.model.PLWorkflowDAG;
|
import com.github.kfcfans.oms.common.model.PEWorkflowDAG;
|
||||||
import com.github.kfcfans.oms.common.model.WorkflowDAG;
|
import com.github.kfcfans.oms.common.model.WorkflowDAG;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
@ -20,21 +20,21 @@ public class WorkflowDAGUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 将点线表示法的DAG图转化为引用表达法的DAG图
|
* 将点线表示法的DAG图转化为引用表达法的DAG图
|
||||||
* @param plWorkflowDAG 点线表示法的DAG图
|
* @param PEWorkflowDAG 点线表示法的DAG图
|
||||||
* @return 引用表示法的DAG图
|
* @return 引用表示法的DAG图
|
||||||
*/
|
*/
|
||||||
public static WorkflowDAG convert(PLWorkflowDAG plWorkflowDAG) {
|
public static WorkflowDAG convert(PEWorkflowDAG PEWorkflowDAG) {
|
||||||
Set<Long> rootIds = Sets.newHashSet();
|
Set<Long> rootIds = Sets.newHashSet();
|
||||||
Map<Long, WorkflowDAG.Node> id2Node = Maps.newHashMap();
|
Map<Long, WorkflowDAG.Node> id2Node = Maps.newHashMap();
|
||||||
|
|
||||||
if (plWorkflowDAG.getNodes() == null || plWorkflowDAG.getNodes().isEmpty()) {
|
if (PEWorkflowDAG.getNodes() == null || PEWorkflowDAG.getNodes().isEmpty()) {
|
||||||
throw new OmsException("empty graph");
|
throw new OmsException("empty graph");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建节点
|
// 创建节点
|
||||||
plWorkflowDAG.getNodes().forEach(node -> {
|
PEWorkflowDAG.getNodes().forEach(node -> {
|
||||||
Long jobId = node.getJobId();
|
Long jobId = node.getJobId();
|
||||||
WorkflowDAG.Node n = new WorkflowDAG.Node(Lists.newLinkedList(), jobId, node.getJobName());
|
WorkflowDAG.Node n = new WorkflowDAG.Node(Lists.newLinkedList(), jobId, node.getJobName(), null, null, null);
|
||||||
id2Node.put(jobId, n);
|
id2Node.put(jobId, n);
|
||||||
|
|
||||||
// 初始阶段,每一个点都设为顶点
|
// 初始阶段,每一个点都设为顶点
|
||||||
@ -42,7 +42,7 @@ public class WorkflowDAGUtils {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 连接图像
|
// 连接图像
|
||||||
plWorkflowDAG.getEdges().forEach(edge -> {
|
PEWorkflowDAG.getEdges().forEach(edge -> {
|
||||||
WorkflowDAG.Node from = id2Node.get(edge.getFrom());
|
WorkflowDAG.Node from = id2Node.get(edge.getFrom());
|
||||||
WorkflowDAG.Node to = id2Node.get(edge.getTo());
|
WorkflowDAG.Node to = id2Node.get(edge.getTo());
|
||||||
|
|
||||||
@ -58,10 +58,41 @@ public class WorkflowDAGUtils {
|
|||||||
|
|
||||||
// 合法性校验
|
// 合法性校验
|
||||||
if (rootIds.size() != 1) {
|
if (rootIds.size() != 1) {
|
||||||
throw new OmsException("Illegal DAG Graph: " + JsonUtils.toJSONString(plWorkflowDAG));
|
throw new OmsException("Illegal DAG Graph: " + JsonUtils.toJSONString(PEWorkflowDAG));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new WorkflowDAG(id2Node.get(rootIds.iterator().next()));
|
return new WorkflowDAG(id2Node.get(rootIds.iterator().next()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验 DAG 是否有效
|
||||||
|
* @param peWorkflowDAG 点线表示法的 DAG 图
|
||||||
|
* @return true/false
|
||||||
|
*/
|
||||||
|
public static boolean valid(PEWorkflowDAG peWorkflowDAG) {
|
||||||
|
try {
|
||||||
|
WorkflowDAG workflowDAG = convert(peWorkflowDAG);
|
||||||
|
return check(workflowDAG.getRoot(), Sets.newHashSet());
|
||||||
|
}catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean check(WorkflowDAG.Node root, Set<Long> ids) {
|
||||||
|
|
||||||
|
// 递归出口(出现之前的节点则代表有环,失败;出现无后继者节点,则说明该路径成功)
|
||||||
|
if (ids.contains(root.getJobId())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (root.getSuccessors().isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ids.add(root.getJobId());
|
||||||
|
for (WorkflowDAG.Node node : root.getSuccessors()) {
|
||||||
|
if (!check(node, Sets.newHashSet(ids))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ public class WorkflowInfoDO {
|
|||||||
private Long appId;
|
private Long appId;
|
||||||
|
|
||||||
// 工作流的DAG图信息(点线式DAG的json)
|
// 工作流的DAG图信息(点线式DAG的json)
|
||||||
private String plDAG;
|
private String peDAG;
|
||||||
|
|
||||||
/* ************************** 定时参数 ************************** */
|
/* ************************** 定时参数 ************************** */
|
||||||
// 时间表达式类型(CRON/API/FIX_RATE/FIX_DELAY)
|
// 时间表达式类型(CRON/API/FIX_RATE/FIX_DELAY)
|
||||||
|
@ -3,6 +3,8 @@ package com.github.kfcfans.oms.server.persistence.core.repository;
|
|||||||
import com.github.kfcfans.oms.server.persistence.core.model.WorkflowInfoDO;
|
import com.github.kfcfans.oms.server.persistence.core.model.WorkflowInfoDO;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DAG 工作流 数据操作层
|
* DAG 工作流 数据操作层
|
||||||
*
|
*
|
||||||
@ -10,4 +12,8 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||||||
* @since 2020/5/26
|
* @since 2020/5/26
|
||||||
*/
|
*/
|
||||||
public interface WorkflowInfoRepository extends JpaRepository<WorkflowInfoDO, Long> {
|
public interface WorkflowInfoRepository extends JpaRepository<WorkflowInfoDO, Long> {
|
||||||
|
|
||||||
|
|
||||||
|
List<WorkflowInfoDO> findByAppIdInAndStatusAndTimeExpressionTypeAndNextTriggerTimeLessThanEqual(List<Long> appIds, int status, int timeExpressionType, long time);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
package com.github.kfcfans.oms.server.service.timing.schedule;
|
package com.github.kfcfans.oms.server.service.timing.schedule;
|
||||||
|
|
||||||
import com.github.kfcfans.oms.common.InstanceStatus;
|
import com.github.kfcfans.oms.common.InstanceStatus;
|
||||||
import com.github.kfcfans.oms.server.common.constans.JobStatus;
|
|
||||||
import com.github.kfcfans.oms.common.TimeExpressionType;
|
import com.github.kfcfans.oms.common.TimeExpressionType;
|
||||||
import com.github.kfcfans.oms.server.common.utils.CronExpression;
|
|
||||||
import com.github.kfcfans.oms.server.service.JobService;
|
|
||||||
import com.github.kfcfans.oms.server.akka.OhMyServer;
|
import com.github.kfcfans.oms.server.akka.OhMyServer;
|
||||||
|
import com.github.kfcfans.oms.server.common.constans.JobStatus;
|
||||||
|
import com.github.kfcfans.oms.server.common.utils.CronExpression;
|
||||||
import com.github.kfcfans.oms.server.persistence.core.model.AppInfoDO;
|
import com.github.kfcfans.oms.server.persistence.core.model.AppInfoDO;
|
||||||
import com.github.kfcfans.oms.server.persistence.core.model.InstanceInfoDO;
|
import com.github.kfcfans.oms.server.persistence.core.model.InstanceInfoDO;
|
||||||
import com.github.kfcfans.oms.server.persistence.core.model.JobInfoDO;
|
import com.github.kfcfans.oms.server.persistence.core.model.JobInfoDO;
|
||||||
|
import com.github.kfcfans.oms.server.persistence.core.model.WorkflowInfoDO;
|
||||||
import com.github.kfcfans.oms.server.persistence.core.repository.AppInfoRepository;
|
import com.github.kfcfans.oms.server.persistence.core.repository.AppInfoRepository;
|
||||||
import com.github.kfcfans.oms.server.persistence.core.repository.JobInfoRepository;
|
|
||||||
import com.github.kfcfans.oms.server.persistence.core.repository.InstanceInfoRepository;
|
import com.github.kfcfans.oms.server.persistence.core.repository.InstanceInfoRepository;
|
||||||
|
import com.github.kfcfans.oms.server.persistence.core.repository.JobInfoRepository;
|
||||||
|
import com.github.kfcfans.oms.server.persistence.core.repository.WorkflowInfoRepository;
|
||||||
import com.github.kfcfans.oms.server.service.DispatchService;
|
import com.github.kfcfans.oms.server.service.DispatchService;
|
||||||
import com.github.kfcfans.oms.server.service.id.IdGenerateService;
|
import com.github.kfcfans.oms.server.service.JobService;
|
||||||
import com.github.kfcfans.oms.server.service.ha.WorkerManagerService;
|
import com.github.kfcfans.oms.server.service.ha.WorkerManagerService;
|
||||||
|
import com.github.kfcfans.oms.server.service.id.IdGenerateService;
|
||||||
import com.google.common.base.Stopwatch;
|
import com.google.common.base.Stopwatch;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
@ -43,7 +45,7 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class JobScheduleService {
|
public class OmsScheduleService {
|
||||||
|
|
||||||
private static final int MAX_BATCH_NUM = 10;
|
private static final int MAX_BATCH_NUM = 10;
|
||||||
|
|
||||||
@ -56,6 +58,8 @@ public class JobScheduleService {
|
|||||||
@Resource
|
@Resource
|
||||||
private JobInfoRepository jobInfoRepository;
|
private JobInfoRepository jobInfoRepository;
|
||||||
@Resource
|
@Resource
|
||||||
|
private WorkflowInfoRepository workflowInfoRepository;
|
||||||
|
@Resource
|
||||||
private InstanceInfoRepository instanceInfoRepository;
|
private InstanceInfoRepository instanceInfoRepository;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
@ -188,6 +192,19 @@ public class JobScheduleService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void scheduleWorkflow(List<Long> appIds) {
|
||||||
|
|
||||||
|
long nowTime = System.currentTimeMillis();
|
||||||
|
long timeThreshold = nowTime + 2 * SCHEDULE_RATE;
|
||||||
|
Lists.partition(appIds, MAX_BATCH_NUM).forEach(partAppIds -> {
|
||||||
|
List<WorkflowInfoDO> wfInfos = workflowInfoRepository.findByAppIdInAndStatusAndTimeExpressionTypeAndNextTriggerTimeLessThanEqual(partAppIds, JobStatus.ENABLE.getV(), TimeExpressionType.CRON.getV(), timeThreshold);
|
||||||
|
|
||||||
|
if (CollectionUtils.isEmpty(wfInfos)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void scheduleFrequentJob(List<Long> appIds) {
|
private void scheduleFrequentJob(List<Long> appIds) {
|
||||||
|
|
||||||
Lists.partition(appIds, MAX_BATCH_NUM).forEach(partAppIds -> {
|
Lists.partition(appIds, MAX_BATCH_NUM).forEach(partAppIds -> {
|
||||||
@ -216,4 +233,5 @@ public class JobScheduleService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.github.kfcfans.oms.server.service.workflow;
|
||||||
|
|
||||||
|
import com.github.kfcfans.oms.server.persistence.core.model.WorkflowInfoDO;
|
||||||
|
import com.github.kfcfans.oms.server.persistence.core.repository.JobInfoRepository;
|
||||||
|
import com.github.kfcfans.oms.server.persistence.core.repository.WorkflowInfoRepository;
|
||||||
|
import com.github.kfcfans.oms.server.service.DispatchService;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理运行中的工作流实例
|
||||||
|
*
|
||||||
|
* @author tjq
|
||||||
|
* @since 2020/5/26
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class WorkflowInstanceManager {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DispatchService dispatchService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private JobInfoRepository jobInfoRepository;
|
||||||
|
@Resource
|
||||||
|
private WorkflowInfoRepository workflowInfoRepository;
|
||||||
|
|
||||||
|
|
||||||
|
private final Map<Long, WorkflowInfoDO> wfId2Info = Maps.newConcurrentMap();
|
||||||
|
|
||||||
|
public void submit(WorkflowInfoDO wfInfo) {
|
||||||
|
wfId2Info.put(wfInfo.getId(), wfInfo);
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
package com.github.kfcfans.oms.server.service.workflow;
|
package com.github.kfcfans.oms.server.service.workflow;
|
||||||
|
|
||||||
|
import com.github.kfcfans.oms.common.OmsException;
|
||||||
import com.github.kfcfans.oms.common.TimeExpressionType;
|
import com.github.kfcfans.oms.common.TimeExpressionType;
|
||||||
import com.github.kfcfans.oms.common.request.http.SaveWorkflowRequest;
|
import com.github.kfcfans.oms.common.request.http.SaveWorkflowRequest;
|
||||||
import com.github.kfcfans.oms.common.utils.JsonUtils;
|
import com.github.kfcfans.oms.common.utils.JsonUtils;
|
||||||
|
import com.github.kfcfans.oms.common.utils.WorkflowDAGUtils;
|
||||||
import com.github.kfcfans.oms.server.common.utils.CronExpression;
|
import com.github.kfcfans.oms.server.common.utils.CronExpression;
|
||||||
import com.github.kfcfans.oms.server.persistence.core.model.WorkflowInfoDO;
|
import com.github.kfcfans.oms.server.persistence.core.model.WorkflowInfoDO;
|
||||||
import com.github.kfcfans.oms.server.persistence.core.repository.WorkflowInfoRepository;
|
import com.github.kfcfans.oms.server.persistence.core.repository.WorkflowInfoRepository;
|
||||||
@ -32,6 +34,10 @@ public class WorkflowService {
|
|||||||
*/
|
*/
|
||||||
public Long saveWorkflow(SaveWorkflowRequest req) throws Exception {
|
public Long saveWorkflow(SaveWorkflowRequest req) throws Exception {
|
||||||
|
|
||||||
|
if (!WorkflowDAGUtils.valid(req.getPEWorkflowDAG())) {
|
||||||
|
throw new OmsException("illegal DAG");
|
||||||
|
}
|
||||||
|
|
||||||
Long wfId = req.getId();
|
Long wfId = req.getId();
|
||||||
WorkflowInfoDO wf;
|
WorkflowInfoDO wf;
|
||||||
if (wfId == null) {
|
if (wfId == null) {
|
||||||
@ -43,7 +49,7 @@ public class WorkflowService {
|
|||||||
|
|
||||||
BeanUtils.copyProperties(req, wf);
|
BeanUtils.copyProperties(req, wf);
|
||||||
wf.setGmtModified(new Date());
|
wf.setGmtModified(new Date());
|
||||||
wf.setPlDAG(JsonUtils.toJSONString(req.getPlWorkflowDAG()));
|
wf.setPeDAG(JsonUtils.toJSONString(req.getPEWorkflowDAG()));
|
||||||
|
|
||||||
// 计算 NextTriggerTime
|
// 计算 NextTriggerTime
|
||||||
TimeExpressionType timeExpressionType = TimeExpressionType.of(req.getTimeExpressionType());
|
TimeExpressionType timeExpressionType = TimeExpressionType.of(req.getTimeExpressionType());
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.github.kfcfans.oms.server.web;
|
package com.github.kfcfans.oms.server.web;
|
||||||
|
|
||||||
|
import com.github.kfcfans.oms.common.OmsException;
|
||||||
import com.github.kfcfans.oms.common.response.ResultDTO;
|
import com.github.kfcfans.oms.common.response.ResultDTO;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
@ -21,7 +22,7 @@ public class ControllerExceptionHandler {
|
|||||||
public ResultDTO<Void> exceptionHandler(Exception e) {
|
public ResultDTO<Void> exceptionHandler(Exception e) {
|
||||||
|
|
||||||
// 不是所有异常都需要打印完整堆栈,后续可以定义内部的Exception,便于判断
|
// 不是所有异常都需要打印完整堆栈,后续可以定义内部的Exception,便于判断
|
||||||
if (e instanceof IllegalArgumentException) {
|
if (e instanceof IllegalArgumentException || e instanceof OmsException) {
|
||||||
log.warn("[ControllerException] http request failed, message is {}.", e.getMessage());
|
log.warn("[ControllerException] http request failed, message is {}.", e.getMessage());
|
||||||
}else {
|
}else {
|
||||||
log.error("[ControllerException] http request failed.", e);
|
log.error("[ControllerException] http request failed.", e);
|
||||||
|
@ -24,7 +24,7 @@ public class WorkflowController {
|
|||||||
private WorkflowService workflowService;
|
private WorkflowService workflowService;
|
||||||
|
|
||||||
@PostMapping("/save")
|
@PostMapping("/save")
|
||||||
public ResultDTO<Long> save(@RequestBody SaveWorkflowRequest req) {
|
public ResultDTO<Long> save(@RequestBody SaveWorkflowRequest req) throws Exception {
|
||||||
return ResultDTO.success(workflowService.saveWorkflow(req));
|
return ResultDTO.success(workflowService.saveWorkflow(req));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user