2021-06-05 22:39:01 +08:00
|
|
|
|
# DAG
|
|
|
|
|
|
|
|
|
|
`hyperf/dag` 是一個輕量級有向無環圖 (**D**irected **A**cyclic **G**raph) 任務編排庫。
|
|
|
|
|
|
|
|
|
|
## 場景
|
|
|
|
|
|
|
|
|
|
假設我們有一系列任務需要執行。
|
|
|
|
|
|
|
|
|
|
- 如果他們之間存在依賴關係,則可以將他們順序執行。
|
|
|
|
|
- 如果他們並不相互依賴,那麼我們可以選擇併發執行,以加快執行速度。
|
|
|
|
|
- 兩者間還存在中間狀態:一部分任務存在依賴關係,而另一些任務又可以併發執行。
|
|
|
|
|
|
|
|
|
|
我們可以將第三種複雜的場景抽象成 `DAG` 來解決。
|
|
|
|
|
|
2021-07-19 13:00:24 +08:00
|
|
|
|
## 安裝
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
composer require hyperf/dag
|
|
|
|
|
```
|
|
|
|
|
|
2021-06-05 22:39:01 +08:00
|
|
|
|
## 示例
|
|
|
|
|
|
|
|
|
|
[![](https://mermaid.ink/img/eyJjb2RlIjoic3RhdGVEaWFncmFtLXYyXG4gICAgWypdIC0tPiBBXG4gICAgQSAtLT4gQlxuICAgIEEgLS0-IENcbiAgICBBIC0tPiBEXG4gICAgRCAtLT4gR1xuICAgIEMgLS0-IEdcbiAgICBDIC0tPiBGXG4gICAgQiAtLT4gRlxuICAgIEIgLS0-IEVcbiAgICBCIC0tPiBIXG4gICAgSCAtLT4gSVxuICAgIEUgLS0-IElcbiAgICBGIC0tPiBJXG4gICAgRyAtLT4gSVxuICAgIEkgLS0-IFsqXVxuICAgICAgICAgICAgIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQiLCJ0aGVtZVZhcmlhYmxlcyI6eyJiYWNrZ3JvdW5kIjoid2hpdGUiLCJwcmltYXJ5Q29sb3IiOiIjRUNFQ0ZGIiwic2Vjb25kYXJ5Q29sb3IiOiIjZmZmZmRlIiwidGVydGlhcnlDb2xvciI6ImhzbCg4MCwgMTAwJSwgOTYuMjc0NTA5ODAzOSUpIiwicHJpbWFyeUJvcmRlckNvbG9yIjoiaHNsKDI0MCwgNjAlLCA4Ni4yNzQ1MDk4MDM5JSkiLCJzZWNvbmRhcnlCb3JkZXJDb2xvciI6ImhzbCg2MCwgNjAlLCA4My41Mjk0MTE3NjQ3JSkiLCJ0ZXJ0aWFyeUJvcmRlckNvbG9yIjoiaHNsKDgwLCA2MCUsIDg2LjI3NDUwOTgwMzklKSIsInByaW1hcnlUZXh0Q29sb3IiOiIjMTMxMzAwIiwic2Vjb25kYXJ5VGV4dENvbG9yIjoiIzAwMDAyMSIsInRlcnRpYXJ5VGV4dENvbG9yIjoicmdiKDkuNTAwMDAwMDAwMSwgOS41MDAwMDAwMDAxLCA5LjUwMDAwMDAwMDEpIiwibGluZUNvbG9yIjoiIzMzMzMzMyIsInRleHRDb2xvciI6IiMzMzMiLCJtYWluQmtnIjoiI0VDRUNGRiIsInNlY29uZEJrZyI6IiNmZmZmZGUiLCJib3JkZXIxIjoiIzkzNzBEQiIsImJvcmRlcjIiOiIjYWFhYTMzIiwiYXJyb3doZWFkQ29sb3IiOiIjMzMzMzMzIiwiZm9udEZhbWlseSI6IlwidHJlYnVjaGV0IG1zXCIsIHZlcmRhbmEsIGFyaWFsIiwiZm9udFNpemUiOiIxNnB4IiwibGFiZWxCYWNrZ3JvdW5kIjoiI2U4ZThlOCIsIm5vZGVCa2ciOiIjRUNFQ0ZGIiwibm9kZUJvcmRlciI6IiM5MzcwREIiLCJjbHVzdGVyQmtnIjoiI2ZmZmZkZSIsImNsdXN0ZXJCb3JkZXIiOiIjYWFhYTMzIiwiZGVmYXVsdExpbmtDb2xvciI6IiMzMzMzMzMiLCJ0aXRsZUNvbG9yIjoiIzMzMyIsImVkZ2VMYWJlbEJhY2tncm91bmQiOiIjZThlOGU4IiwiYWN0b3JCb3JkZXIiOiJoc2woMjU5LjYyNjE2ODIyNDMsIDU5Ljc3NjUzNjMxMjglLCA4Ny45MDE5NjA3ODQzJSkiLCJhY3RvckJrZyI6IiNFQ0VDRkYiLCJhY3RvclRleHRDb2xvciI6ImJsYWNrIiwiYWN0b3JMaW5lQ29sb3IiOiJncmV5Iiwic2lnbmFsQ29sb3IiOiIjMzMzIiwic2lnbmFsVGV4dENvbG9yIjoiIzMzMyIsImxhYmVsQm94QmtnQ29sb3IiOiIjRUNFQ0ZGIiwibGFiZWxCb3hCb3JkZXJDb2xvciI6ImhzbCgyNTkuNjI2MTY4MjI0MywgNTkuNzc2NTM2MzEyOCUsIDg3LjkwMTk2MDc4NDMlKSIsImxhYmVsVGV4dENvbG9yIjoiYmxhY2siLCJsb29wVGV4dENvbG9yIjoiYmxhY2siLCJub3RlQm9yZGVyQ29sb3IiOiIjYWFhYTMzIiwibm90ZUJrZ0NvbG9yIjoiI2ZmZjVhZCIsIm5vdGVUZXh0Q29sb3IiOiJibGFjayIsImFjdGl2YXRpb25Cb3JkZXJDb2xvciI6IiM2NjYiLCJhY3RpdmF0aW9uQmtnQ29sb3IiOiIjZjRmNGY0Iiwic2VxdWVuY2VOdW1iZXJDb2xvciI6IndoaXRlIiwic2VjdGlvbkJrZ0NvbG9yIjoicmdiYSgxMDIsIDEwMiwgMjU1LCAwLjQ5KSIsImFsdFNlY3Rpb25Ca2dDb2xvciI6IndoaXRlIiwic2VjdGlvbkJrZ0NvbG9yMiI6IiNmZmY0MDAiLCJ0YXNrQm9yZGVyQ29sb3IiOiIjNTM0ZmJjIiwidGFza0JrZ0NvbG9yIjoiIzhhOTBkZCIsInRhc2tUZXh0TGlnaHRDb2xvciI6IndoaXRlIiwidGFza1RleHRDb2xvciI6IndoaXRlIiwidGFza1RleHREYXJrQ29sb3IiOiJibGFjayIsInRhc2tUZXh0T3V0c2lkZUNvbG9yIjoiYmxhY2siLCJ0YXNrVGV4dENsaWNrYWJsZUNvbG9yIjoiIzAwMzE2MyIsImFjdGl2ZVRhc2tCb3JkZXJDb2xvciI6IiM1MzRmYmMiLCJhY3RpdmVUYXNrQmtnQ29sb3IiOiIjYmZjN2ZmIiwiZ3JpZENvbG9yIjoibGlnaHRncmV5IiwiZG9uZVRhc2tCa2dDb2xvciI6ImxpZ2h0Z3JleSIsImRvbmVUYXNrQm9yZGVyQ29sb3IiOiJncmV5IiwiY3JpdEJvcmRlckNvbG9yIjoiI2ZmODg4OCIsImNyaXRCa2dDb2xvciI6InJlZCIsInRvZGF5TGluZUNvbG9yIjoicmVkIiwibGFiZWxDb2xvciI6ImJsYWNrIiwiZXJyb3JCa2dDb2xvciI6IiM1NTIyMjIiLCJlcnJvclRleHRDb2xvciI6IiM1NTIyMjIiLCJjbGFzc1RleHQiOiIjMTMxMzAwIiwiZmlsbFR5cGUwIjoiI0VDRUNGRiIsImZpbGxUeXBlMSI6IiNmZmZmZGUiLCJmaWxsVHlwZTIiOiJoc2woMzA0LCAxMDAlLCA5Ni4yNzQ1MDk4MDM5JSkiLCJmaWxsVHlwZTMiOiJoc2woMTI0LCAxMDAlLCA5My41Mjk0MTE3NjQ3JSkiLCJmaWxsVHlwZTQiOiJoc2woMTc2LCAxMDAlLCA5Ni4yNzQ1MDk4MDM5JSkiLCJmaWxsVHlwZTUiOiJoc2woLTQsIDEwMCUsIDkzLjUyOTQxMTc2NDclKSIsImZpbGxUeXBlNiI6ImhzbCg4LCAxMDAlLCA5Ni4yNzQ1MDk4MDM5JSkiLCJmaWxsVHlwZTciOiJoc2woMTg4LCAxMDAlLCA5My41Mjk0MTE3NjQ3JSkifX0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoic3RhdGVEaWFncmFtLXYyXG4gICAgWypdIC0tPiBBXG4gICAgQSAtLT4gQlxuICAgIEEgLS0-IENcbiAgICBBIC0tPiBEXG4gICAgRCAtLT4gR1xuICAgIEMgLS0-IEdcbiAgICBDIC0tPiBGXG4gICAgQiAtLT4gRlxuICAgIEIgLS0-IEVcbiAgICBCIC0tPiBIXG4gICAgSCAtLT4gSVxuICAgIEUgLS0-IElcbiAgICBGIC0tPiBJXG4gICAgRyAtLT4gSVxuICAgIEkgLS0-IFsqXVxuICAgICAgICAgICAgIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQiLCJ0aGVtZVZhcmlhYmxlcyI6eyJiYWNrZ3JvdW5kIjoid2hpdGUiLCJwcmltYXJ5Q29sb3IiOiIjRUNFQ0ZGIiwic2Vjb25kYXJ5Q29sb3IiOiIjZmZmZmRlIiwidGVydGlhcnlDb2xvciI6ImhzbCg4MCwgMTAwJSwgOTYuMjc0NTA5ODAzOSUpIiwicHJpbWFyeUJvcmRlckNvbG9yIjoiaHN
|
|
|
|
|
|
|
|
|
|
假設我們有一系列任務,拓撲結構如上圖所示,頂點代表任務,邊緣代表依賴關係。(A 完成後才能完成 B、C、D,B 完成後才能完成 H、E、F...)
|
|
|
|
|
|
|
|
|
|
通過 `hyperf/dag` 可以使用如下方式構建 `DAG` 並執行。
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
$dag = new \Hyperf\Dag\Dag();
|
|
|
|
|
$a = \Hyperf\Dag\Vertex::make(function() {sleep(1); echo "A\n";});
|
|
|
|
|
$b = \Hyperf\Dag\Vertex::make(function() {sleep(1); echo "B\n";});
|
|
|
|
|
$c = \Hyperf\Dag\Vertex::make(function() {sleep(1); echo "C\n";});
|
|
|
|
|
$d = \Hyperf\Dag\Vertex::make(function() {sleep(1); echo "D\n";});
|
|
|
|
|
$e = \Hyperf\Dag\Vertex::make(function() {sleep(1); echo "E\n";});
|
|
|
|
|
$f = \Hyperf\Dag\Vertex::make(function() {sleep(1); echo "F\n";});
|
|
|
|
|
$g = \Hyperf\Dag\Vertex::make(function() {sleep(1); echo "G\n";});
|
|
|
|
|
$h = \Hyperf\Dag\Vertex::make(function() {sleep(1); echo "H\n";});
|
|
|
|
|
$i = \Hyperf\Dag\Vertex::make(function() {sleep(1); echo "I\n";});
|
|
|
|
|
$dag->addVertex($a)
|
|
|
|
|
->addVertex($b)
|
|
|
|
|
->addVertex($c)
|
|
|
|
|
->addVertex($d)
|
|
|
|
|
->addVertex($e)
|
|
|
|
|
->addVertex($f)
|
|
|
|
|
->addVertex($g)
|
|
|
|
|
->addVertex($h)
|
|
|
|
|
->addVertex($i)
|
|
|
|
|
->addEdge($a, $b)
|
|
|
|
|
->addEdge($a, $c)
|
|
|
|
|
->addEdge($a, $d)
|
|
|
|
|
->addEdge($b, $h)
|
|
|
|
|
->addEdge($b, $e)
|
|
|
|
|
->addEdge($b, $f)
|
|
|
|
|
->addEdge($c, $f)
|
|
|
|
|
->addEdge($c, $g)
|
|
|
|
|
->addEdge($d, $g)
|
|
|
|
|
->addEdge($h, $i)
|
|
|
|
|
->addEdge($e, $i)
|
|
|
|
|
->addEdge($f, $i)
|
|
|
|
|
->addEdge($g, $i);
|
|
|
|
|
|
|
|
|
|
// 需要在協程環境下執行
|
|
|
|
|
$dag->run();
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
輸出:
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
// 1s 後
|
|
|
|
|
A
|
|
|
|
|
// 2s 後
|
|
|
|
|
D
|
|
|
|
|
C
|
|
|
|
|
B
|
|
|
|
|
// 3s 後
|
|
|
|
|
G
|
|
|
|
|
F
|
|
|
|
|
E
|
|
|
|
|
H
|
|
|
|
|
// 4s 後
|
|
|
|
|
I
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> DAG 會按照儘可能早的原則調度任務。嘗試將 B 點的耗時調整為 2 秒,會發現 B 和 G 一起完成。
|
|
|
|
|
|
|
|
|
|
## 訪問前步結果
|
|
|
|
|
|
|
|
|
|
每一個任務可以接收一個數組參數,數組中包含所有前置依賴的結果。`DAG` 執行完畢後,也會返回一個同樣結構的數組,包含每一步的執行結果。
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
$dag = new \Hyperf\Dag\Dag();
|
|
|
|
|
$a = \Hyperf\Dag\Vertex::make(function() {return 1;});
|
|
|
|
|
$b = \Hyperf\Dag\Vertex::make(function($results) use ($a) {
|
|
|
|
|
return $results[$a->key] + 1;
|
|
|
|
|
});
|
|
|
|
|
$results = $dag->addVertex($a)->addVertex($b)->addEdge($a, $b)->run();
|
|
|
|
|
assert($results[$a->key] === 1);
|
|
|
|
|
assert($results[$b->key] === 2);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 定義一個任務
|
|
|
|
|
|
|
|
|
|
在上述文檔中,我們使用了閉包來定義一個任務。格式如下。
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
// Vertex::make 的第二個參數為可選參數,作為 vertex 的 key,也就是結果數組的鍵值。
|
|
|
|
|
\Hyperf\Dag\Vertex::make(function() { return 'hello'; }, "greeting");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
除了使用閉包函數定義任務外,還可以使用實現了 `\Hyperf\Dag\Runner` 接口的類來定義,並通過 `Vertex::of` 將其轉化為一個頂點。
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
class MyJob implements \Hyperf\Dag\Runner {
|
|
|
|
|
public function run($results = []) {
|
|
|
|
|
return 'hello';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
\Hyperf\Dag\Vertex::of(new MyJob(), "greeting");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`\Hyperf\Dag\Dag` 本身也實現了 `\Hyperf\Dag\Runner` 接口,所以可以嵌套使用。
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
// 命名空間已省略
|
|
|
|
|
$a = Vertex::make(function () { return 1;});
|
|
|
|
|
$b = Vertex::make(function () { return 2;});
|
|
|
|
|
$c = Vertex::make(function () { return 3;});
|
|
|
|
|
|
|
|
|
|
$nestedDag = new Dag();
|
|
|
|
|
$nestedDag->addVertex($a)->addVertex($b)->addEdge($a, $b);
|
|
|
|
|
$d = Vertex::of($nestedDag);
|
|
|
|
|
|
|
|
|
|
$superDag = new Dag();
|
|
|
|
|
$superDag->addVertex($c)->addVertex($d)->addEdge($c, $d);
|
|
|
|
|
$superDag->run();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 控制併發數
|
|
|
|
|
`\Hyperf\Dag\Dag` 類提供了 `setConcurrency(int n)` 方法控制最大併發數。默認為 10。
|