分布式调度框架。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

303 lines
15 KiB

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dolphinscheduler.server.master.runner.task.dynamic;
import org.apache.dolphinscheduler.common.constants.CommandKeyConstants;
import org.apache.dolphinscheduler.common.enums.Flag;
import org.apache.dolphinscheduler.common.enums.WorkflowExecutionStatus;
import org.apache.dolphinscheduler.common.utils.JSONUtils;
import org.apache.dolphinscheduler.dao.entity.Command;
import org.apache.dolphinscheduler.dao.entity.ProcessDefinition;
import org.apache.dolphinscheduler.dao.entity.ProcessInstance;
import org.apache.dolphinscheduler.dao.entity.RelationSubWorkflow;
import org.apache.dolphinscheduler.dao.entity.TaskInstance;
import org.apache.dolphinscheduler.dao.mapper.CommandMapper;
import org.apache.dolphinscheduler.dao.mapper.ProcessDefinitionMapper;
import org.apache.dolphinscheduler.dao.repository.ProcessInstanceDao;
import org.apache.dolphinscheduler.dao.repository.TaskInstanceDao;
import org.apache.dolphinscheduler.extract.base.client.SingletonJdkDynamicRpcClientProxyFactory;
import org.apache.dolphinscheduler.extract.master.ITaskInstanceExecutionEventListener;
import org.apache.dolphinscheduler.extract.master.transportor.WorkflowInstanceStateChangeEvent;
import org.apache.dolphinscheduler.plugin.task.api.TaskExecutionContext;
import org.apache.dolphinscheduler.plugin.task.api.model.DynamicInputParameter;
import org.apache.dolphinscheduler.plugin.task.api.model.Property;
import org.apache.dolphinscheduler.plugin.task.api.parameters.DynamicParameters;
import org.apache.dolphinscheduler.plugin.task.api.utils.ParameterUtils;
import org.apache.dolphinscheduler.server.master.exception.MasterTaskExecuteException;
import org.apache.dolphinscheduler.server.master.runner.execute.AsyncTaskExecuteFunction;
import org.apache.dolphinscheduler.server.master.runner.task.BaseAsyncLogicTask;
import org.apache.dolphinscheduler.service.process.ProcessService;
import org.apache.dolphinscheduler.service.subworkflow.SubWorkflowService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.Lists;
@Slf4j
public class DynamicLogicTask extends BaseAsyncLogicTask<DynamicParameters> {
public static final String TASK_TYPE = "DYNAMIC";
private final ProcessInstanceDao processInstanceDao;
private final SubWorkflowService subWorkflowService;
private final ProcessDefinitionMapper processDefineMapper;
private final CommandMapper commandMapper;
private final ProcessService processService;
private ProcessInstance processInstance;
private TaskInstance taskInstance;
private boolean haveBeenCanceled = false;
public DynamicLogicTask(TaskExecutionContext taskExecutionContext,
ProcessInstanceDao processInstanceDao,
TaskInstanceDao taskInstanceDao,
SubWorkflowService subWorkflowService,
ProcessService processService,
ProcessDefinitionMapper processDefineMapper,
CommandMapper commandMapper) {
super(taskExecutionContext,
JSONUtils.parseObject(taskExecutionContext.getTaskParams(), new TypeReference<DynamicParameters>() {
}));
this.processInstanceDao = processInstanceDao;
this.subWorkflowService = subWorkflowService;
this.processService = processService;
this.processDefineMapper = processDefineMapper;
this.commandMapper = commandMapper;
this.processInstance = processInstanceDao.queryById(taskExecutionContext.getProcessInstanceId());
this.taskInstance = taskInstanceDao.queryById(taskExecutionContext.getTaskInstanceId());
}
@Override
public AsyncTaskExecuteFunction getAsyncTaskExecuteFunction() throws MasterTaskExecuteException {
List<Map<String, String>> parameterGroup = generateParameterGroup();
if (parameterGroup.size() > taskParameters.getMaxNumOfSubWorkflowInstances()) {
log.warn("the number of sub process instances [{}] exceeds the maximum limit [{}]", parameterGroup.size(),
taskParameters.getMaxNumOfSubWorkflowInstances());
parameterGroup = parameterGroup.subList(0, taskParameters.getMaxNumOfSubWorkflowInstances());
}
// if already exists sub process instance, do not generate again
List<ProcessInstance> existsSubProcessInstanceList =
subWorkflowService.getAllDynamicSubWorkflow(processInstance.getId(), taskInstance.getTaskCode());
if (CollectionUtils.isEmpty(existsSubProcessInstanceList)) {
generateSubWorkflowInstance(parameterGroup);
} else {
resetProcessInstanceStatus(existsSubProcessInstanceList);
}
return new DynamicAsyncTaskExecuteFunction(taskExecutionContext, processInstance, taskInstance, this,
commandMapper,
subWorkflowService, taskParameters.getDegreeOfParallelism());
}
public void resetProcessInstanceStatus(List<ProcessInstance> existsSubProcessInstanceList) {
switch (processInstance.getCommandType()) {
case REPEAT_RUNNING:
existsSubProcessInstanceList.forEach(processInstance -> {
processInstance.setState(WorkflowExecutionStatus.WAIT_TO_RUN);
processInstanceDao.updateById(processInstance);
});
break;
case START_FAILURE_TASK_PROCESS:
case RECOVER_TOLERANCE_FAULT_PROCESS:
List<ProcessInstance> failedProcessInstances =
subWorkflowService.filterFailedProcessInstances(existsSubProcessInstanceList);
failedProcessInstances.forEach(processInstance -> {
processInstance.setState(WorkflowExecutionStatus.WAIT_TO_RUN);
processInstanceDao.updateById(processInstance);
});
break;
}
}
public void generateSubWorkflowInstance(List<Map<String, String>> parameterGroup) throws MasterTaskExecuteException {
List<ProcessInstance> processInstanceList = new ArrayList<>();
ProcessDefinition subProcessDefinition =
processDefineMapper.queryByCode(taskParameters.getProcessDefinitionCode());
for (Map<String, String> parameters : parameterGroup) {
String dynamicStartParams = JSONUtils.toJsonString(parameters);
Command command = DynamicCommandUtils.createCommand(processInstance, subProcessDefinition.getCode(),
subProcessDefinition.getVersion(), parameters);
// todo: set id to -1? we use command to generate sub process instance, but the generate method will use the
// command id to do
// somethings
command.setId(-1);
DynamicCommandUtils.addDataToCommandParam(command, CommandKeyConstants.CMD_DYNAMIC_START_PARAMS,
dynamicStartParams);
ProcessInstance subProcessInstance = createSubProcessInstance(command);
subProcessInstance.setState(WorkflowExecutionStatus.WAIT_TO_RUN);
processInstanceDao.insert(subProcessInstance);
command.setProcessInstanceId(subProcessInstance.getId());
processInstanceList.add(subProcessInstance);
}
List<RelationSubWorkflow> relationSubWorkflowList = new ArrayList<>();
for (ProcessInstance subProcessInstance : processInstanceList) {
RelationSubWorkflow relationSubWorkflow = new RelationSubWorkflow();
relationSubWorkflow.setParentWorkflowInstanceId(Long.valueOf(processInstance.getId()));
relationSubWorkflow.setParentTaskCode(taskInstance.getTaskCode());
relationSubWorkflow.setSubWorkflowInstanceId(Long.valueOf(subProcessInstance.getId()));
relationSubWorkflowList.add(relationSubWorkflow);
}
log.info("Expected number of runs : {}, actual number of runs : {}", parameterGroup.size(),
processInstanceList.size());
int insertN = subWorkflowService.batchInsertRelationSubWorkflow(relationSubWorkflowList);
log.info("insert {} relation sub workflow", insertN);
}
public ProcessInstance createSubProcessInstance(Command command) throws MasterTaskExecuteException {
ProcessInstance subProcessInstance;
try {
subProcessInstance = processService.constructProcessInstance(command, processInstance.getHost());
subProcessInstance.setIsSubProcess(Flag.YES);
subProcessInstance.setVarPool(taskExecutionContext.getVarPool());
} catch (Exception e) {
log.error("create sub process instance error", e);
throw new MasterTaskExecuteException(e.getMessage());
}
return subProcessInstance;
}
public List<Map<String, String>> generateParameterGroup() {
List<DynamicInputParameter> dynamicInputParameters = getDynamicInputParameters();
Set<String> filterStrings =
Arrays.stream(StringUtils.split(taskParameters.getFilterCondition(), ",")).map(String::trim)
.collect(Collectors.toSet());
List<List<DynamicInputParameter>> allParameters = new ArrayList<>();
for (DynamicInputParameter dynamicInputParameter : dynamicInputParameters) {
List<DynamicInputParameter> singleParameters = new ArrayList<>();
String value = dynamicInputParameter.getValue();
String separator = dynamicInputParameter.getSeparator();
List<String> valueList =
Arrays.stream(StringUtils.split(value, separator)).map(String::trim).collect(Collectors.toList());
valueList = valueList.stream().filter(v -> !filterStrings.contains(v)).collect(Collectors.toList());
for (String v : valueList) {
DynamicInputParameter singleParameter = new DynamicInputParameter();
singleParameter.setName(dynamicInputParameter.getName());
singleParameter.setValue(v);
singleParameters.add(singleParameter);
}
allParameters.add(singleParameters);
}
// use Sets.cartesianProduct to get the cartesian product of all parameters
List<List<DynamicInputParameter>> cartesianProduct = Lists.cartesianProduct(allParameters);
// convert cartesian product to parameter group List<Map<name:value>>
List<Map<String, String>> parameterGroup = cartesianProduct.stream().map(
inputParameterList -> inputParameterList.stream().collect(
Collectors.toMap(DynamicInputParameter::getName, DynamicInputParameter::getValue)))
.collect(Collectors.toList());
log.info("parameter group size: {}", parameterGroup.size());
// log every parameter group
if (CollectionUtils.isNotEmpty(parameterGroup)) {
for (Map<String, String> map : parameterGroup) {
log.info("parameter group: {}", map);
}
}
return parameterGroup;
}
private List<DynamicInputParameter> getDynamicInputParameters() {
List<DynamicInputParameter> dynamicInputParameters = taskParameters.getListParameters();
if (CollectionUtils.isNotEmpty(dynamicInputParameters)) {
for (DynamicInputParameter dynamicInputParameter : dynamicInputParameters) {
String value = dynamicInputParameter.getValue();
Map<String, Property> paramsMap = taskExecutionContext.getPrepareParamsMap();
value = ParameterUtils.convertParameterPlaceholders(value, ParameterUtils.convert(paramsMap));
dynamicInputParameter.setValue(value);
}
}
return dynamicInputParameters;
}
@Override
public void kill() {
try {
changeRunningSubprocessInstancesToStop(WorkflowExecutionStatus.READY_STOP);
} catch (MasterTaskExecuteException e) {
log.error("kill {} error", taskInstance.getName(), e);
}
}
private void changeRunningSubprocessInstancesToStop(WorkflowExecutionStatus stopStatus) throws MasterTaskExecuteException {
this.haveBeenCanceled = true;
List<ProcessInstance> existsSubProcessInstanceList =
subWorkflowService.getAllDynamicSubWorkflow(processInstance.getId(), taskInstance.getTaskCode());
List<ProcessInstance> runningSubProcessInstanceList =
subWorkflowService.filterRunningProcessInstances(existsSubProcessInstanceList);
for (ProcessInstance subProcessInstance : runningSubProcessInstanceList) {
subProcessInstance.setState(stopStatus);
processInstanceDao.updateById(subProcessInstance);
if (subProcessInstance.getState().isFinished()) {
log.info("The process instance [{}] is finished, no need to stop", subProcessInstance.getId());
return;
}
try {
sendToSubProcess(taskExecutionContext, subProcessInstance);
log.info("Success send [{}] request to SubWorkflow's master: {}", stopStatus,
subProcessInstance.getHost());
} catch (Exception e) {
throw new MasterTaskExecuteException(
String.format("Send stop request to SubWorkflow's master: %s failed",
subProcessInstance.getHost()),
e);
}
}
}
private void sendToSubProcess(TaskExecutionContext taskExecutionContext, ProcessInstance subProcessInstance) {
final ITaskInstanceExecutionEventListener iTaskInstanceExecutionEventListener =
SingletonJdkDynamicRpcClientProxyFactory
.getProxyClient(subProcessInstance.getHost(), ITaskInstanceExecutionEventListener.class);
final WorkflowInstanceStateChangeEvent workflowInstanceStateChangeEvent = new WorkflowInstanceStateChangeEvent(
taskExecutionContext.getProcessInstanceId(),
taskExecutionContext.getTaskInstanceId(),
subProcessInstance.getState(),
subProcessInstance.getId(),
0);
iTaskInstanceExecutionEventListener.onWorkflowInstanceInstanceStateChange(workflowInstanceStateChangeEvent);
}
public boolean isCancel() {
return haveBeenCanceled;
}
}