| @@ -1,5 +1,7 @@ | |||
| # 介绍 | |||
| **Author** : **`Leon 李扬`** | |||
| `Dora-rs` 是一个用于构建**实时模块化机器人应用程序**的框架。它使用声明式数据流 ,其中任务被拆分成称为节点的独立进程。这些节点使用高性能 通过事件流传输的 `Arrow` 数据格式,由本地管理 守护进程和中央协调器 。用户使用 `CLI` 与 `Dora` 交互 并使用语言 `API` 绑定开发自定义组件。 | |||
| ## 为什么选择`Dora-rs` | |||
| @@ -2,6 +2,8 @@ import { Tab, Tabs } from 'rspress/theme'; | |||
| # 快速上手 | |||
| **Author** : **`Leon 李扬`** | |||
| 安装 `dora-cli` | |||
| <Tabs> | |||
| @@ -10,7 +12,7 @@ import { Tab, Tabs } from 'rspress/theme'; | |||
| $ pip install dora-rs-cli | |||
| ``` | |||
| </Tab> | |||
| <Tab label="linux"> | |||
| <Tab label="Linux"> | |||
| ```bash | |||
| $ curl --proto '=https' --tlsv1.2 -LsSf https://github.com/dora-rs/dora/releases/latest/download/dora-cli-installer.sh | sh | |||
| ``` | |||
| @@ -20,7 +22,7 @@ $ curl --proto '=https' --tlsv1.2 -LsSf https://github.com/dora-rs/dora/releases | |||
| $ curl --proto '=https' --tlsv1.2 -LsSf https://github.com/dora-rs/dora/releases/latest/download/dora-cli-installer.sh | sh | |||
| ``` | |||
| </Tab> | |||
| <Tab label="windows"> | |||
| <Tab label="Windows"> | |||
| ```bash | |||
| PS C:\> powershell -ExecutionPolicy ByPass -c "irm https://github.com/dora-rs/dorareleases/latest/download/dora-cli-installer.ps1 | iex" | |||
| ``` | |||
| @@ -40,7 +42,7 @@ $ docker pull ghcr.io/dora-rs/dora-slim | |||
| $ docker run ghcr.io/dora-rs/dora-slim dora --help | |||
| ``` | |||
| </Tab> | |||
| <Tab label="源码安装"> | |||
| <Tab label="Source"> | |||
| 1. 安装`Rust`,详细参考:[https://echoli.cn/getting-started/install.html](https://echoli.cn/getting-started/install.html) | |||
| 2. 克隆源码和编译 | |||
| ```bash | |||
| @@ -50,7 +52,7 @@ $ cargo build --release -p dora-cli | |||
| # 在 `target\release` 目录下找到 `dora` 可执行文件,放在系统环境目录中 | |||
| ``` | |||
| </Tab> | |||
| <Tab label="直接下载"> | |||
| <Tab label="Download"> | |||
| [下载地址](https://github.com/dora-rs/dora/releases/latest) 直接下载当前系统的版本即可 | |||
| </Tab> | |||
| </Tabs> | |||
| @@ -1 +1,178 @@ | |||
| # API 绑定 | |||
| # 第六章:API 绑定 | |||
| **Author** : **`Leon 李扬`** | |||
| 欢迎回到 `dora` 教程!到目前为止,我们已经学习了数据流 ( 第 1 章 )作为应用程序蓝图, 节点 ( 第 2 章 )和操作符 ( 第 3 章 )作为构建块, 事件流 ( 第 4 章 )作为其通信通道,以及数据消息/箭头数据 ( 第 5 章 )作为高效数据传输的格式。 | |||
| 现在,我们来谈谈如何实际编写这些节点和操作符的代码 ,让它们完成各自的工作。你用 `Python` 或 `Rust` 编写的自定义逻辑如何连接到 `dora 运行时`来接收输入并发送输出?这时, **`API 绑定`** 就派上用场了。 | |||
| ## 代码的翻译器和工具包 | |||
| 假设你用 `Python` 设计了一个很棒的物体检测算法。你想把它包装在一个 `dora` 节点中,这样它就可以从摄像头节点接收图像,并将检测结果发送到绘图节点,所有这些都按照你的 `Dataflow YAML` 文件中的定义进行。 | |||
| 您的 `Python` 代码需要特定的工具来: | |||
| 1. 告诉 `dora` 运行时它已准备好启动。 | |||
| 2. 监听来自事件流的传入 `INPUT` 事件( 第 4 章 )。 | |||
| 3. 从事件中提取实际图像数据消息 ( 第 5 章 )。 | |||
| 4. 将计算出的边界框数据作为新的数据消息 ( 第 5 章 )发送回运行时,并指定它应该转到哪个输出。 | |||
| 5. 如果 `dora` 运行时发送了 `STOP` 信号,则知道何时停止( 第 4 章 )。 | |||
| 这些工具以库或模块的形式提供给不同的编程语言,它们被称为 `API 绑定` 。它们是连接自定义应用程序逻辑和 `dora 运行时`环境的软件层。 | |||
| 将 `API 绑定`视为: | |||
| - **翻译器:** 它们将代码中的高级指令(如“发送此数据”)转换为 `dora` 运行时可以理解的低级消息和协议。 | |||
| - **工具包:** 它们提供现成的功能和结构( `send_output` 、 `next` 、 `on_event` 等),简化与 `dora` 核心机制(如事件流和数据消息处理)的交互。 | |||
| `dora` 为多种语言提供了 `API 绑定`,包括 `Rust`、`Python`、`C` 和 `C++`。因此,您可以在同一个数据流中混合搭配使用不同语言编写的节点,只要它们使用各自语言的 `dora API 绑定`进行通信即可。 | |||
| ## `API 绑定`提供的核心功能 | |||
| 无论使用哪种语言, `dora API 绑定`通常都会提供用于以下基本任务的函数或方法: | |||
| 1. **初始化:** 用于将 `Node/Operator` 代码连接到正在运行的 `dora` 数据流实例的函数。此步骤对于运行时识别组件并设置其通信通道至关重要。 | |||
| 2. **接收事件:** 监听并从组件的事件流中获取事件的机制。正如我们所见,这通常涉及一个循环或一个回调函数来处理传入的事件( `INPUT` 、 `STOP` 、 `InputClosed` 等)。 | |||
| 3. **发送输出数据:** 用于在组件的输出流上发布数据的函数。您可以指定要发送数据的输出 ID,并提供数据负载(通常使用 `Apache Arrow` 进行结构化)。 | |||
| 4. **访问数据/元数据:** 辅助函数可轻松访问 INPUT 事件中包含的数据( `event["value"]` )和可选 `metadata` ( `event["metadata"]` )。 | |||
| 5. **处理控制信号:** 针对 `STOP` 等控制事件的内置处理或清晰的通知机制。 | |||
| ## 使用 `API 绑定`:代码示例 | |||
| 让我们看一下使用 `Python API` 绑定( `dora-rs` 库)的简化示例,这是 `AI` 和数据处理任务的常见选择。 | |||
| ### 示例 1:使用 `API` 的简单 `Python` 节点 | |||
| 这与我们在第二章:`Node` 中看到的结构类似,但现在重点关注 `API` 调用本身: | |||
| ```py | |||
| from dora import Node # Import the Node class from the API binding | |||
| # 1. Initialize the node | |||
| node = Node() | |||
| print("Python Node initialized. Waiting for events...") | |||
| # 2. Enter the event loop to receive events | |||
| for event in node: | |||
| event_type = event["type"] | |||
| event_id = event["id"] # Often the input ID | |||
| print(f"Received event: Type={event_type}, ID={event_id}") | |||
| if event_type == "INPUT": | |||
| # 3. Access input data and process | |||
| if event_id == "my_input": | |||
| input_data = event["value"] | |||
| print(f" -> Processing data from input '{event_id}'...") | |||
| # --- Your custom processing logic here --- | |||
| processed_data = process_data(input_data) | |||
| # --------------------------------------- | |||
| # 4. Send output data | |||
| print(" -> Sending processed data on output 'my_output'") | |||
| node.send_output("my_output", processed_data) | |||
| elif event_type == "STOP": | |||
| # 5. Handle stop signal | |||
| print(" -> Received STOP command. Exiting loop.") | |||
| break | |||
| print("Python Node stopping gracefully.") | |||
| # Dummy processing function for illustration | |||
| def process_data(data): | |||
| # In a real node, this would transform the data | |||
| print(" (Doing dummy processing...)") | |||
| # Often data is an Arrow Array, need to convert/process it | |||
| # For simplicity, let's just return a dummy byte array | |||
| return b"processed_output_data" | |||
| ``` | |||
| - `from dora import Node`:从 `API 绑定`导入必要的类。 | |||
| - `node = Node()`:初始化与 `dora` 运行时的连接。运行时使用启动期间设置的环境变量(基于你的 `Dataflow YAML` )来识别此节点。 | |||
| - `for event in node`: 这是接收事件的核心。`API` 会处理阻塞,直到下一个事件在事件流中可用,并以结构化格式(类似 `Python` 字典)提供。 | |||
| - `event["type"]` , `event["id"]` , `event["value"]`:访问已接收事件的详细信息。`API` 提供以下标准键。 | |||
| - `node.send_output("my_output", processed_data)`:调用 `API` 函数发送数据。您需要指定 `Dataflow YAML` 中定义的输出 ID `和数据负载。API` 负责格式化数据(如果需要,例如,格式化为 `Arrow` 格式),可能还会使用共享内存 ,并通知运行时。 | |||
| ### 示例 2:使用 `API` 的简单 `Python` 运算符 | |||
| 操作员使用稍微不同的 `API` 结构,专门设计用于 `dora-runtime` 进程( 第 3 章:操作员 )。 | |||
| ```py | |||
| # operators/my_operator.py | |||
| from dora import DoraStatus # Import necessary status enum | |||
| # The Operator logic is typically implemented in a class with specific methods | |||
| class Operator: | |||
| def on_event(self, dora_event, send_output): | |||
| """ | |||
| This method is called by the dora-runtime whenever an event occurs | |||
| for this operator (like receiving an input). | |||
| """ | |||
| event_type = dora_event["type"] | |||
| event_id = dora_event["id"] # Often the input ID | |||
| print(f"Operator received event: Type={event_type}, ID={event_id}") | |||
| if event_type == "INPUT": | |||
| if event_id == "operator_input": | |||
| input_data = dora_event["value"] | |||
| print(f" -> Processing data from operator input '{event_id}'") | |||
| # --- Your custom processing logic here --- | |||
| processed_data = process_operator_data(input_data) | |||
| # --------------------------------------- | |||
| # Send output data using the provided send_output function | |||
| print(" -> Sending processed data on operator output 'operator_output'") | |||
| send_output("operator_output", processed_data, dora_event["metadata"]) # Operators also pass metadata | |||
| elif event_type == "STOP": | |||
| print(" -> Received STOP command. Operator stopping.") | |||
| return DoraStatus.STOP # Return STOP status to signal shutdown | |||
| # By default, continue running | |||
| return DoraStatus.CONTINUE | |||
| # Dummy processing function for illustration | |||
| def process_operator_data(data): | |||
| print(" (Operator doing dummy processing...)") | |||
| return b"processed_operator_data" | |||
| ``` | |||
| - `from dora import DoraStatus`:导入必要的状态值来向运行时发出信号(例如, `DoraStatus.CONTINUE` , `DoraStatus.STOP` )。 | |||
| - `class Operator`: 在 `Python` 中定义运算符的标准方法。 | |||
| - `on_event(self, dora_event, send_output)`: `dora-runtime` 期望此特定的方法签名。运行时调用此方法,并传递事件详情( `dora_event` )以及特定于此运算符实例的 `send_output` 函数。 | |||
| - `dora_event["type"] 、 dora_event["id"] 、 dora_event["value"]`:访问事件详细信息,类似于 `Node API`,但通常直接作为参数提供或嵌套在事件对象中。 | |||
| - `send_output("operator_output", processed_data, dora_event["metadata"])`:调用提供的函数来从此特定运算符发送输出数据。然后,运行时会根据数据流 YAML 将此输出路由到同一节点内的其他运算符,或者路由到节点外的其他运算符。 | |||
| - `return DoraStatus.CONTINUE / DoraStatus.STOP`:操作符在处理事件后明确向运行时返回状态,指示它们是否应该继续或停止。 | |||
| 您可以在 `Rust` 绑定( `dora-node-api` 、 `dora-operator-api` )和 `C/C++` 绑定(通过 `CXX` 和原始 `C FFI` 公开,参见 `apis/rust/node/src/lib.rs` 、 `apis/c++/node/src/lib.rs` 、 `apis/c/node/src/lib.rs` )中看到类似的 `API` 结构和概念。它们提供了针对特定语言范式定制的相同基本功能(初始化、接收、发送、处理信号)。 | |||
| ## 底层:与运行时交互的 `API` 绑定 | |||
| 那么,当您调用 `node.send_output(...)` 或 `send_output(...)` 时会发生什么? | |||
| `API 绑定库`本身并不实现整个 `dora` 通信逻辑。相反,它与启动节点或操作员的 `dora` 运行时进程( `Dora Daemon/Coordinator` )进行通信。 | |||
| 当你的 `Node/Operator` 进程启动时, `dora 运行时`会设置高效的通信渠道: | |||
| 1. **事件通道:** 这是 `dora 运行时`向你的 `Node/Operator` 发送事件的地方(即事件流 )。`API` 绑定的事件循环( f`or event in node` )正在监听此通道。 | |||
| 2. **控制通道:** 这是你的节点/操作员向 `dora 运行时`发送命令的地方(例如“发送此输出”、“我已完成此共享内存块的操作”)。`API` 绑定的 `send_output` 函数使用此通道。 | |||
| 3. **共享内存:** 如第 5 章所述,对于大数据,数据本身通过共享内存传输,并通过控制通道上的消息进行协调。 | |||
| 这是一个简化的序列图: | |||
|  | |||
| **`API 绑定`** 隐藏了这些通道和协议的复杂性,让您可以专注于应用程序逻辑。它将您语言的数据类型转换为适合 `dora` 的格式(例如 `Arrow` ),并管理高效通信所需的低级交互。 | |||
| `Rust` 和 `C++ API 绑定`通常会暴露底层细节或允许更细粒度的控制以提高性能,而 `Python` 绑定则优先考虑易用性。但它们的根本目的都是为你的自定义代码提供一种结构化的方式,以便与 `dora` 运行时进行通信。 | |||
| # 总结 | |||
| `API 绑定`是必不可少的软件库,它使您能够使用熟悉的编程语言(例如 P`ython`、`Rust`、`C` 和 `C++`)为自定义 `dora` 节点和操作符编写核心逻辑。它们提供了标准化的函数工具包( `init` 、 `send_output` 、事件循环/回调),这些函数抽象了进程间通信、共享内存和事件处理的复杂性,使您可以专注于特定的应用程序任务,同时无缝集成到 `dora` 数据流中。 | |||
| 现在您已经了解了如何使用 `API 绑定`为各个组件编写代码,让我们缩小范围并查看用于管理和运行整个数据流的主要工具: `Dora CLI` 。 | |||
| @@ -1 +1,125 @@ | |||
| # 数据信息 / Arrow Data | |||
| # 第五章:数据信息 / Arrow Data | |||
| **Author** : **`Leon 李扬`** | |||
| 欢迎回到 `dora` 教程!在第四章:事件流中,我们学习了节点和操作符通过事件流接收信息,包括携带数据的 INPUT 事件。但是,这些数据在事件内部是什么样的 ? `dora` 又是如何高效地传输这些数据的,尤其是对于图像或传感器读数等大数据项? | |||
| 本章深入探讨 `dora` 数据处理的核心: `数据消息`及其对 `Apache Arrow 格式`的使用。 | |||
| ## 对速度的需求:处理大数据 | |||
| 再次想象一下我们的对象检测数据流:相机节点生成图像,对象检测节点处理它们,绘图节点将它们可视化。 | |||
| - 图像通常很大(例如,具有 3 个颜色通道的 640x480 像素超过 900 KB)。 | |||
| - 传感器数据(例如来自 `LiDAR` 传感器的点云)可以更大。 | |||
| 这些海量数据需要快速地从一个 `Node` 进程传输到另一个 `Node` 进程。如果 `dora` 每次将图像从摄像头节点发送到物体检测节点,然后再发送到绘图节点时,都必须完整复制一份,这将浪费大量的时间和内存。这在毫秒必争的实时应用中尤其糟糕。 | |||
| 我们需要一种以最小的开销在进程之间共享或移动这些数据的方法。 | |||
| ## 数据消息和 `Apache Arrow` | |||
| 在 `dora` 中,当数据到达输入时,它是 `INPUT` 事件的一部分, `dora` 实际的数据有效负载被包装在数据消息中。`dora` 使用称为 `Apache Arrow` 的强大标准来表示大多数数据消息中的数据。 | |||
| `Apache Arrow` 是一种高度优化、标准化的内存数据组织方式。`Arrow` 并非只是提供每个人都必须以不同方式解释的原始字节流,而是为数字列表、数组、表等常见数据类型提供了定义的结构。 | |||
| **为什么选择 Arrow?** | |||
| 1. **标准化:** 为跨不同编程语言和系统的数据提供通用语言。 | |||
| 2. **性能:** 它专为分析处理和数据传输而设计,特别是支持“零拷贝读取”。 | |||
| 3. **零拷贝:** 这是 `dora` 的关键部分。如果数据位于共享内存中,并且格式为 `Arrow`,则多个进程可以从同一内存位置读取数据,而无需将数据复制到各自的地址空间中。 | |||
| 因此,当节点或操作符接收到 `INPUT` 事件时,该事件的 `value` 部分通常是一个 `Arrow 数组` (有时是一个包含多个 `Arrow 数组`的结构体)。`Arrow 数组`是 `Arrow` 术语,指一系列相同类型的值(例如整数列表、浮点数列表或表示图像的字节列表)。 | |||
| ## 在代码中使用 `Arrow Data` | |||
| 当你在 `Node` 或 `Operator` 代码中收到 `INPUT` 事件时, `event["value"]` (`Python` 中)会返回数据。`dora` 会封装这些数据,通常允许你以 `Arrow 对象`的形式访问它,或者轻松地将其转换为你编程语言中的常见数据结构(例如 `Python` 中的 `NumPy` 数组)。 | |||
| 下面是一个概念性的 `Python` 代码片段,展示了如何接收图像数据: | |||
| ```py | |||
| from dora import Node | |||
| import pyarrow as pa | |||
| import numpy as np # Often used with image data | |||
| node = Node() | |||
| for event in node: | |||
| if event["type"] == "INPUT": | |||
| if event["id"] == "image": | |||
| image_arrow_array = event["value"] # This is likely an Arrow Array | |||
| # --- Accessing the data --- | |||
| # If it's a simple byte array (like raw image bytes): | |||
| image_bytes = image_arrow_array.to_pybytes() # Convert to Python bytes | |||
| # If it's structured data (like a list of bounding boxes): | |||
| # boxes_list = image_arrow_array.to_pylist() # Convert to Python list | |||
| # print(f"Received {len(boxes_list)} bounding boxes.") | |||
| # If you need a NumPy array (common for images): | |||
| # Assuming the Arrow Array is a list of bytes or numerical array | |||
| # image_np = image_arrow_array.to_numpy() | |||
| # --------------------------- | |||
| print(f"Received image data on input 'image' with {len(image_bytes)} bytes.") | |||
| # Handle other inputs... | |||
| pass | |||
| elif event["type"] == "STOP": | |||
| break | |||
| ``` | |||
| 这表明数据以 `dora API` 提供的格式到达,该 `API` 基于 `Arrow`。然后,您可以使用 `API` 提供的方法(通常利用 `Python` 中的 `pyarrow` 库)来访问数据,或将数据转换为适合您处理逻辑的格式。 | |||
| 类似地,发送数据时,您需要将其格式化为 `Arrow 数组`或与 `Arrow` 兼容的结构,然后通过 `node.send_output(...)` 或 `send_output(...)` 将其发送给 `Operator`。`dora API` 可以帮助 `dora` 进行这种格式化。 | |||
| ## 底层:共享内存和零拷贝 | |||
| `dora` 如何实现性能提升,尤其是“零拷贝”? | |||
| 对于较大的数据有效载荷(超过一定的大小阈值,由核心代码中的 `ZERO_COPY_THRESHOLD` 定义,当前为 4KB), `dora` 使用共享内存 。 | |||
| 以下是 `节点A` 向 `节点B` 发送大数据消息时的简化流程: | |||
| 1. `节点 A` 以 `Apache Arrow` 格式准备数据。 | |||
| 2. `节点 A` 告诉 `dora 运行时`(通过控制通道)它想要在特定的输出上发送该数据。 | |||
| 3. `dora 运行时`分配了一块共享内存。这块内存`节点 A` 和`节点 B` 进程都可以访问。 | |||
| 4.`节点 A` 将 Arrow 格式的数据写入此共享内存块。 | |||
| 5. 然后, `dora 运行时`向`节点 B` 发送一个 INPUT 事件。此事件不包含数据本身,而是包含数据所在的共享内存块的引用或标识符 ,以及 `Arrow` 结构信息(类型、大小、布局), `dora` 其称为 `ArrowTypeInfo` ,是消息 `Metadata` 的一部分。 | |||
| 6. `节点 B` 收到 `INPUT` 事件,从 `Metadata` 中读取共享内存标识符和 `ArrowTypeInfo` 。 | |||
| 7. `节点 B` 使用其 `dora API` 访问数据。该 `API` 知道如何将共享内存块映射(使其可访问)到`节点 B` 的进程中,并使用 Arrow 格式信息直接从那里读取数据。 | |||
|  | |||
| 这种从共享内存直接读取(“零拷贝”)的方式使得 `dora` 能够快速传输大量数据消息,因为数据本身不需要序列化、通过管道发送,也不需要由接收进程反序列化。 | |||
| 对于小数据有效载荷(低于 `ZERO_COPY_THRESHOLD` ),数据可能会直接在消息本身内发送(例如,通过通信管道复制),因为设置共享内存的开销可能大于复制小数据的成本。 | |||
| ## 管理共享内存:删除令牌 | |||
| 共享内存的一个关键挑战是知道何时可以安全地释放内存。如果发送节点过早释放内存,接收节点在尝试访问时可能会崩溃。 `dora` 使用一种涉及丢弃令牌的机制来管理这个问题。 | |||
| 1. 当`节点 A` 使用共享内存发送数据时,它会为该特定的共享内存块生成一个唯一的 `DropToken` 。 | |||
| 2. dora 运行时指出,`节点 B`(和任何其他订阅者)在处理完数据后需要得到通知。 | |||
| 3. `节点 A` 跟踪它生成的 `DropToken` 。 | |||
| 4. 当`节点 B` 接收到数据并完成处理时(例如,当接收到的数据对象超出其代码范围时),`节点 B` 中的 `dora API` 会向运行时发回信号,表明它已完成与该共享内存 `ID/Drop Token` 相对应的数据处理。 | |||
| 5. dora 运行时会跟踪有多少节点接收了数据。当所有接收该特定数据块的节点都发出完成信号后,运行时会通过一个称为 `Drop Stream` 的专用内部通道,向原始发送节点(`节点 A`)发送 `drop token` 完成”信号。 | |||
| 6. `节点 A` 监听这些“drop token 完成”信号(由 dora Node API 内部处理)。当`节点 A` 收到它之前发送的 `DropToken` 信号时,它就知道特定的共享内存块不再被任何下游节点需要,可以安全地释放或返回到缓存中以供重用。 | |||
| 此 `Drop Token` 机制确保仅在所有预期接收者完成访问后才回收共享内存。 | |||
| 您可以在 `dora-node-api` `Rust` 代码中看到对这些概念的引用: | |||
| - `libraries/message/src/common.rs` 中的 `DataMessage` 枚举具有 `SharedMemory` 变量,其中包括 `shared_memory_id` 、 `len` 和 `drop_token` 。 | |||
| - `DropToken` 结构也在 `libraries/message/src/common.rs` 中定义。 | |||
| - `ZERO_COPY_THRESHOLD` 在 `apis/rust/node/src/node/mod.rs` 中定义。 | |||
| - `apis/rust/node/src/node/mod.rs` 中的 `DoraNode` 结构包括 `sent_out_shared_memory` (用于跟踪其发送的内存的令牌)和 `drop_stream` (用于接收完成信号)等字段。 | |||
| - `apis/rust/node/src/node/mod.rs` 中的 `allocate_data_sample` 和 `handle_finished_drop_tokens` 等方法展示了分配共享内存和处理传入的删除令牌的内部逻辑。 | |||
| - `libraries/message/src/metadata.rs` 中的 `Metadata` 结构包含描述 `Arrow` 数据有效负载的结构的 `type_info` ( `ArrowTypeInfo` )。 | |||
| ## 总结 | |||
| 在本章中,我们了解到 `dora` 中的数据是通过 `INPUT` 数据消息在节点和算子之间传递的。为了实现高性能,尤其是在处理大数据量时, `dora` 主要使用 `Apache Arrow` 格式,并利用共享内存在进程间实现零拷贝数据传输。这意味着接收节点可以直接从发送节点写入的同一内存位置读取数据。`Drop Token` 机制确保此共享内存得到安全管理,并且仅在所有接收节点都处理完数据后才会释放。理解这些概念是理解 `dora` 在实时数据管道中性能特征的关键。 | |||
| 现在您已经了解了数据如何流动和表示,让我们看看您将用来在 `Node` 或 `Operator` 代码中与 `dora 运行时`进行交互的工具: `API 绑定` 。 | |||
| @@ -1,4 +1,6 @@ | |||
| # 数据流 | |||
| # 第一章:数据流 | |||
| **Author** : **`Leon 李扬`** | |||
| 欢迎来到 `dora` 教程! `dora` 是一个强大的框架,旨在帮助您构建复杂的应用程序,尤其是那些需要处理实时数据的应用程序,例如机器人技术或 `AI` 引用。但是,如何设计和管理这些需要不同组件协同工作并共享信息的应用程序呢?这就是 `Dataflow` 的概念! | |||
| @@ -1 +1,172 @@ | |||
| # 命令行 | |||
| # 第七章:命令行 | |||
| **Author** : **`Leon 李扬`** | |||
| 欢迎回到 `dora` 教程!在上一章, [第六章:API 绑定](./api-binding.mdx)中,我们了解了如何使用特定语言的库为自定义节点和操作符编写实际代码,以便与 `dora` 运行时进行通信。 | |||
| 但是,如何才能将完成的 `Dataflow 蓝图`(`YAML` 文件)和 `Node/Operator` 代码真正地运行在 `dora` 上呢?如何让 `dora` 启动、停止所有操作,或者查看哪些程序正在运行? | |||
| 这就是 `Dora CLI` 的作用所在! | |||
| ## 您的 `Dora` 指挥中心 | |||
| `Dora CLI` (命令行界面)是你与 `dora` 系统交互的主要工具。它是一个在终端中运行的命令行程序(类似于 `ls` 、 `cd` 、 `git` 或 `pip` )。 | |||
| 可以将 `Dora CLI` 视为您的命令中心。它允许您向 `dora` 发出指令,以管理您的数据流以及使 `dora` 工作的后台进程(例如 `Dora Daemon` 或 `Dora Coordinator` )。 | |||
| 您可以使用 `CLI` 执行以下基本任务: | |||
| - 根据 YAML 蓝图启动数据流。 | |||
| - 停止正在运行的数据流。 | |||
| - 为您的节点准备必要的代码和依赖项。 | |||
| - 列出当前正在运行的数据流。 | |||
| - 检查 `dora` 系统的状态。 | |||
| 它是您定义的数据流和正在运行的应用程序之间的桥梁。 | |||
| ## 运行数据流: `dora run` 命令 | |||
| 使用 `Dora CLI` 最常见的操作是运行在 `YAML` 文件中定义的数据流。我们在第一章“数据流” 中对此进行了简要介绍。 | |||
| 让我们重新审视一下 `yolo.yml` 中的简单相机、物体检测和绘图数据流: | |||
| ```yaml | |||
| nodes: | |||
| - id: camera | |||
| build: pip install opencv-video-capture | |||
| path: opencv-video-capture | |||
| inputs: | |||
| tick: dora/timer/millis/20 | |||
| outputs: | |||
| - image | |||
| env: | |||
| CAPTURE_PATH: 0 | |||
| IMAGE_WIDTH: 640 | |||
| IMAGE_HEIGHT: 480 | |||
| - id: object-detection | |||
| build: pip install dora-yolo | |||
| path: dora-yolo | |||
| inputs: | |||
| image: camera/image | |||
| outputs: | |||
| - bbox | |||
| - id: plot | |||
| build: pip install dora-rerun | |||
| path: dora-rerun | |||
| inputs: | |||
| image: camera/image | |||
| boxes2d: object-detection/bbox | |||
| ``` | |||
| 要使用 `CLI` 运行此数据流,请使用 `dora run` 命令,后跟 `YAML` 文件的路径: | |||
| ```bash | |||
| dora run yolo.yml | |||
| ``` | |||
| 运行此命令时,`Dora CLI` 会执行以下几项操作: | |||
| 1. 它读取并解析 `yolo.yml` 文件。 | |||
| 2. 它与 `dora` 运行时进行通信(很快会详细介绍此连接!)以告知它有关 `YAML` 中描述的数据流。 | |||
| 3. 然后,运行时根据 `YAML` 中的 `path` 、 `build` 等字段将所有定义的节点 ( `camera` 、 `object-detection` 、 `plot` )作为单独的进程启动。 | |||
| 4. 运行时根据 `inputs` 和 `outputs` 的定义在节点之间建立通信通道( 事件流 )。 | |||
| 您的数据流应用程序现已运行!终端中的输出将显示来自不同 Node 进程的日志。 | |||
| 要停止数据流,通常在执行 `dora run` 终端中按下 `Ctrl + C` 会向运行时发送信号,运行时会向所有节点发送 `STOP` 事件( 参见[第四章:事件流](./event-stream.mdx) ),使它们能够正常关闭。 | |||
| ## 准备依赖项: `dora build` 命令 | |||
| 回顾 `yolo.yml` 示例。每个节点定义都有一个 `build` 字段,例如 `pip install opencv-video-capture` 。这告诉 `dora` 如何获取该特定节点所需的代码或依赖项。 | |||
| 例如: | |||
| - `camera` 节点需要 `opencv-video-capture` `Python` 包。 | |||
| - `object-detection` 节点需要 `dora-yolo` `Python` 包。 | |||
| - `plot` 节点需要 `dora-rerun` `Python` 包。 | |||
| 虽然 `dora run` 可以隐式执行尚未完成的构建步骤,但最佳做法通常是事先显式运行构建过程,尤其是对于更复杂的设置或远程数据流。这可以通过 `dora build` 命令完成: | |||
| ```bash | |||
| dora build yolo.yml | |||
| ``` | |||
| 此命令指示 `dora` 浏览 `yolo.yml` 文件,查看每个节点的所有 `build` 指令,并执行它们。对于 `Python` 节点,这通常意味着运行 `pip install` (如果添加了 `--uv` 则运行 `uv install` )命令,以确保节点运行所需的正确环境中已包含必要的库。对于 `Rust` 或 `C++` 节点,这可能涉及编译代码。 | |||
| 将 `build` 与 `run` 分开允许您准备一次环境和节点代码,然后多次运行数据流。 | |||
| ## 管理正在运行的数据流: `dora stop` 和 `dora list` | |||
| 如果你在后台运行数据流或者将其与终端分离,会怎么样?如何停止它?如何查看当前正在运行的内容? | |||
| `CLI` 提供用于管理正在运行的数据流的命令: | |||
| - `dora stop` :此命令用于停止先前由 `dora run` 启动的数据流(尤其是在分离模式下运行时),或由我们稍后会介绍的低级命令启动的数据流。如果您未指定要停止的数据流,则可能会停止最后一个数据流或需要 ID。 | |||
| ```bash | |||
| # Example (may need specific ID or context depending on how it was run) | |||
| dora stop my_running_dataflow_id | |||
| ``` | |||
| - `dora list` :此命令显示系统上当前由 `dora` 运行时管理的数据流和组件。 | |||
| ```bash | |||
| dora list | |||
| ``` | |||
| 输出可能看起来像这样(简化): | |||
| ```bash | |||
| DATAFLOW ID STATUS YAML FILE | |||
| ----------------------------------- | |||
| abcd123 Running yolo.yml | |||
| efgh456 Running another.yml | |||
| ``` | |||
| 这可以帮助您跟踪 `dora` 正在做的事情。 | |||
| ## 超越运行: `dora start `和`系统管理` | |||
| `CLI` 还包括与核心 `dora` 后台服务更直接交互的命令: | |||
| - `dora up` :一个便捷的命令,用于确保主要的 `dora` 后台进程(例如 `Dora Coordinator` 和 `Dora Daemon` )正在运行。您可以在启动计算机时或运行数据流之前运行此命令。 | |||
| ```bash | |||
| dora up | |||
| ``` | |||
| - `dora destroy` :停止并清理所有正在运行的 `dora` 组件和数据流。使用此命令可以彻底关闭你机器上的 `dora` 系统。 | |||
| ```bash | |||
| dora destroy | |||
| ``` | |||
| - 还有一些低级命令,例如 `dora daemon` 和 `dora coordinator` ,但通常不需要手动运行它们,因为 `dora up` 会负责启动它们,而 `dora run` 甚至可以在它们尚未启动的情况下启动它们。这些命令在下一章讨论 `Dora Daemon` 和 `Dora Coordinator` 的具体角色时更为相关。 | |||
| ## CLI 的工作原理 | |||
| 需要注意的是, `dora CLI` 工具( `dora` 可执行文件) 并非 `dora 运行时`本身。 `dora 运行时`由一个或多个后台进程组成,通常包括 `Dora Daemon` 和 `Dora Coordinator` (我们将在下一章中详细解释这些进程)。 | |||
| 当你输入像 `dora run yolo.yml` 这样的命令时: | |||
| 1. `dora CLI` 程序启动。 | |||
| 2. 它读取 `yolo.yml` 文件。 | |||
| 3. 然后它连接到正在运行的 `dora` 运行时(具体来说,它与 `Dora Coordinator` 对话)。 | |||
| 4. CLI 向协调器发送一条消息或请求,说“请运行此 `YAML` 描述的数据流”。 | |||
| 5. 协调器接收请求,然后协调数据流的实际运行(启动节点进程、建立通信等)。 | |||
| `CLI` 本质上是一个向 `dora 运行时`服务器进程发送命令的客户端应用程序。 | |||
| 以下是 `dora run` 交互的简化视图: | |||
|  | |||
| 这表明 `CLI` 本身并不运行你的 `Nodes` ;它将这项任务委托给 `Coordinator` 和其他运行时组件。这种架构允许运行时在后台管理数据流,即使 `CLI` 处于关闭状态。 | |||
| ## 总结 | |||
| `Dora CLI` 是您与 `dora` 系统交互的主要工具。您可以使用简单的命令,例如 `dora run` 根据 `Dataflow YAML` 蓝图启动数据流,使用 `dora build` 准备依赖项,以及使用 `dora stop` 和 `dora list` 管理正在运行的应用程序。`CLI` 充当命令接口,与 `dora 运行时`进程(例如 `Dora Coordinator` )通信,告诉它们执行操作,而不是直接运行数据流。 | |||
| 现在您已经了解了如何使用 `CLI` 命令 `dora` ,让我们深入了解接收这些命令并管理运行节点的关键组件之一: `Dora Daemon` 。 | |||
| @@ -1 +1,138 @@ | |||
| # 协调员 | |||
| # 第九章:协调器 | |||
| **Author** : **`Leon 李扬`** | |||
| 欢迎来到 `dora` 教程的最后一章!在本教程中,我们学习了`数据流蓝图`、`模块化节点`和`操作符` ,以及它们如何通过事件流进行通信并高效地传输数`据消息/Arrow Data` 。我们还了解了如何使用 `API` 绑定编写逻辑,以及如何使用 `Dora CLI` 作为命令行界面。最近,在[第八章:Dora 守护进程](./dora-daemon.mdx)中,我们学习了 `Dora` 守护进程,它是在单台机器上本地管理 `dora` 组件的后台服务。 | |||
| 但是,当你的应用程序变得更大,需要使用分布在多台计算机上的传感器、处理器或硬件时,会发生什么情况呢?例如,你可能会遇到: | |||
| - 一个机器人上的一个摄像头。 | |||
| - 在强大的台式计算机上运行的重型 AI 模型。 | |||
| - 另一块硬件上的电机控制器。 | |||
| -笔记本电脑上的可视化显示。 | |||
| 您的 `Dataflow` 需要跨越所有这些机器。您将在每台机器上运行一个 `Dora Daemon` 来管理本地节点 。但是,如何让系统在正确的机器上启动正确的节点 ,协调它们之间通过网络的通信,并管理这个分布式应用程序的整个生命周期呢? | |||
| 这就是 **`Dora Coordinator`** 的工作。 | |||
| ## 空中交通管制塔 | |||
| 可以将 `Dora 协调器` 想象成整个 `dora` 系统的中央空中交通管制塔 ,尤其是在数据流分布于多台机器的情况下。每个 `Dora` 守护进程都是其本地工厂车间的“领班”(管理一台机器上的节点),而协调器则是高级管理者,负责监督所有不同的工厂车间,并确保所有设备和谐地协同工作。 | |||
| 其主要职责是: | |||
| 1. **接收命令:** 它从 `Dora CLI` 接收有关整个数据流的指令(例如“启动此数据流”、“停止该数据流”)。 | |||
| 2. **了解景观:** 它跟踪不同机器上可用的 `Dora Daemons` 。 | |||
| 3. **数据流编排:** 它读取数据流 `YAML` ,了解哪些节点需要在哪台机器上运行(如果指定),并告诉适当的 `Dora` 守护进程启动这些节点 。 | |||
| 4. **管理生命周期:** 它管理数据流的整体状态(运行、停止等)并协调跨多台机器停止或重新加载节点等命令。 | |||
| 5. **协调通信(高级):** 守护进程处理本地数据传输(如共享内存 )时,协调器确保底层传输(如 `Zenoh`,通常用于分布式设置)配置正确,以便数据可以按照数据流蓝图在不同机器上的守护进程之间流动。 | |||
| 本质上,协调器是连接用户(通过 `CLI`)和守护进程和节点的分布式集合的大脑。 | |||
| ## 将协调器与分布式数据流结合使用 | |||
| 当你使用 `dora run yolo.yml` 在一台机器上本地运行数据流时,`CLI` 可能会直接与本地 `Dora Daemon` 通信,或者它可能会自动启动并与本地 `Coordinator` 进程通信,然后由该进程管理本地 `Daemon`。对于在单机上运行的初学者来说,`Coordinator` 的存在可能一开始不太明显。 | |||
| 然而,当你定义一个跨多台机器的数据流时,协调器就变得至关重要,并且需要明确指定。你可以使用数据流 `YAML` 文件中的 `_unstable_deploy: machine` 字段来告诉 `dora` 每个节点应该在哪里运行。 | |||
| 以下是分布式数据流的简化示例 YAML: | |||
| ```yaml | |||
| nodes: | |||
| # Node for Camera on Machine A (e.g., a robot) | |||
| - id: camera-robot | |||
| path: camera-node-code # Code runs on the robot | |||
| outputs: | |||
| - image | |||
| _unstable_deploy: # Tell Dora where to deploy this node | |||
| machine: robot-machine-A # Needs a Daemon running on machine "robot-machine-A" | |||
| # Node for Object Detection on Machine B (e.g., a powerful PC) | |||
| - id: object-detector-pc | |||
| path: yolo-node-code # Code runs on the PC | |||
| inputs: | |||
| image: camera-robot/image # Receives image from robot-machine-A | |||
| outputs: | |||
| - bbox | |||
| _unstable_deploy: | |||
| machine: desktop-machine-B # Needs a Daemon running on machine "desktop-machine-B" | |||
| # Node for Plotting on Machine C (e.g., a laptop) | |||
| - id: plot-laptop | |||
| path: plot-node-code # Code runs on the laptop | |||
| inputs: | |||
| image: camera-robot/image # Receives image from robot-machine-A | |||
| boxes2d: object-detector-pc/bbox # Receives bounding boxes from desktop-machine-B | |||
| _unstable_deploy: | |||
| machine: laptop-machine-C # Needs a Daemon running on machine "laptop-machine-C" | |||
| ``` | |||
| 在此设置中,您将拥有: | |||
| - 在 `robot-machine-A` 上运行的 `Dora Daemon` 。 | |||
| - 在 `desktop-machine-B` 上运行的 `Dora Daemon` 。 | |||
| - 在 `laptop-machine-C` 上运行的 `Dora Daemon` 。 | |||
| - 至关重要的是,单个 `Dora Coordinator` 进程在所有这些机器可以访问的地方运行(它甚至可以在机器人、`PC` 或笔记本电脑之一上,或单独的服务器上)。 | |||
| 要运行这个分布式数据流,您可以使用 `Dora CLI` 并告诉它协调器的位置,通常使用 `--coordinator-addr` 标志: | |||
| ```bash | |||
| # Assuming the Coordinator is running at the IP address 192.168.1.100 | |||
| dora run distributed_dataflow.yml --coordinator-addr 192.168.1.100 | |||
| ``` | |||
| 执行此命令时: | |||
| 1. `Dora CLI` 程序启动。 | |||
| 2. 它连接到在 `192.168.1.100` 运行的 `Dora Coordinator` 进程。 | |||
| 3. 它将 `distributed_dataflow.yml` 内容发送给协调器。 | |||
| 4. 协调器读取 `YAML` 并查看哪些节点分配给哪些机器( `robot-machine-A` 、 `desktop-machine-B` 、 `laptop-machine-C` )。 | |||
| 5. 协调器识别与这些机器 `ID` 关联的 `Dora` 守护进程 (守护进程在启动时向协调器注册)。 | |||
| 6. 协调器向 `robot-machine-A` 上的守护进程发送指令以启动 `camera-robot` 节点。 | |||
| 7. 协调器向 `desktop-machine-B` 上的守护进程发送指令以启动 `object-detector-pc` 节点。 | |||
| 8. 协调器向 `laptop-machine-C` 上的守护进程发送指令以启动 `plot-laptop` 节点。 | |||
| 9. 协调器还确保正确设置底层分布式通信层(如 Zenoh),以便数据在承载连接节点的守护进程之间流动( `camera-robot` 输出 -> `object-detector-pc` 输入、 `camera-robot` 输出 -> `plot-laptop` 输入、 `object-detector-pc` 输出 -> `plot-laptop` 输入)。 | |||
| 10. 协调器继续运行,监视守护进程和数据流的状态,并将状态或日志消息(如果需要)传回 `CLI`。 | |||
| 要停止此分布式数据流,您可以再次使用 `CLI`,告诉它与哪个协调器对话: | |||
| ```bash | |||
| dora stop my_dataflow_id --coordinator-addr 192.168.1.100 | |||
| ``` | |||
| `CLI` 告诉协调器停止数据流,然后协调器向相关守护进程发送停止命令,守护进程又向它们管理的节点发送 `STOP` 事件。 | |||
| ## 协调器是如何工作的 | |||
| `Dora` 协调器作为独立、专用的后台进程运行。其主要目标是维护跨多台机器的 `dora` 系统的全局视图。 | |||
| - 通信: 它通过 `TCP/IP` 与 `Dora` 守护进程通信。守护进程启动时会连接到协调器。 | |||
| - 状态管理: 它保存正在运行的数据流的整体状态,包括哪些节点属于哪个数据流以及每个节点被分配到哪个守护进程/机器。 | |||
| - 事件循环: 与其他 `dora` 组件一样,`Coordinator` 也有一个事件循环。它会等待并处理不同的事件: | |||
| - 新的守护进程连接。 | |||
| - 来自 `Dora CLI` 的请求(例如, `Start` 、 `Stop` 、 `List` 、 `Build` 、 `Logs` 、 `Destroy` )。 | |||
| - 来自守护进程的消息(例如,“我的守护进程 ID 是......”,“数据流 X 已完成结果......”,“我的机器上的节点 Y 在订阅之前退出”)。 | |||
| - 内部事件(如心跳定时器,用于检查守护进程是否仍然活跃)。 | |||
| - 编排逻辑: 当它收到数据流的 `Start` 等命令时: | |||
| - 它解析 `YAML` 来了解节点部署。 | |||
| - 它检查其连接的守护进程列表,以查看所需的机器是否可用。 | |||
| - 然后,它将特定的 `DaemonCoordinatorEvent` 消息(如 `Spawn(SpawnDataflowNodes)` )发送给相关的守护进程,告诉每个守护进程它负责为这个特定的数据流启动哪些节点。 | |||
| - 它跟踪来自守护进程的回复( `DaemonCoordinatorReply` )以确认节点已成功生成或构建( `DataflowSpawnResult` , `DataflowBuildResult` )。 | |||
| - 一旦所有需要的节点在各自的守护进程上报告准备就绪,协调器就会向所有涉及的守护进程发送 `AllNodesReady` 事件,节点可以使用该事件(例如开始发送数据)。 | |||
| - 当它收到 `Stop` 命令时,它会向所有涉及的守护进程发送 `StopDataflow` 事件。 | |||
| 下面是一个简化的序列图,说明如何启动分布式数据流: | |||
|  | |||
| 这表明协调器充当着协调分布式守护进程动作的中心点。 | |||
| 您可以在 `dora-coordinator` 包 ( `binaries/coordinator/src/lib.rs` ) 中找到 `Dora Coordinator` 的核心实现。该文件包含 `start` 函数,该函数用于设置传入连接(来自守护进程和 CLI 控制平面)的监听器,以及处理不同事件类型( `Event::NewDaemonConnection` 、 `Event::Control` 、 `Event::Daemon` 等)的主异步循环 ( `start_inner` )。 | |||
| 协调器与守护进程之间交换的消息(例如 `DaemonCoordinatorEvent` 和 `DaemonCoordinatorReply` )定义在 `libraries/message/src/coordinator_to_daemon.rs` 和 `libraries/message/src/daemon_to_coordinator.rs` 中。`CLI` 命令定义在 `libraries/message/src/cli_to_coordinator.rs` 中。协调器的事件循环接收这些消息并触发相应的操作。 | |||
| ## 总结 | |||
| `Dora Coordinator` 是 `dora` 系统的中央编排器和指挥中心,对于管理分布在多台机器上的数据流至关重要。它接收来自 `Dora CLI` 的指令,跟踪可用的 `Dora Daemons` ,并根据数据流 YAML 蓝图协调在正确的机器上启动、停止和管理节点 。通过监督分布式系统,它使您能够无缝构建和管理复杂的多机器应用程序。 | |||
| 至此,我们对 `dora` 核心概念的入门教程就结束了。我们介绍了`蓝图(数据流)`、`构建块(节点、操作符)`、`通信机制(事件流、数据消息/Arrow Data、API 绑定)`以及`运行时基础架构(命令行界面、守护进程、协调器)`。现在,您对 `dora` 工作原理及其主要组件的作用有了基本的了解,这将帮助您更深入地构建自己的实时数据流应用程序。 | |||
| @@ -1 +1,91 @@ | |||
| # 守候进程 | |||
| # 第八章:守候进程 | |||
| **Author** : **`Leon 李扬`** | |||
| 欢迎回到 `dora` 教程!我们探索了 `Dataflow 蓝图`、 `节点`和`操作符`等构建块,它们如何通过事件流接收信息,以及如何高效地传输`数据消息/Arrow Data` 。我们还了解了 `API 绑定`如何帮助您编写组件代码,以及 `Dora CLI` 如何成为您管理一切的主要工具。 | |||
| 当您使用 `Dora CLI` 运行类似 `dora run` 命令时,实际上是在告诉 `dora` 执行您的数据流。但是,`CLI` 程序本身并不是运行节点和管理所有复杂通信的程序。这项工作落到了构成 `dora` 运行时的后台进程身上。这些后台进程中最重要的一个,尤其是在单机上运行的 `Dora 守护进程 (Dora Daemon)` 就是它。 | |||
| ## 本地交通经理 | |||
| 想象一下,你的计算机就像一个小型工厂车间 ,你的数据流描述了不同的工作站( 节点 )以及它们之间的传送带。你需要一个工头或经理来确保: | |||
| 1. 正确的工作站已启动。 | |||
| 2. 他们获得了所需的材料(投入)。 | |||
| 3. 成品(输出)被快速移动到下一个工作站。 | |||
| 4. 它们遵循指令(例如关闭)。 | |||
| `Dora Daemon` 就像那个当地的领班,它是一个运行在机器上的后台服务,负责管理特定机器上的 `dora` 组件。 | |||
| 其主要职责是: | |||
| - **启动节点:** 根据 `Dataflow YAML` 启动分配给其机器的节点进程。 | |||
| - **管理本地通信:** 设置并处理在同一台机器上启动的节点之间的高性能通信通道(例如共享内存 )。这对于速度至关重要! | |||
| - **接收指令:** 从 `Dora CLI` (在简单的本地设置中)或 `Dora Coordinator` (在分布式设置中)获取命令。 | |||
| - **监控节点:** 跟踪其管理的节点的健康和状态。 | |||
| - **处理流量:** 确保在本地节点之间移动的数据高效地到达正确的目标输入。在分布式设置中,它还有助于将数据路由到其他机器或从其他机器路由回来。 | |||
| ## 本地 `dora run` 中的守护进程 | |||
| 让我们考虑最简单的情况:使用 `dora run yolo.yml` 在单台机器上运行像我们的 `yolo.yml` 示例这样的数据流。 | |||
| ```bash | |||
| dora run yolo.yml | |||
| ``` | |||
| 执行此命令时: | |||
| 1. `Dora CLI` 程序启动。 | |||
| 2. `CLI` 连接到您机器后台运行的 `Dora` 守护进程。(如果守护进程未运行,`CLI` 可能会自动启动它)。 | |||
| 3. `CLI` 将 `yolo.yml` 数据流定义发送给守护进程。 | |||
| 4. 守护进程读取 `YAML` 并发现 `camera` 、 `object-detection` 和 `plot` 节点都需要在本地运行(因为没有指定 `_unstable_deploy: machine` 字段)。 | |||
| 5. 对于每个节点,守护进程都会在您的计算机上启动一个单独的进程,执行其 `path` 指定的代码(可能在运行 `build` 步骤之后)。 | |||
| 6. 根据 `YAML` 中定义的 `inputs` 和 `outputs` (例如 `image: camera/image` ),守护进程会在刚刚启动的进程之间建立高性能通信通道。对于图像等大数据,它使用共享内存 ,分配发送方和接收方 `Node` 进程均可直接访问的内存块,从而避免昂贵的复制操作。 | |||
| 7. 守护进程继续在后台运行,监视这些 `Node` 进程。 | |||
| 8. 当 `camera` 头节点发送输出 `image` 时,它会与守护进程通信(通过 `API` 绑定和内部通道)。守护进程会查看其内部映射(来自 `YAML`),并确定该 `image` 输出应该发送到 `object-detection` 的 `image` 输入和 `plot` 的 `image` 输入。 | |||
| 9. 如果图像数据较大,守护进程会确保将其放入共享内存中,并通过事件流通知 `object-detection` 和 `plot` 节点,告知其 `image` 输入端有新数据可用,并提供共享内存的详细信息。接收节点随后会直接从此共享内存块读取数据。 | |||
| 10. 守护进程继续管理此流量并监控节点。 | |||
| 11. 当你在运行 `dora run` 终端中按下 `Ctrl+C` 时,`CLI` 会向守护进程发送一个 `STOP` 信号。守护进程随后会向其管理的每个正在运行的 `Node` 进程的事件流发送 `STOP` 事件,使它们能够正常关闭。 | |||
| 以下是本地运行的简化序列图: | |||
|  | |||
| 该图显示守护进程是其机器上运行的节点的控制和通信的中心点。 | |||
| ## 分布式数据流中的守护进程(简要) | |||
| 在更复杂的设置中,您的数据流跨越多台机器,您将在每台承载 `dora` 节点的机器上运行一个 `Dora` 守护进程 。此外,还会有一个 `Dora 协调器`来管理这些机器上的整体数据流。 | |||
| 在这种情况下: | |||
| 1. `Dora CLI` (通常)与 `Dora 协调器` ( `dora start yolo.yml --coordinator-addr ...` )对话。 | |||
| 2. 协调器根据 `YAML` 中的 `_unstable_deploy: machine` 字段确定哪些节点在哪台机器上运行。 | |||
| 3. 协调器指示相关的守护进程 (每台机器上一个)启动分配给其机器的节点。 | |||
| 4. 然后,其各自机器上的每个守护进程执行如上所述的相同的本地管理任务:启动其指定的节点,在这些本地节点之间设置本地通信通道(如共享内存)并监视它们。 | |||
| 5. 对于不同机器上的节点之间的数据流动,守护进程会相互通信并与协调器(通常使用 `Zenoh`,如 `README` 中所述)进行通信,以确保数据在网络中路由。然而,守护进程的核心职责仍然是在其机器上本地管理 `dora` 环境。 | |||
| ## `Dora Daemon` 内部 | |||
| `Dora Daemon` 作为单个后台进程运行(您经常可以在系统的进程列表中看到它)。它主要用 `Rust` 编写,以提高性能和可靠性。 | |||
| 其内部运作的关键方面包括: | |||
| - **通信通道:** 它使用操作系统提供的各种高效的进程间通信 (`IPC`) 机制与其启动的 `Node` 进程进行通信。这些通道是在 `Node` 使用 `API 绑定`连接到守护进程时建立的。 | |||
| - **共享内存管理:** 守护进程负责分配和释放用于本地节点之间大数据消息的共享内存区域。它与丢弃令牌机制配合使用,在节点处理完共享内存块后接收通知,以便安全地重用或释放该内存块。 | |||
| - **事件循环:** 与节点和操作符类似,守护进程拥有自己的内部事件循环。它监听不同类型的事件: | |||
| - 来自 `Dora CLI` 或 `Dora Coordinator` 的命令(例如,启动数据流、停止数据流、获取日志)。 | |||
| - 来自 `Node` 进程的请求(例如,“我想发送数据”、“我正在订阅我的事件流”、“我已完成此 Drop Token”)。 | |||
| - 来自操作系统的事件(例如,`Node` 进程已退出)。 | |||
| - 与远程通信相关的事件(例如,通过 `Zenoh` 从另一台机器上的守护进程接收数据)。 | |||
| - **状态管理:** 它跟踪哪些数据流在其机器上运行,哪些节点属于哪个数据流,每个节点的状态(运行、停止等)以及本地通信的输入/输出映射。 | |||
| 你可以在 `dora-daemon` 包中找到守护进程的主要逻辑,具体位置为 `binaries/daemon/src/lib.rs` 。此文件包含核心 `Daemon` 结构体及其 `run_inner` 方法,该方法实现了主事件循环,用于处理来自 `CLI/Coordinator`、`Node` 进程和其他来源的事件。 | |||
| 守护进程与 `Node` 进程之间的通信由 `DaemonRequest` (从 `Node` 到守护进程)和 `DaemonReply / NodeEvent / NodeDropEvent` (从守护进程到 Node)等消息定义,您可以在 `libraries/message/src/node_to_daemon.rs` 和 `libraries/message/src/daemon_to_node.rs` 文件中看到这些消息。每种语言的 `API` 绑定都封装了这些消息和底层通信通道。 | |||
| ## 总结 | |||
| `Dora Daemon` 是 `dora` 运行时的关键组件。它充当机器上的本地管理器,负责启动和监控分配给它的 `Node` 进程,最重要的是,它基于 `Dataflow YAML` 建立和管理它们之间高效的本地通信通道(例如共享内存) 。它接收来自 `Dora CLI` 或 `Dora Coordinator` 的指令,并确保数据流量在其本地机器上正确流动。 | |||
| 现在我们了解了本地管理器(守护进程),让我们了解一下协调多个守护进程并管理跨不同机器的数据流的组件: `Dora Coordinator` 。 | |||
| @@ -1 +1,111 @@ | |||
| # 事件流 | |||
| # 第四章:事件流 | |||
| **Author** : **`Leon 李扬`** | |||
| 欢迎回到 `dora` 教程!在第二章:节点中,我们了解到节点是数据流中独立的工作单元。在第三章:操作符中,我们了解了操作符如何在一种特殊的节点内部构建工作。节点和操作符都需要一种方法来感知需要关注的事件发生——例如新数据到达或停止命令。它们是如何接收这些重要通知的? | |||
| 这就是 **`事件流`** 发挥作用的地方。 | |||
| ## 节点(或操作员)的收件箱 | |||
| 想象一下,一个节点或操作员就像一个坐在办公桌前准备处理任务的微型办公室职员。他们不会随意开始工作;他们需要被告知新任务何时到来以及任务是什么 。 | |||
| `事件流`就像是节点或操作员的收件箱或通知源 。它是一个连续的消息流,告知工蜂完成工作所需的一切信息。 | |||
| 无论您的代码是作为独立节点进程运行还是作为运行时节点内的操作员运行,它都会通过其专用`事件流`接收信息。 | |||
| ## 事件流中包含哪些类型的消息? | |||
| 事件流不仅仅用于传入数据。它包含不同类型的消息, `dora` 称之为 “事件” 。这些事件会告诉您的节点或操作员数据流和系统中正在发生的事情。 | |||
| 以下是您通常会在信息流中发现的主要事件类型: | |||
| - **INPUT:** 这是最常见的事件。它表示新数据已到达节点(或算子)定义的某个输入。该事件将包含数据本身,并告知您数据来自哪个输入。这就是节点和算子接收需要处理的数据的方式。 | |||
| - **InputClosed:** 此事件表示节点(或算子)的某个输入源已关闭或断开连接。如果正在为此输入生成数据的节点/算子完成其工作或崩溃,则可能会发生这种情况。您的代码可能需要对此做出反应,例如在关键输入丢失时优雅地关闭。 | |||
| - **STOP:** 此事件是来自 `dora 运行时`的命令,告知 `Node` 或 `Operator` 关闭。当您使用 `dora CLI` 停止数据流,或者数据流自然结束时(例如,所有输入都关闭,没有其他工作要做)时,就会发生这种情况。您的代码应该监听此事件并干净地退出。 | |||
| - **RELOAD:** 此事件表示节点或操作员应重新加载其配置或逻辑(如果支持热重载)。这是一项高级功能,允许在不完全重启的情况下更改数据流。 | |||
| - **ERROR:** 表示事件流本身发生了不可恢复的错误。 | |||
| ## 节点和操作员如何监听流 | |||
| 您的节点或操作员代码使用您选择的语言的 `dora API 绑定`与事件流进行交互(我们将在第 6 章:API 绑定中介绍 API)。 | |||
| 典型的模式是进入一个循环,反复向事件流询问“下一个”事件。然后,代码检查事件的类型及其详细信息,以决定采取什么操作。 | |||
| 下面是一个简化的 `Python` 示例,展示了节点如何监听其事件流: | |||
| ```py | |||
| from dora import Node | |||
| # Initialize the node - connects to the dora runtime | |||
| node = Node() | |||
| print("Node initialized. Waiting for events...") | |||
| # Enter the loop to process events from the stream | |||
| for event in node: | |||
| event_type = event["type"] | |||
| event_id = event["id"] # Often the input ID | |||
| print(f"Received event: Type={event_type}, ID={event_id}") | |||
| if event_type == "INPUT": | |||
| # Handle incoming data | |||
| print(f" -> Data received on input '{event_id}'") | |||
| data = event["value"] | |||
| # Now you would process the 'data'... | |||
| # Example: If it's from an input named 'camera_image' | |||
| if event_id == "camera_image": | |||
| print(" -> Processing camera image...") | |||
| # process_image(data) | |||
| # node.send_output(...) # Send results if needed | |||
| pass # Placeholder for actual processing | |||
| elif event_type == "STOP": | |||
| # Handle stop command | |||
| print(" -> Received STOP command. Exiting loop.") | |||
| break # Exit the main loop | |||
| elif event_type == "InputClosed": | |||
| # Handle input closing | |||
| print(f" -> Input '{event_id}' closed.") | |||
| # Decide if the node should continue or stop | |||
| elif event_type == "RELOAD": | |||
| # Handle reload command | |||
| print(" -> Received RELOAD command.") | |||
| # Reload configuration if implemented | |||
| elif event_type == "ERROR": | |||
| # Handle a stream error | |||
| print(f" -> Received ERROR: {event['error']}") | |||
| # Log or react to the error | |||
| print("Node stopping gracefully.") | |||
| ``` | |||
| 这个简单的循环是许多 `dora Node` 和 `Operator` 实现的核心。`dora API`( `for event in node`: 处理了等待流中下一个事件的复杂任务,并将其呈现为一个简单的类似字典的对象( `event` ) `dora` 以便您的代码可以轻松理解和响应。 | |||
| `Operator` 响应事件的代码结构非常相似,但它使用 `Operator API` 的 `on_event` 函数,而不是直接循环遍历 `Node` 对象(如第三章:`Operator` 中所示)。原理相同:接收事件,检查其类型和 ID,并做出相应的响应。 | |||
| ## 底层:`Stream` 的工作原理 | |||
| 当 `dora 运行时`( `Dora Daemon` 或 `Dora Coordinator` )基于您的 `Dataflow YAML` 启动 `Node` 进程时,它还会设置形成该特定 `Node` 的事件流的通信通道。 | |||
| 回想一下,数据流就像管道图一样。当你在 `YAML` 中将 `camera/image` 连接到 `object-detection/image` 时,运行时并不会神奇地传输数据。它会在 `camera` 节点的输出机制和 `object-detection` 节点的事件流输入机制之间建立专用通道。 | |||
| 当 `camera` 头节点生成 `image` 输出时, `dora 运行时`会获取该数据,并将 `INPUT` 事件放置到事件流中 ,专门用于 `object-detection` 节点 。 `object-detection` 节点的代码在其 `for event in node` 循环中等待,然后接收此 `INPUT` 事件,从中获取图像数据,并开始处理。 | |||
| `STOP` 和 `RELOAD` 命令的工作原理类似。当您从 `CLI` 停止数据流时,运行时会向所有正在运行的节点和算子的事件流发送 `STOP` 事件。 | |||
| 以下是此交互的简化视图: | |||
|  | |||
| 该图显示,`事件流`充当中介,由 `dora 运行时`管理,将各种类型的事件传递给正在主动监听它们的 `Node` 或 `Operator` 进程。`dora` 优化该流的内部机制(通常使用共享内存来存储数据,我们将会看到),以提高性能。 | |||
| ## 总结 | |||
| `事件流`是 `dora` 中的一个基本概念。它是节点和操作符接收其运行所需的所有信息的重要通信渠道,包括传入数据 ( `INPUT` )、输入源关闭信号 ( `InputClosed` ) 以及系统命令( `STOP` 、 `RELOAD` )。通过监听此事件流,您的节点和操作符代码可以动态地响应数据流的状态。`dora` 运行管理这些事件流,并根据数据流蓝图将事件传递给相应的节点和操作符。 | |||
| 现在您已经了解了节点和操作员如何接收通知,让我们仔细看看 `INPUT` 事件中经常包含的 `数据本身` 。 | |||
| @@ -1,6 +1,8 @@ | |||
| # 节点 | |||
| # 第二章:节点 | |||
| 欢迎回到 `dora` 的教程!在[上一章:数据流](./dataflow.mdx)中,我们了解到数据流是 `dora` 系统的蓝图,它使用 `YAML` 文件定义了所有部分以及数据在它们之间流动的方式。现在,让我们深入研究这些单独的部分———节点(`nodes`)。 | |||
| **Author** : **`Leon 李扬`** | |||
| 欢迎回到 `dora` 的教程!在[上一章:数据流](./dataflow.mdx)中,我们了解到数据流是 `dora` 系统的蓝图,它使用 `YAML` 文件定义了所有部分以及数据在它们之间流动的方式。现在,让我们深入研究这些单独的部分 —— **节点**(`nodes`)。 | |||
| ## 应用程序中的工蜂 | |||
| @@ -1,4 +1,6 @@ | |||
| # 操作符 | |||
| # 第三章:操作符 | |||
| **Author** : **`Leon 李扬`** | |||
| 欢迎回来!在第一章:数据流中,我们学习了 `dora` 应用程序的整体蓝图以及它如何连接各个部分。在第二章:节点中,我们深入探讨了这些节点部分,并了解到每个节点通常作为一个独立的进程运行,执行特定的任务,并通过数据流 `YAML` 中定义的输入和输出进行通信。 | |||