zhangyi1357 793418f0a2 update
2024-10-14 09:45:07 +08:00

140 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AimRT 中的基本概念
## AimRT 框架包含的内容
参考 AimRT 源码中的 src 目录AimRT 框架中包含的内容如下:
```
src
├── common --------------------------------- // 一些基础的、可以直接使用的通用组件,例如 string、log 接口、buffer 等
├── examples ------------------------------- // AimRT 官方示例
│ ├── cpp -------------------------------- // CPP 接口的示例
│ ├── py --------------------------------- // Python 接口的示例
│ └── plugins ---------------------------- // 一些各方插件的使用示例
├── interface ------------------------------ // AimRT 接口层
│ ├── aimrt_core_plugin_interface -------- // [CPP] 插件开发接口
│ ├── aimrt_module_c_interface ----------- // [C] 模块开发接口
│ ├── aimrt_module_cpp_interface --------- // [CPP] 模块开发接口,对 C 版本的封装
│ ├── aimrt_module_protobuf_interface ---- // [CPP] 与 protobuf 相关的模块开发接口,基于 CPP 版本接口
│ ├── aimrt_module_ros2_interface -------- // [CPP] 与 ROS2 相关的模块开发接口,基于 CPP 版本接口
│ ├── aimrt_pkg_c_interface -------------- // [C] Pkg 开发接口
│ └── aimrt_type_support_pkg_c_interface - // [C] Type support 包接口
├── plugins -------------------------------- // AimRT 官方插件
├── protocols ------------------------------ // 一些 AimRT 官方的标准协议
├── runtime -------------------------------- // AimRT 运行时
│ ├── core ------------------------------- // 运行时核心库
│ ├── main ------------------------------- // 基于 core 实现的一个主进程"aimrt_main"
│ └── python_runtime --------------------- // 基于 pybind11 封装的 python 版本运行时
└── tools ---------------------------------- // 一些配套工具
```
## AimRT 中的 "Module" 概念
与大多数框架一样AimRT 拥有一个用于标识独立逻辑单元的概念:`Module``Module`是一个逻辑层面的概念,代表一个逻辑上内聚的块。`Module`之间可以在逻辑层通过`Channel``RPC`两种抽象的接口通信。可以通过实现几个简单的接口来创建一个`Module`
一个`Module`通常对应一个硬件抽象、或者是一个独立算法、一项业务功能。`Module`可以使用框架提供的句柄来调用各项运行时功能,例如配置、日志、执行器等。框架给每个`Module`提供的句柄也是独立的,来实现一些资源统计、管理方面的功能。
## AimRT 中的 "Node" 概念
`Node`代表一个可以部署启动的进程,在其中运行了一个 AimRT 框架的 Runtime 实例。`Node`是一个部署、运行层面的概念,一个`Node`中可能存在多个`Module``Node`在启动时可以通过配置文件来设置日志、插件、执行器等运行参数。
## AimRT 中的 "Pkg" 概念
`Pkg`是 AimRT 框架运行`Module`的一种途径。`Pkg`代表一个包含了单个或多个`Module`的动态库,`Node`在运行时可以加载一个或多个`Pkg`。可以通过实现几个简单的模块描述接口来创建`Pkg`
`Module`的概念更侧重于代码逻辑层面,而`Pkg`则是一个部署层面的概念,其中不包含业务逻辑代码。一般来说,在可以兼容的情况下,推荐将多个`Module`编译在一个`Pkg`中,这种情况下使用 RPC、Channel 等功能时性能会有优化。
通常`Pkg`中的符号都是默认隐藏的,只暴露有限的纯 C 接口,不同`Pkg`之间不会有符号上的相互干扰。不同`Pkg`理论上可以使用不同版本的编译器独立编译,不同`Pkg`里的`Module`也可以使用相互冲突的第三方依赖,最终编译出的`Pkg`可以二进制发布。
## AimRT 框架集成业务逻辑的两种方式
AimRT 框架可以通过两种方式来集成业务逻辑,分别是 **App模式****Pkg模式**,实际采用哪种方式需要根据具体场景进行判断。两者的区别如下:
- **App模式**:在开发者自己的 Main 函数中直接链接 AimRT 运行时库,编译时直接将业务逻辑代码编译进主程序:
- **优势**:没有 dlopen 这个步骤,没有 so只会有最终一个 exe。
- **劣势**:可能会有第三方库的冲突;无法独立的发布`Module`,想要二进制发布只能直接发布 exe。
- **使用场景**:一般用于小工具、小型 Demo 场景,没有太大的模块解耦需求;
- **Pkg模式**:使用 AimRT 提供的 **aimrt_main** 可执行程序,在运行时根据配置文件加载动态库形式的`Pkg`,导入其中的`Module`类:
- **优势**:编译业务`Module`时只需要链接非常轻量的 AimRT 接口层,不需要链接 AimRT 运行时库,减少潜在的依赖冲突问题;可以二进制发布 so独立性较好。
- **劣势**:框架基于 dlopen 加载`Pkg`,极少数场景下会有一些兼容性问题。
- **使用场景**:一般用于中大型项目,对模块解耦、二进制发布等有较强烈需求时;
无论采用哪种方式都不影响业务逻辑,且两种方式可以共存,实际采用哪种方式需要根据具体场景进行判断。
注意,上述说的两种方式只是针对 Cpp 开发接口。如果是使用 Python 开发,则只支持**App模式**。
## AimRT 中的 "Protocol" 概念
`Protocol`意为协议,代表`Module`之间通信的数据格式,用来描述数据的字段信息以及序列化、反序列化方式,例如`Channel`的订阅者和发布者之间制定的数据格式、或者`RPC`客户端和服务端之间制定的请求包/回包的数据格式。通常由一种`IDL`( Interface description language )描述,然后由某种工具转换为各个语言的代码。
AimRT 目前官方支持两种 IDL
- Protobuf
- ROS2 msg/srv
但 AimRT 并不限定协议与IDL的具体类型使用者可以实现其他的 IDL例如 Thrift IDL、FlatBuffers 等,甚至支持一些自定义的 IDL。
## AimRT 中的 "Channel" 概念
`Channel`也叫数据通道,是一种典型的通信拓补概念,其通过`Topic`标识单个数据通道,由发布者`Publisher`和订阅者`Subscriber`组成,订阅者可以获取到发布者发布的数据。`Channel`是一种多对多的拓补结构,`Module`可以向任意数量的`Topic`发布数据,同时可以订阅任意数量的`Topic`。类似的概念如 ROS 中的 Topic、Kafka/RabbitMQ 等消息队列。
在 AimRT 中Channel 由`接口层``后端`两部分组成,两者相互解耦。接口层定义了一层抽象的 Api表示逻辑层面上的`Channel`;而后端负责实际的 Channel 数据传输可以有多种类型。AimRT 官方提供了一些 Channel 后端,例如 mqtt、ros2 等,使用者也可以自行开发新的 Channel 后端。
开发者在使用 AimRT 中的 Channel 功能时,先在业务逻辑层调用接口层的 API往某个 Topic 中发布数据,或订阅某个 Topic 的数据。然后 AimRT 框架会根据一定的规则,选择一个或几个 Channel 后端进行处理,这些后端将数据通过一些特定的方式发送给其他节点,由其他节点上对应的 Channel 后端接收数据并传递给业务逻辑层。整个逻辑流程如下图所示:
![](./picture/pic_3.png)
## AimRT 中的 "Rpc" 概念
`RPC`也叫远程过程调用,基于请求-回复模型,由客户端`Client`和服务端`Server`组成,`Module`可以创建客户端句柄,发起特定的 RPC 请求,由其指定的、或由框架根据一定规则指定的服务端来接收请求并回复。`Module`也可以创建服务端句柄,提供特定的 RPC 服务,接收处理系统路由过来的请求并回复。类似的概念如 ROS 中的 Services、GRPC/Thrift 等 RPC 框架。
在 AimRT 中RPC 也由`接口层``后端`两部分组成,两者相互解耦。接口层定义了 RPC 的抽象 Api而后端则负责实际的 RPC 调用。AimRT 官方提供了一些 RPC 后端,例如 http、ros2 等,使用者也可以自行开发新的 RPC 后端。
开发者使用 AimRT 的 RPC 功能时,先在业务逻辑层调用接口层 API通过 Client 发起一个 RPC 调用AimRT 框架会根据一定的规则选择一个 RPC 后端进行处理,它将数据通过一些特定的方式发送给 Server 节点,由 Server 节点上对应的 Rpc 后端接收数据并传递给业务层,并将业务层的回包传递回 Client 端。整个逻辑流程如下图所示:
![](./picture/pic_4.png)
## AimRT 中的 "Filter" 概念
AimRT 中提供了`Filter`功能,用以增强 RPC 或 Channel 的能力。Filter 是一层贴着 Interface 层的、用户可自定义的逻辑插接点,按照相对于 Interface 层的位置,分为框架侧 FilterFramework Filter和用户侧 FilterUser Filter。按照服务的功能又分为 RPC Filter 和 Channel Filter。
Filter 在 RPC 或 Channel 每次被调用时触发,以一种类似洋葱的运行结构,在 RPC 或 Channel 调用前后做一些自定义动作,例如计算耗时、上报监控等。以 RPC Filter 为例,其运行时流程如下图所示:
![RPC Filter](./picture/pic_7.png)
## AimRT 中的 "Executor" 概念
`Executor`,或者叫执行器,是指一个可以运行任务的抽象概念,一个执行器可以是一个 Fiber、Thread 或者 Thread Pool我们平常写的代码也是默认的直接指定了一个执行器Main 线程。一般来说,能提供以下接口的就可以算是一个执行器:
```cpp
void Execute(std::function<void()>&& task);
```
还有一种`Executor`提供定时执行的功能,可以指定在某个时间点或某段时间之后再执行任务。其接口类似如下:
```cpp
void ExecuteAt(std::chrono::system_clock::time_point tp, std::function<void()>&& task);
void ExecuteAfter(std::chrono::nanoseconds dt, std::function<void()>&& task);
```
在 AimRT 中,执行器功能由**接口层**和**实际执行器的实现**两部分组成,两者相互解耦。接口层定义了执行器的抽象 Api提供投递任务的接口。而实现层则负责实际的任务执行根据实现类型的不同有不一样的表现。AimRT 官方提供了几种执行器,例如基于 Asio 的线程池、基于 Tbb 的无锁线程池、基于时间轮的定时执行器等。
开发者使用 AimRT 的执行器功能时,在业务层将任务打包成一个闭包,然后调用接口层的 API将任务投递到具体的执行器内而执行器会根据自己的调度策略在一定时机执行投递过来的任务。具体逻辑流程如下图所示
![](./picture/pic_5.png)
## AimRT 中的 "Plugin" 概念
`Plugin`指插件,是指一个可以向 AimRT 框架注册各种自定义功能的动态库可以被框架运行时加载或在用户自定义的可执行程序中通过硬编码的方式注册到框架中。AimRT 框架暴露了大量插接点和查询接口,例如:
- 日志后端注册接口
- Channel/Rpc 后端注册接口
- Channel/Rpc 注册表查询接口
- 各组件启动 hook 点
- RPC/Channel 调用过滤器
- 模块信息查询接口
- 执行器注册接口
- 执行器查询接口
- ......
使用者可以直接使用一些 AimRT 官方提供的插件,也可以从第三方开发者处寻求一些插件,或者自行实现一些插件以增强框架的服务能力,满足特定需求。