工作流和状态机
起源
因为工作相关的一些原因,最近开始看一些工作流的框架或者产品,有兴趣的可以看我这篇文章。任务流是一种很常见的任务组织形式:
graph LR
Start-->Step1
Step1-->Step2
Step2-->End
这种例子充斥我们的生活,比如订单系统,Hr系统,审批系统,CI/CD 系统,都是任务流的常见引用。
Dag
我们常见两种任务流的实现形式,一种是 DAG,dag 的表达的核心在于描述单个任务, 比如 描述一个 Task 他完成什么样的动作,以及他依赖什么样的动作,DAG 的描述和他的名字一样,缺陷在于不支持有环的任务流转(这一点只是为了简化,实际上也有办法实现)
TaskA:
Actions:
- 动作 A
Dependencies:
TaskB:
Actions:
- 动作 B
Dependencies:
- TaskA
TaskC:
Actions:
- 动作 C
Dependencies:
- TaskA
- TaskB
对这个 dag 进行简单的拓扑排序,就能得到他大概的执行流程, 下图为依赖图,首先会执行 TaskA,然后 TaskB,最后 TaskC
graph LR
TaskA-->TaskB
TaskA-->TaskC
TaskB-->TaskC
这个计算过程可以用下面的伪代码来简单描述,可以看出实现非常简单
For task in Tasks:
if all_done(task's Dependencies):
do(task)
状态机
状态机是实现任务流的另一种形式,表达的核心在于描述任务流转行为,即 Transition.
下面是一个 状态机的例子
Start: TaskA
TaskA:
Actions:
- 动作A
Next:
TaskB
TaskB:
Actions:
- 动作B
Next:
TaskC
TaskC:
Actions:
- 动作C
Next:
End
用一个简化的图表示:
graph LR
Start-->TaskA
TaskA-->TaskB
TaskB-->TaskC
TaskC-->End
对比
Dag |
状态机 |
---|---|
关注单个任务 |
关注状态流转 |
无环(也能实现,不过没这么直观) |
可以简单的实现有环 |
实现简单 |
比较麻烦,需要记录任务当前状态 |
可以多个 Start |
单个 Start |
灵活性
Dag 串行表达,看上去不是很灵活, 而状态机表达更灵活,可以有环,可以任意流转。实际上并非如此:想象这样一种场景,如下图中的任务流程,我们需要新增一个任务 D,在任务 A 后执行,在任务 C 之前执行。对于这种变化,对 Dag 的修改是简单的,我们只需要新增一个节点就可以了, 同时修改节点 D 增加一个依赖:
TaskA:
Actions:
- 动作 A
Dependencies:
TaskB:
Actions:
- 动作 B
Dependencies:
- TaskA
TaskC:
Actions:
- 动作 C
Dependencies:
- TaskA
- TaskB
- TaskD
TaskD:
Actions:
- 动作 D
Dependencies:
- TaskA
而对于状态机,同样的修改则更为麻烦,D 应该放在什么位置呢,首先考虑到 D 在 A之后执行,又在 D 之前,那么我们可以推理出:B和D可以并发执行, 然而这个结论并不那么显而易见,对于更负责的任务流转更是如此,涉及的修改也更多。新增一个流程对原先的状态机破坏,可能是颠覆性的。
Start: TaskA
TaskA:
Actions:
- 动作A
Next:
TaskB
Parallel:
Tasks:
TaskB
Actions:
- 动作B
TaskD
Actions:
- 动作D
Next:
TaskC
TaskC:
Actions:
- 动作C
Next:
End
可读性
状态机模型似乎可读性更高,因为 Dag 关注描述任务,一眼很难看出任务流转的模式,状态机直接描述任务流转,内部就可以很明显的看出每个 Stage。事实并非总是如此。想象一个任务流程有几百个任务, 这种情况下,即使是任务流所在的领域,比如 CRM 领域的专家也很难读懂整个任务流,这时候阅读当个任务的动作或者依赖,变成了一种更为简单直观的方式,通过好的前端实现,Dag 的可读性至少不会比 状态机差。
效率
不管是从实现相关系统的效率(Dag 的实现更为简单),还是从描述一个任务流程的效率(Dag 的描述更简单,状态机往往有更多概念,比如 并行,Map等),还是具体的运行效率(考虑上面的例子,确定两个任务可以并发执行,并不是一件容易的事情)Dag 都有相当的优势。
总结
最近的实现 Aws 的 statemachine spec,以及类似的 云厂商都推出了类似的状态机描述 spec,以及对应的实现。cncf 甚至推出了一个更复杂的实现。然而事实却没那么理想,各个云产商应该是跟随 AWS 做了一个错误的决策。
和这些产品相比,AWS 的一个老产品 Simple Workflow 的理念(DAG)则更优秀,不过由于 AWS 当年的实现过于抽象,这个产品也不是很成功,现在已经处于不再更新的状态(开源产品 Argo 有两种表达形态,既可以用 StateMachine,也可以用 Dag 表示)。但是未来的某一天,产品经理们可能会把它从垃圾堆里重新翻出来,因为他们最终发现,这才是正确的产品。
参考
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- android RecycleView实现下拉刷新和上拉加载
- 解析Android 8.1平台SystemUI 导航栏加载流程
- Mysql系列第二十二讲 mysql索引管理详解
- Android自定义View实现五子棋小游戏
- 通过.htaccess防盗链
- Android实现聊天记录上传本地服务器(即时通讯)
- Android10.0实现本地音乐播放(附源码下载)
- python - 获取网站PR及百度权重
- 详解Android Studio3.5及使用AndroidX的一些坑
- AndroidQ分区存储权限变更及适配的实现
- Gradle的缓存路径修改的四种方法(小结)
- 适配AndroidQ拍照和读取相册图片的实现方法
- 这是我的第一篇文章
- Android自定义View实现五子棋游戏
- AndroidQ(10)黑暗模式适配的实现