背景
公司打算使用 dolphinscheduler 框架作为中台项目使用,因为其中任务调度,工作流,数据源,资源中心等功能比较健全,可以用其统一好好管理公司的资源。
既然要使用 dolphinscheduler,第一步当然是要看看其源码,了解它的架构设计,方便后续扩展开发啦。
ds架构源码解析
整体流程图:
API 模块
一 command 生成
整体逻辑图为:
ProcessScheduleJob 放入 quartz 中,核心执行代码即是 生成 command
public class ProcessScheduleJob extends QuartzJobBean {
private static final Logger logger = LoggerFactory.getLogger(ProcessScheduleJob.class);
@Autowired
private ProcessService processService;
@Autowired
private QuartzExecutor quartzExecutor;
@Counted(value = "quartz_job_executed")
@Timed(value = "quartz_job_execution", percentiles = {0.5, 0.75, 0.95, 0.99}, histogram = true)
@Override
protected void executeInternal(JobExecutionContext context) {
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
int projectId = dataMap.getInt(Constants.PROJECT_ID);
int scheduleId = dataMap.getInt(Constants.SCHEDULE_ID);
Date scheduledFireTime = context.getScheduledFireTime();
Date fireTime = context.getFireTime();
logger.info("scheduled fire time :{}, fire time :{}, process id :{}", scheduledFireTime, fireTime, scheduleId);
// query schedule
Schedule schedule = processService.querySchedule(scheduleId);
if (schedule == null || ReleaseState.OFFLINE == schedule.getReleaseState()) {
logger.warn("process schedule does not exist in db or process schedule offline,delete schedule job in quartz, projectId:{}, scheduleId:{}", projectId, scheduleId); deleteJob(context, projectId, scheduleId); return; }
ProcessDefinition processDefinition = processService.findProcessDefinitionByCode(schedule.getProcessDefinitionCode());
// release state : online/offline
ReleaseState releaseState = processDefinition.getReleaseState();
if (releaseState == ReleaseState.OFFLINE) {
logger.warn("process definition does not exist in db or offline,need not to create command, projectId:{}, processId:{}", projectId, processDefinition.getId());
return;
}
Command command = new Command();
command.setCommandType(CommandType.SCHEDULER);
command.setExecutorId(schedule.getUserId());
command.setFailureStrategy(schedule.getFailureStrategy());
command.setProcessDefinitionCode(schedule.getProcessDefinitionCode());
command.setScheduleTime(scheduledFireTime); command.setStartTime(fireTime);
command.setWarningGroupId(schedule.getWarningGroupId()); String workerGroup =
StringUtils.isEmpty(schedule.getWorkerGroup()) ? Constants.DEFAULT_WORKER_GROUP : schedule.getWorkerGroup();
command.setWorkerGroup(workerGroup);
command.setWarningType(schedule.getWarningType());
command.setProcessInstancePriority(schedule.getProcessInstancePriority());
command.setProcessDefinitionVersion(processDefinition.getVersion());
processService.createCommand(command);
}
}
master模块
通俗讲,负责发号施令,布置任务,接受成果
专业讲,负责 DAG 任务切分,任务提交监控,同时监控其他 master 和 worker的健康状态
采用 分布式无中心的设计理念
整体代码逻辑图:
从图中可知,整个 master 架构可分为三大模块,分别是核心定时任务处理客户端,接收请求服务端,stateEvent 处理中心。
一 client 客户端
逻辑图:
核心类 为 MasterSchedulerService,通过 slot 从数据库拉取 command,进行任务提交
WorkerflowExecuteThread 负责工作流的整个任务执行过程
ITaskProcessor 有多个实现类,不同类型的 Task,选用合适的 processor 进行task提交进 TaskPriorityQueue 队列中
如图:
二 server 服务端
核心处理对象为 TaskEvent
几大处理procesor,如图所示:
整体流程图如下:
TaksExecuteThread 中 TaskEvent 存储代码:
public class TaskExecuteThread {
private static final Logger logger = LoggerFactory.getLogger(TaskExecuteThread.class);
private final int processInstanceId;
private final ConcurrentLinkedQueue<TaskEvent> events = new ConcurrentLinkedQueue<>();
}
WorkflowExecuteThread 中 StateEvent 存储代码逻辑:
private ConcurrentLinkedQueue<StateEvent> stateEvents = new ConcurrentLinkedQueue<>();
public void submitStateEvent(StateEvent stateEvent) {
WorkflowExecuteThread workflowExecuteThread = processInstanceExecCacheManager.getByProcessInstanceId(stateEvent.getProcessInstanceId());
if (workflowExecuteThread == null) {
logger.warn("workflowExecuteThread is null, stateEvent:{}", stateEvent); return;
}
workflowExecuteThread.addStateEvent(stateEvent);
}
三 stateEvent 处理中心
作用:及时更新全局中的缓存,推进 process
全局的缓存有:
核心代码在 EventExecuteService
public class EventExecuteService extends Thread {
@Autowired
private ProcessInstanceExecCacheManager processInstanceExecCacheManager;
/** * workflow exec service */
@Autowired
private WorkflowExecuteThreadPool workflowExecuteThreadPool;
@Override
public synchronized void start() {
super.setName("EventServiceStarted");
super.start();
}
@Override
public void run() {
logger.info("Event service started");
while (Stopper.isRunning()) {
try {
eventHandler();
TimeUnit.MILLISECONDS.sleep(Constants.SLEEP_TIME_MILLIS_SHORT);
} catch (Exception e) {
logger.error("Event service thread error", e);
}
}
}
private void eventHandler() {
for (WorkflowExecuteThread workflowExecuteThread : this.processInstanceExecCacheManager.getAll()) {
workflowExecuteThreadPool.executeEvent(workflowExecuteThread);
}
}
}
WorkflowExecuteThread 依据 stateEvent 修改缓存,推进 process
private boolean stateEventHandler(StateEvent stateEvent) {
logger.info("process event: {}", stateEvent.toString());
if (!checkProcessInstance(stateEvent)) {
return false;
}
boolean result = false;
switch (stateEvent.getType()) {
case PROCESS_STATE_CHANGE:
result = processStateChangeHandler(stateEvent);
break;
case TASK_STATE_CHANGE:
// finish 的时候,开始后一个节点task
result = taskStateChangeHandler(stateEvent);
break;
case PROCESS_TIMEOUT:
result = processTimeout();
break;
case TASK_TIMEOUT:
result = taskTimeout(stateEvent);
break;
case WAIT_TASK_GROUP:
result = checkForceStartAndWakeUp(stateEvent);
break;
case TASK_RETRY:
result = taskRetryEventHandler(stateEvent);
break;
case PROCESS_BLOCKED:
result = processBlockHandler(stateEvent);
break;
default:
break;
}
if (result) {
this.stateEvents.remove(stateEvent);
}
return result;
}
StateWheelExecuteThread 健康检查task,process状态
public class StateWheelExecuteThread extends Thread {
@Override
public void run() {
while (Stopper.isRunning()) {
try {
//检查task是否超时
checkTask4Timeout();
//检查 task 是否需要重试
checkTask4Retry();
//检查 task 是否需要更改state
checkTask4State();
//检查 process 是否超时
checkProcess4Timeout();
} catch (Exception e) {
logger.error("state wheel thread check error:", e);
}
ThreadUtil.sleepAtLeastIgnoreInterrupts((long) masterConfig.getStateWheelInterval() * Constants.SLEEP_TIME_MILLIS); } }
worker 模块
通俗讲,负责真正干活
专业讲,主要负责任务的执行和提供日志服务。
注册服务于 ZK:workerRegistryClient 采用 分布式无中心的设计理念
整体代码逻辑图:
1 master 根据 commandType 从 NettyClientHandler 中获取一个 对应的 NettyRequeProcessor,从 zk 中获取该 处理器的ip,请求发送过去
2 任务逻辑真正执行代码 TaskExecuteThread.run() --→ 创建 task -→ 执行 task.handle()
逻辑详解:
背景:
框架中,支持多种类型的 Task,例如 sql,python,shell 等等...
备注:AbstractTask 抽象接口,统一定义执行方法名称 handle()
AbstractTaskExecutor 负责打印日志,参数校验等
框架具体实现 一个 AbstractTask ,从 上下文变量 TaskExecutorContent 中取参数创建
//工厂模式 + 策略模式 //两层工厂
TaskChannel taskChannel = taskPluginManager.getTaskChannelMap().get(taskExecutionContext.getTaskType()); AbstractTask task = taskChannel.createTask(taskExecutionContext);
//task实际执行逻辑
this.task.handle();
//表示该任务执行成功,不修改状态,则默认失败
task.setExitStatusCode(0);
自定义插件分享
这里分享一个 自定义插件,插入 dolphinscheduler使用的案例。
step 一
在 dolphinscheduler-task-plugin 下新建一个子插件模块
其中实现 TaskChannelFactory 和 TaskChannel
@AutoService(TaskChannelFactory.class)
public class XXXTaskChannelFactory implements TaskChannelFactory {
@Override
public String getName() {
return "XXX";
}
@Override
public List<PluginParams> getParams() {
return null;
}
@Override
public TaskChannel create() {
return new XXXTaskChannel();
}
}
public class XXXTaskChannel implements TaskChannel {
@Override
public void cancelApplication(boolean status) {
}
@Override
public AbstractTask createTask(TaskExecutionContext taskRequest) {
return new XXXTask(taskRequest);
}
@Override
public AbstractParameters parseParameters(ParametersNode parametersNode) {
return JSONUtils.parseObject(parametersNode.getTaskParams(), XXXParameters.class);
}
@Override
public ResourceParametersHelper getResources(String parameters) {
return null;
}
}
step 二
实现 AbstractTask 具体执行核心的逻辑在 重新 实现 handle() 方法里
public class XXXTask extends AbstractTaskExecutor {
@Override
public void init() {
}
//具体执行逻辑
@Override
public void handle() throws Exception {
.......
}
@Override
public AbstractParameters getParameters() {
return this.XXXParameters;
}
step 三
修改 总插件 pom 依赖文件 dolphinscheduler-task-all
<dependency>
<groupId>org.apache.dolphinscheduler</groupId>
<artifactId>dolphinscheduler-task-XXX</artifactId>
<version>${project.version}</version>
</dependency>
总结
总的来看,dolphinscheduler 的核心逻辑还是在 master 模块上,理解起来略微有些费劲。但是其中的一些设计方法还是值得好好学习的,例如用发布事件,异步推动工作流的进展等设计。
公司后期打算运用dolphinscheduler,统一公司的大部分项目接口的用法,下线现有的一些内部服务,全部改为从dolphinscheduler 入口执行。个人觉得自定义插件功能还是比较简单的。