简历回答逐字稿:低代码工作流引擎 DAG 架构设计
关联:简历追问:低代码工作流引擎 DAG 架构设计、15分钟:低代码平台。
30 秒开场
我做这个工作流引擎时,重点不是画一个流程图,而是让画布配置真的能被运行时稳定执行。低代码里很多流程看起来是连线,但背后要解决节点依赖、并发调度、条件分支、失败重试和上下文传递。
所以我把流程协议抽象成 nodes 和 edges,运行前编译成 DAG。用 Kahn 算法做拓扑排序和环检测,入度为 0 的节点先进入队列,节点完成后释放下游节点。这样 Start、Form、HTTP、Condition 甚至后续 LLM 节点,都可以挂在同一个调度模型上。
如果面试官问:Kahn 算法在你这里怎么用?
我会这样说:
流程保存或运行前,我会先根据 edges 构建邻接表和入度表。所有入度为 0 的节点进入队列。每弹出一个节点,就把它的下游节点入度减 1;如果下游入度变成 0,就说明依赖都满足了,可以进入待执行队列。
如果最后处理过的节点数少于总节点数,就说明图里有环。这个时候不能发布或不能运行,因为普通审批流和自动化流程默认不应该出现无限循环。
我一般会在两个时机做校验:保存时做一次,防止明显错误进入系统;运行前再做一次,防止历史配置或接口导入绕过前端校验。
如果面试官追:条件分支怎么处理?
我会说条件分支是 DAG 调度里很容易被低估的点。
比如 Condition 后面有 A、B 两条分支,只命中 A。不能只是让 B 不执行,因为 B 下游可能还有 Join 节点。如果 B 一直保持未完成,Join 的入度永远归不了零,流程就卡死。
所以我的做法是 Condition 执行后,对未命中分支做动态剪枝。未命中的节点会被标记为 SKIPPED,同时它们对下游的依赖也要被释放或标记为无效入度。这样命中路径可以继续推进,未命中路径不会阻塞主流程。
如果面试官追:并发和 fan-in 怎么办?
我会这样回答:
同一轮入度为 0 的节点理论上可以并发执行,但不能无限并发。运行时会有并发限制,比如按流程、租户或节点类型限制。HTTP 节点和批处理节点尤其要限流。
fan-in 节点本质上就是多个上游都完成后才入度归零。如果上游有失败,要看配置:有些流程是 fail-fast,一个关键节点失败就终止;有些流程允许部分失败,下游拿到的是成功输出加失败信息。这个要在节点输出和流程策略里显式表达,不能靠默认猜。
如果面试官追:失败重试和幂等怎么设计?
我会说工作流里最怕“重试导致重复副作用”。
所以每次运行会有 executionId,每个节点会有 nodeExecutionId 或 attempt。HTTP 这类有副作用的节点最好支持幂等 key,重试时带同一个业务幂等标识。节点状态会从 PENDING、RUNNING、SUCCESS、FAILED、SKIPPED、RETRYING 这些状态流转。
如果服务重启,不能只靠内存状态。至少执行记录和关键节点结果要持久化,恢复时根据状态决定继续跑、标记失败,还是等待人工处理。
如果面试官追:支持 Loop 怎么办?
我会先说第一版我会保持主图是 DAG,因为这样可解释、可测试、也容易恢复。
如果业务确实需要循环,我不会允许用户任意连环,而是把 Loop 做成一个有明确退出条件和最大次数的局部容器。主图仍然是 DAG,Loop 节点内部有自己的执行语义。这样不会破坏整个调度器的可控性。
收尾句
所以这个引擎我最看重的不是“画布能连线”,而是画布配置能被编译、校验、调度、恢复和审计。DAG 只是基础,真正难点在条件剪枝、上下文隔离、失败恢复和副作用幂等。