2021-11-22
2021SC@SDUSC
DolphinScheduler(9)
1. ShellTask
AbstractTask的子类现在有9个,分析ShellTask,这是一个常见的任务类型。
public ShellTask(TaskProps taskProps, Logger logger) {
super(taskProps, logger);
this.taskDir = taskProps.getTaskDir();
this.shellCommandExecutor = new ShellCommandExecutor(this::logHandle, taskProps.getTaskDir(),
taskProps.getTaskAppId(),
taskProps.getTaskInstId(),
taskProps.getTenantCode(),
taskProps.getEnvFile(),
taskProps.getTaskStartTime(),
taskProps.getTaskTimeout(),
logger);
this.processDao = SpringApplicationContext.getBean(ProcessDao.class);
}
先看其构造函数,有两个字段的初始化比较重要:
shellCommandExecutor、processDao。
ShellCommandExecutor是shell脚本的执行器,具体功能后面再分析。
processDao的初始化方法又是通过SpringApplicationContext.getBean获取到的。
把这些dao等其他类型的全局或局部变量封装到TaskContenxt,如果任务之间传递变量,就可以用TaskContenxt了。
根据init、handle、after的名称来看,具体的执行应该是在handle。
public void handle() throws Exception {
try {
// construct process
exitStatusCode = shellCommandExecutor.run(buildCommand(), processDao);
} catch (Exception e) {
logger.error("shell task failure", e);
exitStatusCode = -1;
}
}
handle就是调用shellCommandExecutor.run,如果出现异常,则exitStatusCode赋值-1
shellCommandExecutor.run的代码多,只分析shellCommandExecutor的buildProcess的方法。
buildProcess
private void buildProcess(String commandFile) throws IOException {
//init process builder
ProcessBuilder processBuilder = new ProcessBuilder();
// setting up a working directory
processBuilder.directory(new File(taskDir));
// merge error information to standard output stream
processBuilder.redirectErrorStream(true);
// setting up user to run commands
processBuilder.command("sudo", "-u", tenantCode, commandType(), commandFile);
process = processBuilder.start();
// print command
printCommand(processBuilder);
}
它根据commandFile创建了一个ProcessBuilder,返回了Process对象。当然了,是通过sudo -u执行的shell命令。
2.DependentTask
DependentTask虽然是AbstractTask的一个子类,虽然与shell属于同一个层级的类,但由于其功能的特殊性,此处单独拿出来做分析。
public void handle(){
// set the name of the current thread
String threadLoggerInfoName = String.format(Constants.TASK_LOG_INFO_FORMAT, taskProps.getTaskAppId());
Thread.currentThread().setName(threadLoggerInfoName);
try{
TaskInstance taskInstance = null;
while(Stopper.isRunning()){
taskInstance = processDao.findTaskInstanceById(this.taskProps.getTaskInstId());
if(taskInstance == null){
exitStatusCode = -1;
break;
}
if(taskInstance.getState() == ExecutionStatus.KILL){
this.cancel = true;
}
if(this.cancel || allDependentTaskFinish()){
break;
}
Thread.sleep(Constants.SLEEP_TIME_MILLIS);
}
if(cancel){
exitStatusCode = Constants.EXIT_CODE_KILL;
}else{
DependResult result = getTaskDependResult();
exitStatusCode = (result == DependResult.SUCCESS) ?
Constants.EXIT_CODE_SUCCESS : Constants.EXIT_CODE_FAILURE;
}
}catch (Exception e){
logger.error(e.getMessage(),e);
exitStatusCode = -1;
}
}
逻辑总结如下:
1.通过任务实例id,获取当前最新的任务实例信息
2.判断状态是否为kill,是则退出
3.判断所有依赖任务是否完成,是则退出
4.休眠1秒,进入下一次循环。
allDependentTaskFinish
allDependentTaskFinish是一个非常重要的逻辑。
private boolean allDependentTaskFinish(){
boolean finish = true;
for(DependentExecute dependentExecute : dependentTaskList){
for(Map.Entry<String, DependResult> entry: dependentExecute.getDependResultMap().entrySet()) {
if(!dependResultMap.containsKey(entry.getKey())){
dependResultMap.put(entry.getKey(), entry.getValue());
//save depend result to log
logger.info("dependent item complete {} {},{}",
DEPENDENT_SPLIT, entry.getKey(), entry.getValue().toString());
}
}
if(!dependentExecute.finish(dependentDate)){
finish = false;
}
}
return finish;
}
它遍历了dependentTaskList,通过dependentExecute.finish(dependentDate)判断了依赖的作业是否全部完成,任意一个没有完成,则退出循环,返回false。
dependentDate的值也很重要,它其实是任务的调度时间或者启动时间(补数时间)
if(taskProps.getScheduleTime() != null){
this.dependentDate = taskProps.getScheduleTime();
}else{
this.dependentDate = taskProps.getTaskStartTime();
}
calculateResultForTasks
通过一层层追踪分析DependentExecute.finish,我们定位到了DependentExecute.calculateResultForTasks,这是用来判断某个依赖项的依赖结果的。
/**
* calculate dependent result for one dependent item.
* @param dependentItem dependent item
* @param dateIntervals date intervals
* @return dateIntervals
*/
private DependResult calculateResultForTasks(DependentItem dependentItem,
List<DateInterval> dateIntervals) {
DependResult result = DependResult.FAILED;
for(DateInterval dateInterval : dateIntervals){
ProcessInstance processInstance = findLastProcessInterval(dependentItem.getDefinitionId(),
dateInterval);
if(processInstance == null){
logger.error("cannot find the right process instance: definition id:{}, start:{}, end:{}",
dependentItem.getDefinitionId(), dateInterval.getStartTime(), dateInterval.getEndTime() );
return DependResult.FAILED;
}
if(dependentItem.getDepTasks().equals(Constants.DEPENDENT_ALL)){
result = getDependResultByState(processInstance.getState());
}else{
TaskInstance taskInstance = null;
List<TaskInstance> taskInstanceList = processDao.findValidTaskListByProcessId(processInstance.getId());
for(TaskInstance task : taskInstanceList){
if(task.getName().equals(dependentItem.getDepTasks())){
taskInstance = task;
break;
}
}
if(taskInstance == null){
// cannot find task in the process instance
// maybe because process instance is running or failed.
result = getDependResultByState(processInstance.getState());
}else{
result = getDependResultByState(taskInstance.getState());
}
}
if(result != DependResult.SUCCESS){
break;
}
}
return result;
}
总结:如果依赖整个DAG,则判断流程定义实例的状态;
否则依次判断依赖任务实例的状态。
DependentTask的逻辑简单清晰,就是循环等待所有的任务结束。DependentTask可以单独设计成一个线程,或者放到独立的线程池去运行。对于一个调度系统来说,“依赖”还是一个非常重要的概念。