2021-10-18
2021SC@SDUSC—DolphinScheduler(4)
1. Stopper.isRunning()
在分析run之前,首先我们先分析一下该方法的内在的逻辑
/**
* if the process closes, a signal is placed as true, and all threads get this flag to stop working
*/
public class Stopper {
private static volatile AtomicBoolean signal = new AtomicBoolean(false);
public static final boolean isStoped(){
return signal.get();
}
public static final boolean isRunning(){
return !signal.get();
}
public static final void stop(){
signal.getAndSet(true);
}
}
其逻辑非常简单,就是用一个原子boolean(布尔值),标志当前进程是否要退出。
如果收到了退出信号,则signal为true,该进程内所有的线程都退出当前循环。
下面我们来分析查询到一个Command之后的逻辑:
if (command != null) {
logger.info(String.format("find one command: id: %d, type: %s", command.getId(),command.getCommandType().toString()));
try{
processInstance = processDao.handleCommand(logger, OSUtils.getHost(), this.masterExecThreadNum - activeCount, command);
if (processInstance != null) {
logger.info("start master exec thread , split DAG ...");
masterExecService.execute(new MasterExecThread(processInstance,processDao));
}
}catch (Exception e){
logger.error("scan command error ", e);
processDao.moveToErrorCommand(command, e.toString());
}
}
其实就是根据Command创建了一个ProcessInstance(流程实例),
之前也分析过,流程定义是由Scheduler自动创建的,
而Quartz已经根据Schedule信息创建了Command保存到了数据库。
至此,流程定义与定时的关联逻辑就已经串起来了。
创建流程实例的时候传入了当前可用(masterExecThreadNum - activeCount)的线程数量,
如果满足当前dag,则返回ProcessInstance,否则返回null。
ProcessInstance最终交由MasterExecThread去执行。
至此MasterSchedulerThread类的主要逻辑如下:
调用OSUtils.checkResource,检查当前资源(内存、CPU)。
资源超出阈值,则休眠1秒进入下一次循环。
检查zookeeper是否连接成功
获取一个InterProcessMutex锁(分布式的公平可重入互斥锁)。
也就是只有一个master可以获取到这个锁
查询一个Command,
如果当前线程数够用,则创建一个流程实例(ProcessInstance)
交给MasterExecThread线程处理。
休眠1秒,进入下一次循环
进入下一次循环之前,释放InterProcessMutex锁
在结束MasterExecThread的源码分析之前,我们再简要分析一下这个类比较重要的一个字段:
processDao![请添加图片描述](https://i-blog.csdnimg.cn/blog_migrate/a54359f116a2e17cb30cfc55a247101f.png)
这个类,可以看成是与流程定义相关的操作集合,
与流程定义存储相关的操作、逻辑的集合。
processDao.moveToErrorCommand需要稍微注意一下,
在异常情况下,它把Command从原来的表中删除,
然后插入到了t_ds_error_command表1
MasterExecThread
与MasterSchedulerThread一样,MasterExecThread也是实现了Runnable的线程类,不过我们先来看MasterExecThread的构造函数。
public MasterExecThread(ProcessInstance processInstance,ProcessDao processDao){
this.processDao = processDao;
this.processInstance = processInstance;
int masterTaskExecNum = conf.getInt(Constants.MASTER_EXEC_TASK_THREADS,
Constants.defaultMasterTaskExecNum);
this.taskExecService = ThreadUtils.newDaemonFixedThreadExecutor("Master-Task-Exec-Thread",
masterTaskExecNum);
}
taskExecService这个字段非常重要,它是一个固定大小(20)的后台线程池。
这意味着,一个DAG最大的并发任务数就是20。
另外细心的读者发现,conf字段是一个static字段,在static代码块初始化的。
为啥不从MasterSchedulerThread传过来呢?
我还以为要自动reload呢,结果也没有。
static {
try {
conf = new PropertiesConfiguration(Constants.MASTER_PROPERTIES_PATH);
}catch (ConfigurationException e){
logger.error("load configuration failed : " + e.getMessage(),e);
System.exit(1);
}
}
下面分析该类的run方法
@Override
public void run() {
// process instance is null
if (processInstance == null){
logger.info("process instance is not exists");
return;
}
// check to see if it's done
if (processInstance.getState().typeIsFinished()){
logger.info("process instance is done : {}",processInstance.getId());
return;
}
try {
if (processInstance.isComplementData() && Flag.NO == processInstance.getIsSubProcess()){
// sub process complement data
executeComplementProcess();
}else{
// execute flow
executeProcess();
}
}catch (Exception e){
logger.error("master exec thread exception: " + e.getMessage(), e);
logger.error("process execute failed, process id:{}", processInstance.getId());
processInstance.setState(ExecutionStatus.FAILURE);
processInstance.setEndTime(new Date());
processDao.updateProcessInstance(processInstance);
}finally {
taskExecService.shutdown();
// post handle
postHandle();
}
}
分析源码后,简要总结其逻辑如下:
判断processInstance是否为null。为null则退出
判断processInstance是否已经完成(成功、报错、取消、暂停、等待)
判断是否为补数。是则走补数的逻辑
执行当前流程定义实例(executeProcess)
调用taskExecService.shutdown(),等待所有线程正常退出
executeProcess按顺序调用了prepareProcess、runProcess、endProcess三个方法,简单来说就是初始化、执行、释放资源。 prepareProcess又按顺序调用了initTaskQueue、buildFlowDag。
initTaskQueue就是一些资源的初始化操作,比如通过流程定义ID查询到当前的任务实例。下面是其核心逻辑,可以发现,就是查询了完成的任务列表,报错且不能重试的任务列表。
List<TaskInstance> taskInstanceList = processDao.findValidTaskListByProcessId(processInstance.getId());
for(TaskInstance task : taskInstanceList){
if(task.isTaskComplete()){
completeTaskList.put(task.getName(), task);
}
if(task.getState().typeIsFailure() && !task.taskCanRetry()){
errorTaskList.put(task.getName(), task);
}
}
buildFlowDag看名字应该是生成DAG实例的,代码虽短,但调用了好几个函数,我们只重点分析最后一个函数调用。
private void buildFlowDag() throws Exception {
recoverNodeIdList = getStartTaskInstanceList(processInstance.getCommandParam());
forbiddenTaskList = DagHelper.getForbiddenTaskNodeMaps(processInstance.getProcessInstanceJson());
// generate process to get DAG info
List<String> recoveryNameList = getRecoveryNodeNameList();
List<String> startNodeNameList = parseStartNodeName(processInstance.getCommandParam());
ProcessDag processDag = generateFlowDag(processInstance.getProcessInstanceJson(),
startNodeNameList, recoveryNameList, processInstance.getTaskDependType());
if(processDag == null){
logger.error("processDag is null");
return;
}
// generate process dag
dag = DagHelper.buildDagGraph(processDag);
}
DagHelper.buildDagGraph生成了一个DAG对象实例,根据名字和注释猜测,这应该是对有向无环图的一个抽象。
/**
* the object of DAG
*/
private DAG<String,TaskNode,TaskNodeRelation> dag;