Skip to content

Commit 42cf9ed

Browse files
committed
图引擎文章一篇
1 parent 7b474f4 commit 42cf9ed

6 files changed

Lines changed: 192 additions & 1 deletion

File tree

57.3 KB
Loading
36.4 KB
Loading
229 KB
Loading
144 KB
Loading
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
+++
2+
title = "策略调度引擎的演进"
3+
date = 2025-10-02T13:00:00+08:00
4+
author = "Skyan"
5+
tags = ["framework", "programming"]
6+
ShowToc = true
7+
ShowBreadCrumbs = true
8+
+++
9+
10+
在搜索、推荐和广告等高复杂度在线系统中,“策略算法的调度执行”是支撑系统智能的基础能力。为了高效运行各种策略算法,几乎所有大型平台最终都走向了相同的抽象:将策略算法封装为算子(Operator),并通过 DAG(有向无环图)驱动其有序并发执行。这种调度引擎的设计已经历二十余年的演进,并在实践中不断向更高层次的智能和复杂性发展。
11+
12+
策略调度引擎通常位于算法密集型在线服务的执行框架之中。下图展示了一个典型推荐系统的策略流程(搜索和广告系统也类似):
13+
```mermaid
14+
graph TD
15+
A[解析请求参数] --> B[召回算法1]
16+
A --> C[召回算法2]
17+
A --> D[召回算法3]
18+
B --> E[去重]
19+
C --> E
20+
D --> E
21+
E --> F[过滤]
22+
G[历史曝光] --> F
23+
F --> H[排序]
24+
H --> I[重排序]
25+
I --> J[返回结果]
26+
```
27+
28+
上述流程只是推荐系统的一个高层抽象。真实系统中,这样的流程会跨越数十甚至上百个微服务,每个微服务内部都可能执行一个多步骤策略流水线,规模可达几十到几百万行代码。在这种背景下,如何高效开发各阶段策略,并以流程化方式调度执行,成为系统设计的基础问题。
29+
30+
## 第一代引擎——算子调度(2004~2014)
31+
32+
第一代引擎诞生于桌面互联网兴起到移动互联网爆发前夜的十年间。当时以搜索和广告为代表的复杂在线系统,开始采用面向对象和微服务的设计思想,并把策略算法抽象为算子对象,由调度引擎顺序执行。
33+
34+
这一代的核心特征是:
35+
36+
* **策略抽象为算子,实现配置化的调度执行**
37+
38+
算子化的设计带来了低耦合、高内聚的好处。推荐各阶段复杂算法被封装为独立算子,不同策略可以交给不同的开发者负责,调度引擎只负责顺序调度,从而实现了调度逻辑与策略逻辑的解耦。
39+
40+
但受制于当时的硬件和语言特性,这一代调度主要是单线程串行执行,对多核 CPU 的利用率极低。随着硬件演进和业务规模增长,这种调度方式逐渐暴露出瓶颈。
41+
42+
第一代策略调度引擎一般没有统一的标准,即使在一个系统中,每个微服务内部都有各自不同的调度框架。配置的方式,调度的方式也各有差别。以广告系统为例,一个典型的高级排序服务内部流程如下:
43+
```mermaid
44+
graph TD
45+
A[解析请求参数] --> B[获取特征]
46+
B --> C[广告触发/基础位索引]
47+
C --> D[广告排序]
48+
D --> E[获取物料]
49+
E --> F[返回结果]
50+
```
51+
52+
类结构如下:
53+
```mermaid
54+
classDiagram
55+
class Strategy {
56+
<<interface>>
57+
}
58+
class Scheduler
59+
class ParserStrategy
60+
class SearchStrategy
61+
62+
Scheduler ..> Strategy : Use
63+
Strategy <|-- ParserStrategy : Extends
64+
Strategy <|-- SearchStrategy : Extends
65+
Strategy <|-- OtherStrategies : Extends
66+
67+
class OtherStrategies
68+
```
69+
70+
框架通过Strategy设计模式,实现了逻辑上的抽象和隔离。策略算子的调度以顺序执行为主,并行计算很少。
71+
72+
第一代引擎结构上简单,但缺点也很明显,无法支持更复杂的并行调度方式,无法充分利用多核CPU资源。随着数据中心服务器核数的不断增多,可供在线服务使用的CPU资源越来越多,而第一代调度引擎,无法充分利用多核CPU资源,逐步落后于时代。
73+
74+
## 第二代引擎——DAG 调度(2014~2019)
75+
76+
第二代调度引擎诞生于移动互联网大爆发时期,引入了 DAG(Directed Acyclic Graph)调度的思想,将每个算子抽象为图中的节点,按拓扑序并发执行。这使得策略调度的并行度大幅提升,多核 CPU 资源得以充分利用。各个在线系统可以实现更为复杂的图结构。
77+
78+
下图展示了某推荐场景下的 DAG 拓扑示意:
79+
![dag](image.png)
80+
81+
上述的策略调度图,一共有几十个策略节点,中间还有各种并行执行阶段,包括多层扇入和扇出的拓扑结构,整体执行流程已经足够复杂。这只是一个一般规模的流程图,更大规模的调度图中,包含上百个策略节点可以执行各种并行、串行的调度运行。由于并行度大幅提升,这种调度引擎可以充分利用多核CPU的性能优势,顺应了多核多CPU硬件系统的发展趋势。
82+
83+
第二代调度引擎具备很强的通用性,可以在多个服务中作为基础组件使用。在很多搜索/广告和推荐系统内部,都有类似实现的调度引擎来实现DAG调度。以taskflow[^7]为代表的开源DAG调度引擎,实现了类似的功能。
84+
85+
然而,这种调度却存在先天的缺陷,导致了在更大规模DAG图和算子节点的线程安全性之间,存在天然的矛盾。第二代引擎虽然继承了第一代引擎抽象算子的思想,将策略算法抽象为节点。然而,算法和数据是密不可分,每个算子节点都需要读和写变量数据的。以下面一个典型的三节点并行调度为例,可以看到多节点并行执行时,很难避免对同一对象的读写:
86+
```mermaid
87+
---
88+
title: 多线程访问上下文数据示意图
89+
---
90+
graph LR
91+
subgraph 线程1
92+
A[节点1]
93+
end
94+
subgraph 线程2
95+
B[节点2]
96+
end
97+
subgraph 线程3
98+
C[节点3]
99+
end
100+
101+
A -->|写| D((上下文数据))
102+
B -->|写| D
103+
D -->|读| C
104+
105+
style D fill:#f9f,stroke:#333,stroke-width:2px
106+
```
107+
108+
在第二代DAG引擎中,对于数据的处理很容易发生上述的多线程读写冲突问题,当多个线程同时读写同一份数据对象时,会导致数据错误,甚至会抛异常,对于C++程序而言,还会导致内存段错误引起的进程崩溃问题。
109+
110+
在真实的生产环境中,这样的问题往往难以排查,定位和解决问题的效率很低。尤其对于成百上千算子的DAG图,排查这类race condition问题将耗费大量的人力。
111+
112+
有的二代调度引擎,也考虑了数据的流转关系,设计了类似Pandas Dataframe的共享数据结构,希望将节点间共享的数据,统一读写到类似Dataframe的数据对象中。这类对象具备多列表格式的并发读写接口,可以方便编程开发。这种数据读写接口一般是采用加锁的方式来实现并发安全,但这样又会限制DAG整体运行的并发度。因此在并发度和并发安全之间,两者无法两全。总得来说,无论如何优化,并不能摆脱其设计思想的过时:**无法将数据和计算分离**,是这类调度引擎刻在基因中的缺陷和不足。
113+
114+
综上,第二代策略调度引擎实现了DAG调度能力,实现了算子节点之间的调度执行。然而,这一代引擎很难解决数据依赖和并发读写问题,这样引发了第三代引擎的诞生和发展。
115+
116+
## 第三代引擎——数据流调度(2019 ~ 今)
117+
118+
在2019年前后,国内各大互联网公司内部开始意识到第二代引擎的缺陷,从而不约而同地提出第三代引擎,其核心思想在各平台实现中有异曲同工之妙。它同时兼顾了数据并发安全和节点并行计算的优点,最大的特点就是:
119+
120+
* **将数据和计算解耦,实现数据与计算的协同调度**
121+
122+
以下图为例,可以清楚看到第三代调度引擎的调度方式:
123+
```mermaid
124+
---
125+
title: Data5 = (Data1 + Data2) * Data4
126+
---
127+
graph TD
128+
D1((Data1)) --> Add
129+
D2((Data2)) --> Add
130+
Add[Add] --> D3((Data3))
131+
D3 --> Multiple
132+
D4((Data4)) --> Multiple
133+
Multiple[Multiple] --> D5((Data5))
134+
135+
classDef dataNode fill:#f5f5f5,stroke:#666
136+
class D1,D2,D3,D4,D5 dataNode
137+
```
138+
139+
整个图分解为数据和算子节点两种,当数据就绪时触发计算,计算产出新的数据,再驱动下游算子执行,如此递归推进。这种引擎最大的好处是将数据和计算的依赖关系在图中显式定义。在引擎调度中,可以避免类似多节点并发写数据,以及并发读写同一个数据对象的问题,最大程度上解决了数据并发读写和算子并发执行之间的矛盾。
140+
141+
第三代引擎的思想本质上源自Tony Hoare于1978年提出[^2]的CSP(Communicating Sequential Processes)[^1]的古老思想。它以通信代替共享数据同步,天然无锁、易于理解,并成为并发编程最具生命力的思想之一。经过几十年的发展,CSP的思想在Unix系统中的管道,以及Go语言的goroutine和channel[^3]等设计中大放异彩。
142+
143+
第三代调度引擎的代表性实现有tensorflow项目的计算图[^4]。它以边为tensor数据,节点为计算函数,实现了tensor在节点之间的流动。
144+
![tensorflow](image-1.png)
145+
146+
同样,在著名开源项目anyflow[^5]中,实现了采用同样思想的图引擎,实现了数据和计算节点的并发调度。同时扩展了数据和边的类型,支持条件边(condition),pipeline数据(channel)等高级能力。功能丰富,性能优秀,具备工业级质量。
147+
![graph-engine](image-2.png)
148+
149+
淘宝的TPP图引擎[^6],本质上也是这类思想。它通过java的annotation机制,定义数据字段之间的依赖关系,通过图化编排的方式实现了算子的DAG调度和数据的流转,同时配合AIRec平台,实现整图运行时的可视化。
150+
![tpp](image-3.png)
151+
152+
第三代引擎在国内发展已经有6年多时间,基本上在成熟的搜广推系统中得到了广泛的应用。它的生命力还不止如此,在机器学习引擎、数据流计算、甚至深度学习编译器领域都有同样思想的不同框架实现。
153+
154+
## 下一代引擎——全局图调度
155+
随着2023年AI大模型的迅速爆发式发展,搜索、广告和推荐领域产生了巨大的变化。首当其中的就是各种生成式模型被用来改造搜广推系统。伴随着大模型引入到在线服务中,分布式微服务的耗时和内存带宽是一个巨大的约束条件。如何实现低延迟高吞吐的在线系统的同时,还能最大化整个分布式系统的CPU、GPU、内存、网络、SSD等资源的利用率,充分发掘生成式大模型的智能能力,是下一代策略调度需要面临的挑战。
156+
157+
下一代的策略调度引擎,将会延续“数据与计算解耦并协同调度”的思想,将视角放到全局分布式系统的数据和计算调度中,催生出新的愿景:
158+
* **从单机图调度演进为跨服务、跨节点的全局图调度系统**
159+
160+
在这种模式下,整个分布式系统被抽象为一个全局运行时图(Global Execution Graph)。调度对象从算子拓展到子图、微服务、RPC 调用、消息流甚至模型本身。数据流不仅包括内存数据,还包括网络数据、消息事件和持久化状态。
161+
162+
这一代引擎可能具备的新特性包括:
163+
* 跨语言 / 跨协议 / 跨基础设施的统一 IR 层(如 MLIR for system);
164+
* 节点可以是模型、算子、RPC 调用、异步流、消息队列事件;
165+
* 调度器能做全局最优(性能、延迟、收益、成本)规划;
166+
* 图本身是动态演化的(支持拓扑自适应和在线重排)。
167+
168+
这意味着,调度引擎将不再是某个服务内部的“算法执行器”,而是整个系统的分布式操作系统内核,实现端到端全局调度与在线自适应。这在具体实现中,将面临巨大的挑战。
169+
170+
## 总结
171+
我们可以将策略调度引擎的发展概括如下:
172+
173+
| 代际 | 名称 | 核心思想 | 本质变化 | 代表项目 |
174+
|------|------|----------|-----------|-----------|
175+
| 第一代 | 算子调度引擎 | 算法算子化、串行配置化调度 | 调度粒度:函数级 | 各公司内部框架 |
176+
| 第二代 | DAG调度引擎 | 支持复杂依赖和并行调度,部分数据路径优化 | 调度粒度:任务级 | taskflow等 |
177+
| 第三代 | 数据流调度引擎 | 节点是计算,边是数据流,数据与计算完全解耦并协同调度 | 数据成为一等公民,调度基于数据流拓扑 | anyflow、TPP等 |
178+
| 第四代 | 全局图调度引擎 | 跨服务、跨节点的统一 DAG 调度,RPC/消息流也纳入图内统一调度 | 调度对象:系统级图,具备全局最优能力 | 即将出现 |
179+
180+
伴随着 GPU 等硬件的进步和大模型的普及,策略调度引擎正在向更高层次的智能和复杂性演化。第四代“全局图调度引擎”代表了面向 AI 时代的新一代引擎形态。
181+
虽然数据与计算的协同调度的思想源远流长,但它的生命力却在每个时代都焕发出新的活力。
182+
183+
184+
## 参考文献
185+
[^1]: [Communicating sequential processes wikipedia](https://en.wikipedia.org/wiki/Communicating_sequential_processes)
186+
[^2]: Hoare, C. A. R. (1978). "Communicating sequential processes". Communications of the ACM. 21 (8): 666–677. doi:10.1145/359576.359585. S2CID 849342.
187+
[^3]: [Bell Labs and CSP Threads](https://swtch.com/~rsc/thread/)
188+
[^4]: [Introduction to graphs and tf.function](https://www.tensorflow.org/guide/intro_to_graphs)
189+
[^5]: [anyflow document](https://github.com/baidu/babylon/blob/main/docs/anyflow/README.zh-cn.md)
190+
[^6]: [打造算法在线服务领域极致开发体验与性能 — 阿里 TPP 图化框架技术实践](https://mp.weixin.qq.com/s/lsvNNogV_dg-Xd6h_5TKmQ)
191+
[^7]: [taskflow: A General-purpose Task-parallel Programming System](https://taskflow.github.io/)

hugo.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ baseURL: https://skyan.github.io
22
languageCode: zh-Hans
33
title: Sky's Blog
44
theme: ["PaperMod"]
5-
copyright: "© 2024 [Sky's Blog](https://skyan.github.io)"
5+
copyright: "© 2025 [Sky's Blog](https://skyan.github.io)"
66
pagination:
77
pagerSize: 10
88

0 commit comments

Comments
 (0)