CFG 和 DAG 综述
在需求分析阶段,选择 控制流图 (CFG) 还是 依赖图 (DAG),核心决策点在于:你关注的是“执行逻辑的完整性(包含循环和条件)”还是“任务调度的并行性与顺序(无环依赖)”。
这是一个从业务逻辑描述到工程执行模型的转化过程。以下是详细的决策指南:
🚀 核心决策矩阵
维度 选择 CFG (控制流图) 选择 DAG (有向无环图)
核心关注点 逻辑正确性:流程怎么走?分支怎么选?循环怎么跳? 执行效率:哪些能并发?谁先谁后?关键路径在哪?
结构特征 允许循环 (Loops)、复杂嵌套分支、状态跳转。 严禁循环,必须是单向流动的依赖关系。
典型场景 业务流程设计、算法逻辑推导、状态机建模、用户交互流程。 大数据计算 (Spark/Airflow)、编译优化、CI/CD 流水线、微服务编排。
运行时语义 单线程/顺序主导:通常由一个控制器按步骤一步步走,遇到判断做选择。 并行/分布式主导:调度器根据依赖就绪情况,同时触发多个任务。
互斥性处理 原生支持:菱形决策节点天然表达 if-else 互斥。 不支持:需通过标签、网关或拆分图来额外表达互斥。
需求阶段 需求调研 & 逻辑设计阶段 (What & How) 架构设计 & 性能优化阶段 (When & Where)
🧐 需求分析时的 4 个关键决策点 (Checklist)
在拿到需求文档时,问自己以下 4 个问题,答案将直接决定选型:
流程中是否存在“循环”或“回溯”?
是 (例如:while 重试、goto 错误回退、用户反复修改提交):
👉 必须选 CFG。DAG 无法表达循环,强行转 DAG 会丢失逻辑或需要无限展开。
否 (流程也是线性的,或者虽然有分支但最终汇聚且不回退):
👉 可以考虑 DAG。
系统的核心目标是“逻辑严密”还是“执行速度/并发”?
逻辑严密 (例如:银行转账审批流,必须严格按步骤,一步错了就停,不能乱序):
👉 选 CFG。重点在于状态流转的正确性。
执行速度/并发 (例如:渲染农场、数据清洗,A 和 B 没依赖就要同时跑,谁快先谁):
👉 选 DAG。重点在于挖掘最大并行度。
“互斥分支”是业务的核心复杂性吗?
是 (例如:复杂的规则引擎,不同条件触发完全不同的子流程,且逻辑嵌套深):
👉 选 CFG。用菱形节点表达清晰,DAG 处理复杂互斥会很别扭(需要大量元数据)。
否 (分支很简单,或者分支只是数据处理的可选路径,不影响整体拓扑):
👉 选 DAG。可以用带标签的边简单处理。
执行环境是“状态机”还是“调度器”?
状态机/工作流引擎 (如 Activiti, Camunda):
👉 底层模型通常是 CFG (或 BPMN,本质是带状态的 CFG)。
任务调度器 (如 Airflow, Kubernetes Job, Make, Build System):
👉 底层模型必须是 DAG。调度器只认“依赖就绪”,不认“如果...那么...”。
💡 实际工作流中的演变策略
在实际的软件工程中,这往往不是“二选一”,而是“先 CFG 后 DAG”的转换过程:
阶段 1:需求分析与业务建模 -> 使用 CFG
目的:与产品经理、业务方沟通,确保逻辑无漏洞。
产出:包含循环、异常处理、复杂判断的流程图。
理由:人类思维是过程式的,CFG 最符合人类直觉。此时强行画 DAG 会让业务方困惑(“为什么这里没有循环?”“为什么这两个互斥的任务画在一起?”)。
阶段 2:架构设计与性能优化 -> 转换为 DAG
目的:指导开发实现,特别是涉及并发、分布式执行时。
动作:
展平循环:将 while 循环转化为“单次执行 + 外部重试机制”或“递归子图”。
剥离控制:将 if-else 决策转化为“带条件的边”或拆分为多个静态执行计划。
识别并行:找出 CFG 中没有依赖关系的节点,在 DAG 中将其并列。
产出:可被调度器执行的依赖图。
🌰 举例说明
案例 A:用户注册流程
需求:用户填表 -> 校验格式(错则返回重填) -> 查重(存在则提示) -> 发送验证码 -> 用户输入 -> 校验(错则重输) -> 写入数据库。
分析:
有大量的“返回重填/重输” (循环)。
强依赖用户交互状态。
决策:CFG。
原因:这是一个典型的状态机,必须记录当前处于哪一步,允许回退。DAG 无法优雅表达“回到上一步”。
案例 B:每日新闻推荐生成
需求:拉取新闻源 -> 清洗数据 -> (并行) 提取关键词 / 计算热度 / 识别类别 -> 合并结果 -> 排序 -> 推送。
分析:
无循环(每天跑一次,跑完即止)。
中间步骤(提取、计算、识别)完全独立,可以并行。
主要瓶颈是耗时,需要最大化并发。
决策:DAG。
原因:这是典型的 ETL 管道。用 DAG 可以清晰地告诉 Spark/Airflow:“清洗一结束,立刻同时启动这三个任务”。
📝 总结建议
画图给老板/产品看:用 CFG。他们关心业务逻辑通不通,有没有死循环,异常怎么处理。
写代码给机器跑:用 DAG。编译器、调度器、分布式框架只认 DAG。
如果需求里既有复杂循环又要高性能并发:
外层用 CFG 管理状态和循环(如重试机制)。
内层的具体执行批次用 DAG 描述并行任务。
这就是现代工作流引擎(如 Argo Workflows, Tekton)的做法:Workflow (CFG) 包含 Steps (DAG)。
一句话口诀:
讲逻辑、有循环、重状态,选 CFG;
跑任务、要并发、无回路,选 DAG。

浙公网安备 33010602011771号