2021-12-06
2021SC@SDUSC
2021-12-06-DolphinScheduler(11)
1.ZKMasterClient
private ZKMasterClient(ProcessDao processDao){
this.processDao = processDao;
init();
}
public void init(){
// init dao
this.initDao();
InterProcessMutex mutex = null;
try {
// create distributed lock with the root node path of the lock space as /dolphinscheduler/lock/failover/master
String znodeLock = getMasterStartUpLockPath();
mutex = new InterProcessMutex(zkClient, znodeLock);
mutex.acquire();
// init system znode
this.initSystemZNode();
// monitor master
this.listenerMaster();
// monitor worker
this.listenerWorker();
// register master
this.registerMaster();
// check if fault tolerance is required,failure and tolerance
if (getActiveMasterNum() == 1) {
failoverWorker(null, true);
failoverMaster(null);
}
}catch (Exception e){
logger.error("master start up exception : " + e.getMessage(),e);
}finally {
releaseMutex(mutex);
}
}
总结:
initDao。其实就是初始化alertDao,调DaoFactory.getDaoInstance
(AlertDao.class)。
申请/dolphinscheduler/lock/failover/master路径的分布式锁。
申请到锁之后,依次调用initSystemZNode、listenerMaster、
listenerWorker、registerMaster.
如果当前活动的master个数为1则进行容灾。
listenerMaster是监听到其他master节点被移除后调用removeZKNodePath
如果是当前节点就退出。
removeZKNodePath,它主要是处理死掉的节点、进行预警,
如果要进行故障转移,就调用failoverServerWhenDown。
这是一个非常重要的方法,它在里面按照不同情况调用了failoverMaster或failoverWorker。也就是说master和worker的故障转移都是在master处理的。
2.failoverMaster
failoverMaster查询了指定master节点运行的流程定义实例,然后调动processNeedFailoverProcessInstances进行处理。
private void failoverMaster(String masterHost) {
logger.info("start master failover ...");
List<ProcessInstance> needFailoverProcessInstanceList = processDao.queryNeedFailoverProcessInstances(masterHost);
//updateProcessInstance host is null and insert into command
for(ProcessInstance processInstance : needFailoverProcessInstanceList){
processDao.processNeedFailoverProcessInstances(processInstance);
}
logger.info("master failover end");
}
processNeedFailoverProcessInstances是更新为当前流程定义实例host字段为null字符串
插入一条类型为RECOVER_TOLERANCE_FAULT_PROCESS的Command,
参数是流程定义实例id。
@Transactional(rollbackFor = Exception.class)
public void processNeedFailoverProcessInstances(ProcessInstance processInstance){
//1 update processInstance host is null
processInstance.setHost("null");
processInstanceMapper.updateById(processInstance);
//2 insert into recover command
Command cmd = new Command();
cmd.setProcessDefinitionId(processInstance.getProcessDefinitionId());
cmd.setCommandParam(String.format("{\"%s\":%d}", Constants.CMDPARAM_RECOVER_PROCESS_ID_STRING, processInstance.getId()));
cmd.setExecutorId(processInstance.getExecutorId());
cmd.setCommandType(CommandType.RECOVER_TOLERANCE_FAULT_PROCESS);
createCommand(cmd);
}
3.RECOVER_TOLERANCE_FAULT_PROCESS
之后我们定位到ProcessDao.constructProcessInstance,
如果是RECOVER_TOLERANCE_FAULT_PROCESS类型,则调用processInstance.setRecovery(Flag.YES)。
case RECOVER_TOLERANCE_FAULT_PROCESS:
processInstance.setRecovery(Flag.YES);
runStatus=processInstance.getState();
break;
回到cmd的构造上,它设置了4个值,就得研究下这跟普通的Command的其他区别了。
Command command = new Command();
command.setCommandType(CommandType.SCHEDULER);
command.setExecutorId(schedule.getUserId());
command.setFailureStrategy(schedule.getFailureStrategy());
command.setProcessDefinitionId(schedule.getProcessDefinitionId());
command.setScheduleTime(scheduledFireTime);
command.setStartTime(fireTime);
command.setWarningGroupId(schedule.getWarningGroupId());
command.setWorkerGroupId(schedule.getWorkerGroupId());
command.setWarningType(schedule.getWarningType());
command.setProcessInstancePriority(schedule.getProcessInstancePriority());
现在回过头去ProcessScheduleJob.execute看一下,普通Command是如何构造的。大家会发现RECOVER_TOLERANCE_FAULT_PROCESS的Command多了一个commandParam的设置,这个param的key是Constants.CMDPARAM_RECOVER_PROCESS_ID_STRING。
定位到ProcessDao.constructProcessInstance,这里有对Constants.CMDPARAM_RECOVER_PROCESS_ID_STRING的处理。
if(cmdParam != null ){
Integer processInstance =0;
//recover from fallure or pause tasks
if(cmdParam.containsKey(Constants.CMDPAPM_RECOVER_PROCESS_ID_STRING)){
String procssId = cmdParam.get(Constants.CMDPARAM_RECOVER_PROCESS_ID_STRING);
processInstanceId = Integer.parseInt(processId);
if(processInstanceId==0){...}
}else if(cmdParam.containsKey(Contants.CMDPARAM_SUB_PROCESS)){
//sub process map
String pId =cmdParam.constainsKey(Constants.CMDPARAM_SUB_PROCESS);
processInstanceId=Integer.parseInt(pId);
}else if(cmdParam.containsKey(Constants.CMDPARAM_RECOVERY_WAITING_THREAD)){...}
if(processInstanceId ==0 ){
processInstance = generateNewProcessInstance(processDefinition,command,cmdParam);
}else{
processInstance = this.findProcessInstanceDetailById(processInstanceId);
}
processDefiniton = processDefineMapper.selectById(processInstance.getProcessDefinitionId());
processInstance.setProcessDefinition(processDefinition);
//reset command parameter
if(processInstance.getCommendParam()!= null){
Map<String,string> processCmdParam =JSONUtils.toMap(processInstance.getCommandParam());
for(Map.Enrty<String,String>entry:processCmdParam.entrySet()){
if(!cmdParam.containsKey(entry.getKey())){
cmdParam.put(entry.getKey(),entry.getValue());
}
}
}
}
if(cmdParam.containsKey(Contains.CMDPARAM_SUB_PROCESS)){...}
}else{
//generate one new process,instance
processInstance = generateNewProcessInstance(processDefinition,command,cmdParam);
}
处理就是获取到Constants.CMDPARAM_RECOVER_PROCESS_ID_STRING的值,
然后查询到流程定义实例对象而不是重新创建一个。
后续逻辑就是把流程定义实例的参数,全都put到cmdParam中进行后续的逻辑判断
根据流程实例id查询,而不是重新创建。
这样的好处是可以避免已经执行成功的任务不再重复执行。
那处于running状态的任务呢?这个就要结合master的退出来看了。
其实master会等待所调度的所有流程定义实例中的任务直至结束的。
也就是说,调度的任务应该会被正常的执行完毕的,
状态最终会被更新成失败或者成功。
如果宕掉的master没有能成功更新作业的状态,
DolphinScheduler没有处理这部分异常,
此时的作业只能是永久处于running状态,除非手动干预。
到这里就会发现,其实master的故障转移就是把某个master节点的流程定义实例
交由其他master节点去驱动,原来的流程定义实例中的任务状态没有任何改变。