低代码后端平台搭建-v2.0

前言

        从2.0版本开始这个系统将会实现并行执行多个分支的能力,但因为包主的能力有限,不太能纯手敲实现,或只能写成屎山类的,因此这次找到了一个很牛的开源系统,会在此系统上进行二次开发和学习。

开源系统的链接:

https://github.com/icefoxhz/taskflow-java-

        这个开源系统算比较复杂的,本着学习的初衷,我还是会先借鉴最核心和基础的并发执行能力,其他的在以后有机会的话可能会参考补充。本篇v2.0会先实现一个简单基础的并行架构,因此建议大佬可以直接跟着链接过去参考了(话说到底有没有非机器人在看这个专栏)。

系统分析

        要实现并行执行组件的能力,就不能简单的依靠v1.x中 FlowNode 类的顺序字段来决定执行顺序了,也因此不能简单的通过循环组件流list来执行了。

        参考开源代码的思路,可以用dag——有向无环图 作为这个系统的架构,放到实际应用中可以加一个线的类,用于记录组件之间是如何连接的;

        然后需要定义一个引擎类,作为计算如何执行这个组件流的引擎,每次调用组件流的时候,流程是创建引擎 --> 解析组件之间关系 --> 执行引擎 --> 得到结果。

        引擎的核心逻辑是:

  1. 如果一个组件依赖多个组件的结果,那么必须等待这些组件全部运行结束,得到结果后才能开始此组件的执行。
  2. 没有依赖关系的组件,可以用多个线程并行执行。
  3. 主线程需要阻塞等待组件执行结束

        学习的这个开源系统在第一点是通过记录每个组件的入度(indegree)实现,依赖几个组件则入度是几,每执行完成一个组件入度减一,入度归0时可以执行本组件;

第二点通过线程池实现,每次执行最后一个后继节点时用当前线程(如果后继只有一个也是用当前线程),其他的后继节点则创建新线程;

第三点通过CountDownLatch实现,初始值设置为结束节点的个数——因为此开源系统可以定义多个起始和结束节点,但我这里暂时只需要一个即可。同时CountDownLatch的设置最大阻塞时间的功能也很方便,所以这里依然使用这个实现。

代码示例

因为类变多了,很多包我都只把后缀加了个2为了更清晰。

lowcode.core.dto2.Edge

package com.example.lowcode.core.dto2;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * 组件之间的连线
 *
 * @author llxzdmd
 */
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Edge implements Serializable {
    // 起始组件
    String sourceName;
    // 目标组件
    String targetName;
}

lowcode.core.dto2.Flow

这个类是代替v1.x中的list,一个Flow就代表一个可执行的组件流。

package com.example.lowcode.core.dto2;

import com.example.lowcode.core.dto.FlowNode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

/**
 * 组件流2.0。1.0是直接用List<FlowNode>表示组件流,2.0因为加了连线,用List不合理,所以新建一个更外层的对象代替之前的list
 *
 * @author llxzdmd
 */
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Flow implements Serializable {
    private Long id;
    private String name;
    private String description;
    private String createName;
    private String modifiedName;
    private List<FlowNode> nodeInstances;
    private List<Edge> edgeInstances;
}

lowcode.core.dto2.FlowEngineBuilder

这个类用于解析组件之间的依赖关系

package com.example.lowcode.core.dto2;

import com.example.lowcode.core.dto.ComponentInfo;
import com.example.lowcode.core.dto.FlowNode;
import com.example.lowcode.core.exception.FlowConfigException;
import com.example.lowcode.core.framework.SpringUtil;
import com.example.lowcode.core.framework2.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;

/**
 * @author llxzdmd
 */
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FlowEngineBuilder<O> {
    private Flow flow;
    private Map<String, ComponentInfo> inputParams;
    private ExecutorService executor;

    public FlowEngineBuilder<O> setFlow(Flow flow) {
        this.flow = flow;
        return this;
    }

    public FlowEngineBuilder<O> setExecutor(ExecutorService executor) {
        this.executor = executor;
        return this;
    }

    public FlowEngineBuilder<O> setInputParams(Map<String, ComponentInfo> inputParams) {
        this.inputParams = inputParams;
        return this;
    }

    public DagEngine<O> buildEngine() {
        check();
        DagEngine<O> engineWithOpConfig = getEngineWithOpConfig(flow);
        clear();
        return engineWithOpConfig;
    }

    private void check() {
        if (flow == null) throw new FlowConfigException("Flow不能为null");
        if (executor == null) throw new FlowConfigException("线程池不能为null");
    }

    private void clear() {
        this.setFlow(null);
        this.setExecutor(null);
        this.setInputParams(null);
    }

    private DagEngine<O> getEngineWithOpConfig(Flow flow) {
        DagEngine<O> engine = new DagEngine<>(executor);
        // 得到执行类OperatorWrapper列表
        List<OperatorWrapper<?, ?>> operatorWrappers = getWrappersWithOpConfig(flow, engine);
        // 解析依赖
        resolveDependencies(flow, operatorWrappers);
        return engine;
    }

    private List<OperatorWrapper<?, ?>> getWrappersWithOpConfig(Flow flow, DagEngine<O> engine) {
        List<OperatorWrapper<?, ?>> operatorWrappers = new ArrayList<>();
        for (FlowNode node : flow.getNodeInstances()) {
            DefaultInvokeMethodComponent component = getDefaultComponent();
            // 创建Op元信息
            OpConfig opConfig = new OpConfig();
            opConfig.setComponentInfo(ComponentInfo.builder()
                    .flowId(flow.getId())
                    .componentId(node.getComponentId())
                    .inputs(node.getComponentInfo().getInputs())
                    .outputs(node.getComponentInfo().getOutputs())
                    .build());
            opConfig.setClassName(node.getComponentMetaInfo().getClassName());

            // 创建执行类OperatorWrapper
            OperatorWrapper<OperatorWrapper<?, ?>, Object> operatorWrapper = new OperatorWrapper<OperatorWrapper<?, ?>, Object>()
                    .instanceName(node.getNodeName())
                    .opConfig(opConfig, component)
                    .engine(engine);
            operatorWrappers.add(operatorWrapper);
        }
        return operatorWrappers;
    }

    /**
     * 解析依赖
     *
     * @param flow
     * @param operatorWrappers
     */
    private void resolveDependencies(Flow flow, List<OperatorWrapper<?, ?>> operatorWrappers) {
        Map<String, OperatorWrapper<?, ?>> wrapperMap = operatorWrappers.stream()
                .collect(Collectors.toMap(OperatorWrapper::getInstanceName, e -> e));

        Map<String, List<Edge>> groupBySource = flow.getEdgeInstances().stream().collect(Collectors.groupingBy(
                Edge::getSourceName
        ));
        groupBySource.forEach((id, followings) -> {
            for (Edge following : followings) {
                OperatorWrapper<?, ?> targetOp = wrapperMap.get(following.getTargetName());
                targetOp.depend(id);
            }
        });
    }

    private DefaultInvokeMethodComponent getDefaultComponent() {
        try {
            return SpringUtil.getBean(DefaultInvokeMethodComponent.class);
        } catch (Exception e) {
            throw new FlowConfigException("组件不存在", e);
        }
    }
}

lowcode.core.dto2.OperatorResult

这个类用于存放执行结果

package com.example.lowcode.core.dto2;

import com.example.lowcode.core.model2.ResultState;
import lombok.Getter;

/**
 * @author llxzdmd
 */
@Getter
public class OperatorResult<V> {
    /**
     * 执行的结果
     */
    private V result;
    /**
     * 结果状态
     */
    private ResultState resultState;
    /**
     * 异常信息
     */
    private Throwable ex;

    public OperatorResult(V result, ResultState resultState) {
        this(result, resultState, null);
    }

    public OperatorResult(V result, ResultState resultState, Exception ex) {
        this.result = result;
        this.resultState = resultState;
        this.ex = ex;
    }

    public static <V> OperatorResult<V> defaultResult() {
        return new OperatorResult<>(null, ResultState.DEFAULT);
    }

    @Override
    public String toString() {
        return "OperatorResult{" +
                "result=" + result +
                ", resultState=" + resultState +
                ", ex=" + ex +
                '}';
    }

    public void setEx(Throwable ex) {
        this.ex = ex;
    }

    public void setResult(V result) {
        this.result = result;
    }

    public void setResultState(ResultState resultState) {
        this.resultState = resultState;
    }
}

lowcode.core.framework2.DagEngine

dag引擎类,基本直接用的开源系统的代码,但删了很多东西

package com.example.lowcode.core.framework2;

import com.alibaba.ttl.threadpool.TtlExecutors;
import com.example.lowcode.core.dto2.OperatorResult;
import com.example.lowcode.core.exception.FlowExecutionException;
import com.example.lowcode.core.exception.TaskFlowException;
import com.example.lowcode.core.framework.ComponentContext;
import com.example.lowcode.core.model2.DagWrapperState;
import com.example.lowcode.core.model2.ResultState;
import com.example.lowcode.util.ConvertUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * DAG执行引擎
 *
 * @author llxzdmd
 */
@Slf4j
@Getter
public class DagEngine<O> {
    /**
     * 工作线程池
     */
    private ExecutorService executor;
    /**
     * 主线程阻塞等待所有结束节点执行完成
     */
    private CountDownLatch syncLatch;
    /**
     * wrapper集合
     */
    private Map<String, OperatorWrapper<?, ?>> wrapperMap = new HashMap<>();
    /**
     * 工作线程的快照
     */
    private ConcurrentHashMap.KeySetView<Thread, Boolean> runningThreadSet = ConcurrentHashMap.newKeySet();
    /**
     * OP异常堆栈,只保存一个
     */
    private Throwable ex;
    /**
     * 执行的状态
     */
    private int state = DagWrapperState.INIT.getStage();
    /**
     * 执行引擎上下文
     */
    // private DagContext<O> dagContext = new DagContext<O>();
    private ComponentContext context;
    /**
     * 开始节点集合
     */
    private Set<OperatorWrapper<?, ?>> beginWrapperSet = new HashSet<>();
    /**
     * 结束节点集合
     * 引擎执行过程中可以根据节点执行情况动态设置结束节点,需要使用线程安全的集合
     */
    private Set<OperatorWrapper<?, ?>> endWrapperSet = ConcurrentHashMap.newKeySet();

    /**
     * 引擎执行结果
     */
    private OperatorResult<O> output;

    public DagEngine(ExecutorService executor) {
        this.executor = TtlExecutors.getTtlExecutorService(executor);
    }

    /**
     * 阻塞主线程,等待流程执行结束,根据依赖关系自动解析出开始节点
     */
    public void runAndWait() {
        //解析依赖
        parseNextDepend();
        if (beginWrapperSet == null || beginWrapperSet.isEmpty()) {
            throw new TaskFlowException("Flow无开始节点,请检查并修复配置");
        }
        if (endWrapperSet == null || endWrapperSet.isEmpty()) {
            throw new TaskFlowException("Flow无终止节点,请检查并修复配置");
        }
        OperatorWrapper[] beginWrappers = ConvertUtil.set2Array(beginWrapperSet);
        this.runWrappers(beginWrappers);
    }

    private void runWrappers(OperatorWrapper<?, ?>[] beginWrappers) {
        try {
            state = DagWrapperState.RUNNING.getStage();
            //设置DAG引擎上下文,上下文的生命周期从开始节点到结束节点之间
            //DagContextHolder.set(dagContext);
            this.context = new ComponentContext();
            //初始化信号量
            syncLatch = new CountDownLatch(endWrapperSet.size());
            //将初始节点交给线程池执行,此过程是异步的
            for (OperatorWrapper<?, ?> wrapper : beginWrappers) {
                doRun(wrapper, true);
            }
            //线程阻塞等待DAG执行结束,或超时被唤醒
            awaitAndInterruptRunningThread();
        } catch (Throwable e) {
            log.error("getDagInitialTask error", e);
        } finally {
            //清理DAG引擎上下文
            //DagContextHolder.remove();
        }
    }

    /**
     * 主线程阻塞,唤醒后会打断还在执行中的节点
     */
    private void awaitAndInterruptRunningThread() {
        boolean isTimeout = false;
        try {
            isTimeout = !syncLatch.await(4, TimeUnit.HOURS);
        } catch (InterruptedException e) {
            state = DagWrapperState.ERROR.getStage();
            log.error("dagEngine is interrupted", e);
        }
        if (isTimeout) {
            state = DagWrapperState.ERROR.getStage();
            log.error("DagEngine execution timeout");
            final FlowExecutionException flowExecutionTimeout = new FlowExecutionException("Flow execution timeout");
            this.ex = flowExecutionTimeout;
            throw flowExecutionTimeout;
        }
        //理论上不会出现这种情况,防御性容错
        if (state == DagWrapperState.RUNNING.getStage()) {
            state = DagWrapperState.FINISH.getStage();
        }
        if (!runningThreadSet.isEmpty()) {
            for (Thread thread : runningThreadSet) {
                thread.interrupt();
            }
        }
    }

    /**
     * 解析节点之间的依赖关系、开始节点集合、结束节点集合
     */
    private void parseNextDepend() {
        //解析节点依赖关系
        for (Map.Entry<String, OperatorWrapper<?, ?>> entry : wrapperMap.entrySet()) {
            OperatorWrapper<?, ?> wrapper = entry.getValue();
            //根据 depend 解析依赖关系
            Map<String, Boolean> dependWrapperIdMap = wrapper.getDependWrapperIdMap();
            if (dependWrapperIdMap != null && !dependWrapperIdMap.isEmpty()) {
                for (String dependId : dependWrapperIdMap.keySet()) {
                    OperatorWrapper<?, ?> depend = wrapperMap.get(dependId);
                    if (wrapper.getDependWrappers() != null && wrapper.getDependWrappers().contains(depend)) {
                        continue;
                    }
                    //将前驱节点添加到当前节点的依赖集合中
                    if (wrapper.getDependWrappers() == null) {
                        wrapper.setDependWrappers(new HashSet<>());
                    }
                    wrapper.getDependWrappers().add(depend);
                    //将当前节点添加到前驱节点的后继集合中
                    if (depend.getNextWrappers() == null) {
                        depend.setNextWrappers(new HashSet<>());
                    }
                    depend.getNextWrappers().add(wrapper);
                    //强依赖前驱节点时,indegree+1,弱依赖不需要
                    if (dependWrapperIdMap.get(dependId)) {
                        wrapper.getIndegree().incrementAndGet();
                        if (depend.getSelfIsMustSet() == null) {
                            depend.setSelfIsMustSet(new HashSet<>());
                        }
                        //将当前节点添加到前驱节点的强依赖集合
                        depend.getSelfIsMustSet().add(wrapper);
                    }
                }
            }
        }
        //解析开始节点、结束节点
        for (Map.Entry<String, OperatorWrapper<?, ?>> entry : wrapperMap.entrySet()) {
            OperatorWrapper<?, ?> wrapper = entry.getValue();
            if (wrapper.getDependWrappers() == null || wrapper.getDependWrappers().isEmpty()) {
                beginWrapperSet.add(wrapper);
            }
            if (wrapper.getNextWrappers() == null || wrapper.getNextWrappers().isEmpty()) {
                endWrapperSet.add(wrapper);
            }
        }
    }

    /**
     * 将节点包装成线程提交给线程池执行,或直接复用当前线程执行
     */
    private void doRun(OperatorWrapper<?, ?> wrapper, boolean inNewThread) {
        //DAG引擎状态不是RUNNING,说明编排已经执行完
        if (state != DagWrapperState.RUNNING.getStage()) {
            return;
        }
        //wrapper节点状态不等于INIT,说明该节点已经执行过
        if (!wrapper.compareAndSetState(DagWrapperState.INIT.getStage(), DagWrapperState.RUNNING.getStage())) {
            return;
        }
        if (inNewThread) {
            executor.submit(this.getRunningTask(wrapper));
        } else {
            this.getRunningTask(wrapper).run();
        }
    }

    /**
     * 获取执行节点逻辑的线程,主要处理流程逻辑如下:
     * 1、将执行线程绑定到当前节点,添加到工作线程集合,打断时使用
     * 2、执行全局的节点回调接口
     * 3、执行节点主逻辑
     * 4、节点执行异常后,停止引擎
     * 5、节点正常执行完,根据条件选择后续要执行的分支、节点(组)等
     * 6、将节点执行结果保存到上下文、在工作线程集合中删除当前线程、判断是否将信号量减一
     * 7、执行后继节点
     */
    private Runnable getRunningTask(OperatorWrapper wrapper) {
        return () -> {
            try {
                wrapper.setThread(Thread.currentThread());
                runningThreadSet.add(Thread.currentThread());

                this.doExecute(wrapper);

                wrapper.compareAndSetState(DagWrapperState.RUNNING.getStage(), DagWrapperState.FINISH.getStage());
                wrapper.getOperatorResult().setResultState(ResultState.SUCCESS);
            } catch (Throwable throwable) {
                wrapper.compareAndSetState(DagWrapperState.RUNNING.getStage(), DagWrapperState.ERROR.getStage());
                wrapper.getOperatorResult().setResultState(ResultState.EXCEPTION);
                wrapper.getOperatorResult().setEx(throwable);
                this.ex = throwable;

                //当前节点被其它节点强依赖,出现异常中断整个执行流程
                if (wrapper.getSelfIsMustSet() != null && !wrapper.getSelfIsMustSet().isEmpty()) {
                    state = DagWrapperState.ERROR.getStage();
                }
            } finally {
                //将OP的执行结果保存到上下文
                //DagContextHolder.putOperatorResult(wrapper.getInstanceName(), wrapper.getOperatorResult());
                //节点执行完,释放线程
                wrapper.setThread(null);
                //从工作线程快照中移除该节点的线程
                runningThreadSet.remove(Thread.currentThread());
                //如果是结束节点,则将信号量减一
                boolean isEndOp = false;
                if (endWrapperSet.contains(wrapper)) {
                    isEndOp = true;
                    if (endWrapperSet.size() <= syncLatch.getCount()) {
                        syncLatch.countDown();
                    }
                    endWrapperSet.remove(wrapper);
                    //结束节点全部执行完,停止DAG引擎
                    if (endWrapperSet.isEmpty()) {
                        //状态不是 RUNNING,有以下几种情况
                        //1、多线程情况下,同一时刻多个结束节点都执行完,状态被其它线程修改
                        //2、当前节点执行异常,已经设置成ERROR状态
                        //3、引擎执行超时,主线程被唤醒,设置成了ERROR状态
                        state = state == DagWrapperState.RUNNING.getStage() ? DagWrapperState.FINISH.getStage() : state;
                        //dagContext.setOutput(wrapper.getOperatorResult());
                        this.output = wrapper.getOperatorResult();
                    }
                }
                if (state != DagWrapperState.RUNNING.getStage()) {
                    //停止引擎,唤醒阻塞线程
                    shutdown(state);
                }
                //通知后继节点
                if (!isEndOp) {
                    notifyNextWrappers(wrapper, wrapper.getNextWrappers());
                }
            }
        };
    }

    /**
     * 调用目标operator的 execute 方法,主要逻辑如下:
     * 1、执行节点监听器(开始、异常、成功)
     * 2、执行节点回调接口
     * 3、执行节点主逻辑
     */
    private void doExecute(OperatorWrapper wrapper) throws Exception {
        Object operatorResult = wrapper.getOperator().execute(wrapper, context);
        wrapper.getOperatorResult().setResult(operatorResult);
        context.getContextMap().putAll((Map) operatorResult);
        // wrapper.getContext().getContextMap().putAll((Map) operatorResult);
    }

    /**
     * 停止DAG引擎,唤醒阻塞线程
     */
    private void shutdown(int currState) {
        state = currState;
        while (syncLatch.getCount() >= 1) {
            syncLatch.countDown();
        }
    }

    /**
     * 执行当前节点的后继节点,主要逻辑如下:
     * 1、后继节点强依赖当前节点,将入度减一(弱依赖时,不计入入度)
     * 2、如果后继节点入度不为零,不执行
     * 3、如果后继节点有弱依赖的节点时,后继节点在执行时可以将还在执行中或未执行的依赖节点中断
     */
    private void notifyNextWrappers(OperatorWrapper<?, ?> wrapper, Set<OperatorWrapper<?, ?>> nextWrappers) {
        if (nextWrappers == null || state != DagWrapperState.RUNNING.getStage()) {
            return;
        }
        Set<OperatorWrapper<?, ?>> selfIsMustSet = wrapper.getSelfIsMustSet();
        List<OperatorWrapper<?, ?>> needRunWrappers = null;
        for (OperatorWrapper<?, ?> next : nextWrappers) {
            //当前节点被后继节点强依赖时,需要将后继节点的 indegree-1
            if (selfIsMustSet != null && selfIsMustSet.contains(next)) {
                next.getIndegree().decrementAndGet();
            }
            //强依赖的节点没有执行完时,节点不能开始执行
            if (next.getIndegree().get() != 0) {
                continue;
            }
            //节点不能重复执行
            if (next.getWrapperState().get() != DagWrapperState.INIT.getStage()) {
                continue;
            }
            if (needRunWrappers == null) {
                needRunWrappers = new ArrayList<>();
            }
            needRunWrappers.add(next);
        }
        if (needRunWrappers != null) {
            for (int i = 0; i < needRunWrappers.size(); i++) {
                OperatorWrapper<?, ?> nextWrapper = needRunWrappers.get(i);
                boolean inNewThread = true;
                //执行最后一个后继节点时复用当前线程,其它的节点交给线程池执行
                if (i == needRunWrappers.size() - 1) {
                    inNewThread = false;
                }
                doRun(nextWrapper, inNewThread);
            }
        }
    }

}

lowcode.core.framework2.IOperator

这里把执行的方法多抽象了一个接口出来,下面的OperatorWrapper类中记录这个接口的实现,然后再调用具体的执行方法execute。

package com.example.lowcode.core.framework2;

import com.example.lowcode.core.dto2.OperatorResult;
import com.example.lowcode.core.framework.ComponentContext;

/**
 * Operator接口
 *
 * @author llxzdmd
 */
public interface IOperator<P, V> {

    /**
     * 自定义OP的默认返回值,比如节点执行异常时
     */
    default V defaultValue(P param) {
        return null;
    }

    /**
     * 该方法实现OP的具体处理逻辑
     */
    V execute(P param, ComponentContext context) throws Exception;
}

lowcode.core.framework2.DefaultInvokeMethodComponent

IOperator接口的实现类

package com.example.lowcode.core.framework2;

import com.example.lowcode.core.exception.FlowConfigException;
import com.example.lowcode.core.framework.AbstractComponent;
import com.example.lowcode.core.framework.ComponentContext;
import com.example.lowcode.core.framework.SpringUtil;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @author llxzdmd
 */
@Component
public class DefaultInvokeMethodComponent implements IOperator<OperatorWrapper<?, ?>, Object> {

    @Override
    public Object execute(OperatorWrapper<?, ?> param, ComponentContext context) throws Exception {
        return invokeMethodByName(param, context);
    }

    private Object invokeMethodByName(OperatorWrapper<?, ?> param, ComponentContext context){
        OpConfig opConfig = param.getOpConfig();
        Map<String, Object> result;
        try {
            Class<?> aClass = Class.forName(opConfig.getClassName());
            AbstractComponent abstractComponent = (AbstractComponent) SpringUtil.getBean(aClass);
            result = abstractComponent.execute(context, opConfig.getComponentInfo());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

}

lowcode.core.framework2.OperatorWrapper

核心类之二,这个类用于执行dag引擎

package com.example.lowcode.core.framework2;

import com.example.lowcode.core.dto2.OperatorResult;
import com.example.lowcode.core.exception.TaskFlowException;
import com.example.lowcode.core.framework.ComponentContext;
import com.example.lowcode.core.model2.DagWrapperState;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * OP节点的包装类
 *
 * @author llxzdmd
 */
public class OperatorWrapper<P, V> {
    /**
     * 该wrapper的id,默认是Operator的全限定名
     */
    private String instanceName;
    /**
     * OP节点配置
     */
    private OpConfig opConfig;
    /**
     * 该wrapper具体要执行的目标OP
     */
    private IOperator<P, V> operator;
    /**
     * 依赖该OP的后续OP集合
     */
    private Set<OperatorWrapper<?, ?>> nextWrappers;
    /**
     * 该OP依赖的OP集合
     */
    private Set<OperatorWrapper<?, ?>> dependWrappers;
    /**
     * 该OP依赖的OP集合id
     */
    private Map<String /*dependWrapperId*/, Boolean /*当前节点是否强依赖前置节点*/> dependWrapperIdMap;
    /**
     * 强依赖于该OP的后续wrapper集合,是nextWrappers的子集
     */
    private Set<OperatorWrapper<?, ?>> selfIsMustSet;
    /**
     * 节点的入度,不同于常规的定义,这里的入度只计算强依赖的节点,当 indegree=0 时,当前OP才能执行,在一个编排流程中,一定满足如下条件
     * indegree <= dependWrappers.size()
     */
    private AtomicInteger indegree = new AtomicInteger(0);
    /**
     * OP返回的结果
     */
    private volatile OperatorResult<V> operatorResult = OperatorResult.defaultResult();
    /**
     * 当前节点执行的状态
     */
    private AtomicInteger wrapperState = new AtomicInteger(DagWrapperState.INIT.getStage());
    /**
     * 执行该节点的线程
     */
    private Thread thread;
    /**
     * 绑定的DAG执行引擎
     */
    private DagEngine<?> engine;

    public OperatorWrapper<P, V> instanceName(String instanceName) {
        this.instanceName = instanceName;
        return this;
    }

    public OperatorWrapper<P, V> opConfig(OpConfig opConfig, IOperator<P, V> operator) {
        this.opConfig = opConfig;
        this.operator = operator;
        return this;
    }

    public OperatorWrapper<P, V> engine(DagEngine<?> engine) {
        if (engine == null) {
            throw new TaskFlowException("DagEngine is null");
        }
        if (instanceName == null) {
            throw new TaskFlowException("id is null");
        }
        if (engine.getWrapperMap().containsKey(instanceName)) {
            throw new TaskFlowException("id duplicates");
        }
        engine.getWrapperMap().put(instanceName, this);
        this.engine = engine;
        return this;
    }

    public OperatorWrapper<P, V> depend(String ... wrapperIds) {
        if (wrapperIds == null) {
            return this;
        }
        for (String wrapperId : wrapperIds) {
            this.depend(wrapperId, true);
        }
        return this;
    }

    public OperatorWrapper<P, V> depend(String wrapperId, boolean isMust) {
        if (wrapperId == null) {
            return this;
        }
        if (dependWrapperIdMap == null) {
            dependWrapperIdMap = new HashMap<>();
        }
        dependWrapperIdMap.put(wrapperId, isMust);
        return this;
    }

    public boolean compareAndSetState(int expect, int update) {
        return this.wrapperState.compareAndSet(expect, update);
    }

    @Override
    public String toString() {
        return "OperatorWrapper{" +
                "id='" + instanceName + '\'' +
                ", operator=" + operator +
                '}';
    }

    public String getInstanceName() {
        return instanceName;
    }

    public AtomicInteger getIndegree() {
        return indegree;
    }

    public IOperator<P, V> getOperator() {
        return operator;
    }

    public OperatorResult<V> getOperatorResult() {
        return operatorResult;
    }

    public AtomicInteger getWrapperState() {
        return wrapperState;
    }

    public Set<OperatorWrapper<?, ?>> getNextWrappers() {
        return nextWrappers;
    }

    public void setNextWrappers(Set<OperatorWrapper<?, ?>> nextWrappers) {
        this.nextWrappers = nextWrappers;
    }

    public Set<OperatorWrapper<?, ?>> getDependWrappers() {
        return dependWrappers;
    }

    public void setDependWrappers(Set<OperatorWrapper<?, ?>> dependWrappers) {
        this.dependWrappers = dependWrappers;
    }

    public Set<OperatorWrapper<?, ?>> getSelfIsMustSet() {
        return selfIsMustSet;
    }

    public void setSelfIsMustSet(Set<OperatorWrapper<?, ?>> selfIsMustSet) {
        this.selfIsMustSet = selfIsMustSet;
    }

    public void setThread(Thread thread) {
        this.thread = thread;
    }

    public Thread getThread() {
        return thread;
    }

    public void setEngine(DagEngine<?> engine) {
        this.engine = engine;
        this.engine.getWrapperMap().put(this.getInstanceName(), this);
    }

    public void setInstanceName(String instanceName) {
        this.instanceName = instanceName;
    }

    public Map<String, Boolean> getDependWrapperIdMap() {
        return dependWrapperIdMap;
    }

    public DagEngine<?> getEngine() {
        return engine;
    }

    public OpConfig getOpConfig() {
        return opConfig;
    }
}

lowcode.core.framework2.OpConfig

OP类的元信息

package com.example.lowcode.core.framework2;

import com.example.lowcode.core.dto.ComponentInfo;
import lombok.Data;

import java.util.List;
import java.util.Map;

/**
 * @author llxzdmd
 */
@Data
public class OpConfig {
    private ComponentInfo componentInfo;
    private String className;
    /**
     * 其它配置
     */
    private Map<String, Object> extMap;
}

lowcode.core.model2.DagWrapperState

package com.example.lowcode.core.model2;

import lombok.Getter;

/**
 * 节点执行状态枚举
 * 
 * @author llxzdmd
 * @version DagWrapperState.java, 2024年02月18日 15:45 llxzdmd
 */
@Getter
public enum DagWrapperState {
    /**
     * 初始状态
     */
    INIT(0),
    /**
     * 执行中
     */
    RUNNING(1),
    /**
     * 执行结束
     */
    FINISH(2),
    /**
     * 节点执行异常
     */
    ERROR(3),
    /**
     * 跳过当前节点
     */
    SKIP(4);

    private final int stage;

    DagWrapperState(int stage){
        this.stage = stage;
    }
}

lowcode.core.model2.ResultState

package com.example.lowcode.core.model2;

/**
 * 节点执行结果枚举
 *
 * @author llxzdmd
 */
public enum ResultState {
    /**
     * 成功
     */
    SUCCESS,
    /**
     * 超时
     */
    TIMEOUT,
    /**
     * 异常
     */
    EXCEPTION,
    /**
     * 默认
     */
    DEFAULT
}

com.example.lowcode.core.exception.FlowConfigException

自定义异常类,先随便定义了三个,暂不特殊处理什么。

package com.example.lowcode.core.exception;

/**
 * @author llxzdmd
 */
public class FlowConfigException extends RuntimeException {
    public FlowConfigException (String message) {
        super(message);
    }

    public FlowConfigException (String message, Throwable cause) {
        super(message, cause);
    }
}

lowcode.core.exception.FlowExecutionException

package com.example.lowcode.core.exception;

/**
 * @author llxzdmd
 */
public class FlowExecutionException extends RuntimeException {
    public FlowExecutionException (String message) {
        super(message);
    }

    public FlowExecutionException (String message, Throwable cause) {
        super(message, cause);
    }
}

lowcode.core.exception.TaskFlowException

package com.example.lowcode.core.exception;

/**
 * @author llxzdmd
 */
public class TaskFlowException extends RuntimeException {

    public TaskFlowException(String message) {
        super(message);
    }

    public TaskFlowException(Exception e) {
        super(e);
    }
}

lowcode.util.ConvertUtil

package com.example.lowcode.util;

import com.example.lowcode.core.framework2.OperatorWrapper;

import java.util.Set;

/**
 * 类型转换工具
 *
 * @author llxzdmd
 */
public class ConvertUtil {

    @SuppressWarnings("rawtypes")
    public static OperatorWrapper[] set2Array(Set<OperatorWrapper<?, ?>> wrapperSet) {
        if (wrapperSet == null || wrapperSet.isEmpty()) {
            return new OperatorWrapper[0];
        }
        return wrapperSet.toArray(new OperatorWrapper[0]);
    }
}

lowcode.core.service.RunServiceImpl

这里是修改实现类代码,新建V3方法

    private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            20,100,60, TimeUnit.MINUTES,new LinkedBlockingDeque<>(500),
            new ThreadFactoryBuilder().setNameFormat("pool-%d").build(),new ThreadPoolExecutor.AbortPolicy());



    @Override
    public Map<String, Object> runFlowV3(long flowId, Map<String, ComponentInfo> inputParams) {
        FlowSnapshot flowSnapshot = FlowSnapshotDO.selectByFlowId(flowId);
        assert flowSnapshot != null;
        Flow flow = JSON.parseObject(flowSnapshot.getJsonParam(), new TypeReference<>() {
        });

        DagEngine<Map<String, Object>> engine = new FlowEngineBuilder<Map<String, Object>>()
                .setFlow(flow)
                .setInputParams(inputParams)
                .setExecutor(THREAD_POOL_EXECUTOR)
                .buildEngine();
        // 这里没有直接用.builder()构造,因为要构造Map<String, Object>泛型
//        FlowEngineBuilder<Object> build = FlowEngineBuilder.builder()
//                .flow(flow)
//                .inputParams(inputParams)
//                .executor(THREAD_POOL_EXECUTOR)
//                .build();
//        DagEngine<Object> engine = build.buildEngine();
        engine.runAndWait();
        if (engine.getEx() != null) {
            throw new FlowExecutionException(String.format("【%s:%s】执行异常,原因:%s", flow.getId(), flow.getName(), engine.getEx().getMessage()), engine.getEx());
        }
        return engine.getOutput().getResult();
    }

测试

这里还用上一篇的示例进行测试,但要注意因为架构增加了组件之间的连线,因此测试方法的代码也需要加一部分,这里也贴上示例:

lowcode.service.DagRunServiceTest

package com.example.lowcode.service;

import com.alibaba.fastjson.JSON;
import com.example.lowcode.core.dto.ComponentInfo;
import com.example.lowcode.core.dto.ComponentParam;
import com.example.lowcode.core.dto.FlowNode;
import com.example.lowcode.core.dto2.Edge;
import com.example.lowcode.core.dto2.Flow;
import com.example.lowcode.core.model.ComponentTypeEnum;
import com.example.lowcode.core.model.ParamTypeEnum;
import com.example.lowcode.core.service.RunService;
import com.example.lowcode.dao.mock.ComponentMetaInfoDO;
import com.example.lowcode.dao.mock.FlowSnapshotDO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author llxzdmd
 * @version DagRunServiceTest.java, 2024年02月19日 14:41 llxzdmd
 */
@SpringBootTest
public class DagRunServiceTest {

    @Autowired
    private RunService runService;

    @Test
    public void runFlowV3Test() {
        saveFlowSnapshotTest();
        Map<String, Object> result = runService.runFlowV3(3L, new HashMap<>());
        System.out.println(result);
    }

    @Test
    public void saveFlowSnapshotTest() {
        Flow flow = new Flow();
        flow.setId(3L);
        flow.setName("测试流2.0");
        List<FlowNode> flowNodeList = new ArrayList<>();
        List<Edge> edgeList = new ArrayList<>();
        flow.setNodeInstances(flowNodeList);
        flow.setEdgeInstances(edgeList);

        // 1.组件1
        FlowNode flowNode1 = new FlowNode();
        flowNode1.setNodeName("去重组件(前端展示的组件名)");
        // flowNode1.setOrder(2);
        flowNode1.setComponentId(2L);
        flowNode1.setComponentName("DistinctFilter");
        flowNode1.setType(ComponentTypeEnum.FILTER);
        // 去重过滤器填参数
        ComponentInfo componentInfo1 = new ComponentInfo();
        componentInfo1.setComponentId(2L);
        componentInfo1.setFlowId(3L);
        Map<String, ComponentParam> inputMap1 = new HashMap<>();
        inputMap1.put("list", buildComponentParam(ParamTypeEnum.LIST, "list123", "需要去重的集合", "${inputList}", true));

        Map<String, ComponentParam> outputMap1 = new HashMap<>();
        outputMap1.put("result", buildComponentParam(ParamTypeEnum.STRING, "result", "去重组件执行结果", "result2", true));
        componentInfo1.setInputs(inputMap1);
        componentInfo1.setOutputs(outputMap1);
        flowNode1.setComponentInfo(componentInfo1);
        flowNode1.setComponentMetaInfo(ComponentMetaInfoDO.mockData().get(1));

        // 2.组件2
        FlowNode flowNode2 = new FlowNode();
        flowNode2.setNodeName("分页组件(前端展示的组件名)");
        // flowNode2.setOrder(3);
        flowNode2.setComponentId(4L);
        flowNode2.setComponentName("PageFilter");
        flowNode2.setType(ComponentTypeEnum.FILTER);
        // 分页过滤器填参数
        ComponentInfo componentInfo2 = new ComponentInfo();
        componentInfo2.setComponentId(4L);
        componentInfo2.setFlowId(3L);
        Map<String, ComponentParam> inputMap2 = new HashMap<>();
        inputMap2.put("list", buildComponentParam(ParamTypeEnum.LIST, "list456", "需要分页的集合", "${result2}", true));
        inputMap2.put("pageNum", buildComponentParam(ParamTypeEnum.INT, "pageNum111", "起始在第几页,默认1", 2, false));
        inputMap2.put("pageSize", buildComponentParam(ParamTypeEnum.INT, "pageSize111", "分页大小,默认10", 3, false));

        Map<String, ComponentParam> outputMap2 = new HashMap<>();
        outputMap2.put("result", buildComponentParam(ParamTypeEnum.STRING, "result", "分页组件执行结果", "result3", true));
        componentInfo2.setInputs(inputMap2);
        componentInfo2.setOutputs(outputMap2);
        flowNode2.setComponentInfo(componentInfo2);
        flowNode2.setComponentMetaInfo(ComponentMetaInfoDO.mockData().get(3));

        // 3.组件3
        FlowNode flowNode3 = new FlowNode();
        flowNode3.setNodeName("inputParam");
        // flowNode3.setOrder(1);
        flowNode3.setComponentId(1L);
        flowNode3.setComponentName("BuildParamComponent");
        flowNode3.setType(ComponentTypeEnum.FILTER);
        // 填参数
        ComponentInfo componentInfo3 = new ComponentInfo();
        componentInfo3.setComponentId(1L);
        componentInfo3.setFlowId(3L);
        List<String> stringList = new ArrayList<>();
        stringList.add("a");
        stringList.add("b");
        stringList.add("c");
        stringList.add("d");
        stringList.add("e");
        stringList.add("f");
        stringList.add("g");
        stringList.add("c");
        stringList.add("a");
        Map<String, ComponentParam> inputMap3 = new HashMap<>();
        inputMap3.put("params", buildComponentParam(ParamTypeEnum.MAP, "list", "入口入参list", new HashMap<>() {{
            put("inputList", stringList);
        }}, true));

        Map<String, ComponentParam> outputMap3 = new HashMap<>();
        outputMap3.put("result", buildComponentParam(ParamTypeEnum.STRING, "result", "入参结果", "result1", true));
        componentInfo3.setInputs(inputMap3);
        componentInfo3.setOutputs(outputMap3);
        flowNode3.setComponentInfo(componentInfo3);
        flowNode3.setComponentMetaInfo(ComponentMetaInfoDO.mockData().get(0));

        // 4.组件4
        FlowNode flowNode4 = new FlowNode();
        flowNode4.setNodeName("HttpClient-123");
        // flowNode4.setOrder(4);
        flowNode4.setComponentId(3L);
        flowNode4.setComponentName("HttpClient");
        flowNode4.setType(ComponentTypeEnum.FILTER);
        // 填参数
        ComponentInfo componentInfo4 = new ComponentInfo();
        componentInfo4.setComponentId(3L);
        componentInfo4.setFlowId(3L);
        componentInfo4.setInputs(new HashMap<>() {{
            put("url", new ComponentParam().setType(ParamTypeEnum.STRING).setRequired(true).setName("url").setValue("https://www.baidu.com"));
            put("method", new ComponentParam().setType(ParamTypeEnum.STRING).setRequired(true).setName("method").setValue("get"));
            put("params", new ComponentParam().setType(ParamTypeEnum.STRING).setRequired(false).setName("params").setValue(new HashMap<>()));
            put("headers", new ComponentParam().setType(ParamTypeEnum.STRING).setRequired(false).setName("headers").setValue(new HashMap<>()));
        }});
        componentInfo4.setOutputs(new HashMap<>() {{
            put("result", new ComponentParam().setType(ParamTypeEnum.STRING).setRequired(true).setName("result").setValue("result4"));
        }});
        flowNode4.setComponentInfo(componentInfo4);
        flowNode4.setComponentMetaInfo(ComponentMetaInfoDO.mockData().get(2));

        // 5.组件5
        FlowNode flowNode5 = new FlowNode();
        flowNode5.setNodeName("outputParam");
        // flowNode5.setOrder(5);
        flowNode5.setComponentId(1L);
        flowNode5.setComponentName("BuildParamComponent");
        flowNode5.setType(ComponentTypeEnum.FILTER);
        // 填参数
        ComponentInfo componentInfo5 = new ComponentInfo();
        componentInfo5.setComponentId(1L);
        componentInfo5.setFlowId(3L);
        Map<String, ComponentParam> inputMap5 = new HashMap<>();
        inputMap5.put("params", buildComponentParam(ParamTypeEnum.MAP, "params", "呃呃", new HashMap<>() {{
            put("pageResult", "${result3}");
            put("httpResult", "${result4}");
        }}, true));
        Map<String, ComponentParam> outputMap5 = new HashMap<>();
        outputMap5.put("result", buildComponentParam(ParamTypeEnum.STRING, "result", "输出结果", "result5", true));
        componentInfo5.setInputs(inputMap5);
        componentInfo5.setOutputs(outputMap5);
        flowNode5.setComponentInfo(componentInfo5);
        flowNode5.setComponentMetaInfo(ComponentMetaInfoDO.mockData().get(0));

        flowNodeList.add(flowNode1);
        flowNodeList.add(flowNode2);
        flowNodeList.add(flowNode3);
        flowNodeList.add(flowNode4);
        flowNodeList.add(flowNode5);

        Edge edge1 = new Edge();
        edge1.setSourceName("inputParam");
        edge1.setTargetName("去重组件(前端展示的组件名)");

        Edge edge2 = new Edge();
        edge2.setSourceName("去重组件(前端展示的组件名)");
        edge2.setTargetName("分页组件(前端展示的组件名)");

        Edge edge3 = new Edge();
        edge3.setSourceName("inputParam");
        edge3.setTargetName("HttpClient-123");

        Edge edge4 = new Edge();
        edge4.setSourceName("分页组件(前端展示的组件名)");
        edge4.setTargetName("outputParam");

        Edge edge5 = new Edge();
        edge5.setSourceName("HttpClient-123");
        edge5.setTargetName("outputParam");

        edgeList.add(edge1);
        edgeList.add(edge2);
        edgeList.add(edge3);
        edgeList.add(edge4);
        edgeList.add(edge5);

        // 调用保存组件流快照的接口,模拟入库
        FlowSnapshotDO.insertData(3L, JSON.toJSONString(flow));
    }

    private ComponentParam buildComponentParam(ParamTypeEnum typeEnum, String name, String desc, Object value, boolean required) {
        ComponentParam componentParam = new ComponentParam();
        componentParam.setType(typeEnum);
        componentParam.setName(name);
        componentParam.setDesc(desc);
        componentParam.setValue(value);
        componentParam.setRequired(required);
        return componentParam;
    }
}

另外为了方便演示,这里还在每个组件前面展示了当前线程名;

以及纠正一个之前的错误,本次v2.0升级架构后,不需要在每个组件最后手动把结果放入上下文了,放到执行的方法中就可以了,看来还是设计模式学的不到位啊。

最后注:这次把ComponentContext类中的上下文定义为ConcurrentHashMap了,防止多个组件同时修改上下文时出现线程安全问题。

运行结果:

可以看到用到了两个线程,有兴趣的话可以多试试其他的测试用例,等有时间我也会补充上。


这里因为好奇线程安全问题,测试了一下并发场景

    private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            20, 1000, 60, TimeUnit.MINUTES, new LinkedBlockingDeque<>(500),
            new ThreadFactoryBuilder().setNameFormat("pool-%d").build(), new ThreadPoolExecutor.AbortPolicy());

    @Test
    public void testJUC1() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(1000);
        int numberOfTasks = 1000;
        try {
            saveFlowSnapshotTest();
        } catch (Exception e) {
            System.out.println("ERROR1");
            System.out.println(e.getMessage());
        }
        for (int i = 0; i < numberOfTasks; i++) {
            THREAD_POOL_EXECUTOR.submit(() -> {
                try {
                    Map<String, Object> result = runService.runFlowV3(3L, new HashMap<>());
                } catch (Exception e) {
                    System.out.println("ERROR2");
                    System.out.println(e.getMessage());
                }
                try {
                    latch.countDown();
                } catch (Exception e) {
                    System.out.println("ERROR3");
                    System.out.println(e.getMessage());
                }
            });
        }
        latch.await();
    }

发现每次运行都会报几次 ERROR2 错误

显示原因是DagEngine类的output属性获取为null,在几个关键的位置都设置埋点后定位到是在runFlowV3方法的最后一行:“return engine.getOutput().getResult();”处报的这个错。

自己研究了一大会儿,也和一个同事讨论了半天,都想不出为什么会有这个问题。

带着疑惑又去跑了github上的源码,发现尽管源码里面包了ThreadLocal等结构,也会报这个错。所以这个问题暂时无解了,不知到会不会有大佬看到这个问题并解答一下。

总结

        先不写了之后补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值