2. ROS2核心概念

说实话,ROS2的四大核心概念——节点、话题、服务、动作——在Apollo里并不是原封不动搬过来的。Apollo作为一套工业级的自动驾驶系统,它对ROS2做了一层自己的封装和改造。我当年刚接触Apollo时,第一反应是:「这跟ROS2长得有点像,但又不完全一样。」

今天我们就来拆解一下,这四样东西在Apollo里到底变成了什么模样。

2.1 节点(Node):从独立进程到Cyber组件

ROS2里的节点,说白了就是一个独立的可执行程序。每个节点干自己的活,通过话题跟别人通信。但在Apollo里,他们搞了一套叫Cyber RT的框架,节点这个概念被重新定义了。

在Apollo中,你几乎看不到直接创建Node的代码。取而代之的是Component。每个Component就是一个算法模块,比如感知、规划、控制。它们被动态加载到Cyber的调度框架里运行。

核心区别:

  • ROS2节点:每个节点一个进程,独立启动
  • Apollo Component:多个组件可以共享一个进程,资源利用率更高

我刚开始用Apollo时,习惯性地想用ros2 run去启动模块,结果发现根本行不通。Apollo用的是mainboard这个工具来加载组件。举个例子:

# ROS2的方式
ros2 run perception_node perception_node

# Apollo的方式
mainboard -d /apollo/modules/perception/dag/perception.dag

看到区别了吗?Apollo通过DAG文件来定义组件之间的依赖关系和执行顺序。这其实比ROS2的节点管理要强大得多。

2.2 话题(Topic):从发布订阅到Channel

话题是ROS2里最常用的通信方式。发布者往话题上发消息,订阅者接收。Apollo里也有类似的东西,叫Channel。

嗯,这里要注意:Channel和Topic在概念上几乎一样,但实现细节完全不同。

特性 ROS2 Topic Apollo Channel
通信方式 DDS中间件 共享内存 + 无锁队列
延迟 毫秒级 微秒级
序列化 CDR序列化 自定义序列化
典型用法 传感器数据、控制指令 传感器数据、控制指令

我个人习惯用cyber_monitor来查看Apollo里的Channel数据流。比如你想看激光雷达的点云数据:

# ROS2查看话题
ros2 topic echo /lidar_points

# Apollo查看Channel
cyber_monitor -c /apollo/sensor/lidar128/compensator/PointCloud2

我在项目中遇到过一个问题:用ROS2的Topic传输大尺寸点云时,延迟经常飙到几十毫秒。但Apollo的Channel用了共享内存,延迟基本稳定在1毫秒以内。这就是为什么自动驾驶系统必须自己造轮子——通用中间件扛不住这种实时性要求。

2.3 服务(Service):从请求响应到Service

ROS2的服务是同步的请求-响应模式。客户端发请求,服务端处理完返回结果。Apollo保留了这种模式,但用起来更轻量。

举个例子,在Apollo里你想获取当前车辆的定位信息:

# ROS2服务调用
ros2 service call /get_localization GetLocalization "{}"

# Apollo服务调用
cyber_service call /apollo/localization/pose LocalizationEstimation "{}"

看起来差不多对吧?但底层实现有区别。ROS2的服务基于DDS的请求-响应模式,而Apollo的服务是基于共享内存的。这意味着Apollo的服务调用延迟更低,但跨机器调用时需要额外配置。

我的建议:在Apollo里,能用Channel就别用Service。因为Service是同步的,会阻塞调用方。而Channel是异步的,更适合自动驾驶这种实时系统。我见过有人用Service来获取感知结果,结果导致规划模块卡死——这就是典型的「用错工具」。

2.4 动作(Action):从Goal到Task

ROS2的动作是处理长时间任务的利器。比如让车辆从A点开到B点,这个过程可能需要几十秒甚至几分钟。ROS2用Action来实现:客户端发送目标,服务端反馈进度和结果。

Apollo里没有直接叫Action的东西,但有一个类似的机制叫Task。比如在规划模块中,PlanningTask就是一个典型的动作实现。

我曾经在调试Apollo的泊车功能时,发现它内部就是用Task来管理整个泊车过程的。大致流程是这样的:

  1. 发送泊车目标(Goal)
  2. 规划模块生成路径(Feedback)
  3. 控制模块执行路径(Feedback)
  4. 车辆到达目标位置(Result)

你看,这不就是ROS2 Action的翻版吗?只不过Apollo把它封装到了自己的框架里。

避坑指南:我曾经在Apollo里实现一个自定义动作时,直接照搬了ROS2的ActionServer API,结果发现Cyber RT根本不支持。后来才搞清楚,Apollo的Task机制需要自己管理状态机。所以如果你从ROS2转过来,千万别直接套用ROS2的Action模式。

2.5 知识体系总览

下面这张图是我自己整理的,把ROS2和Apollo的对应关系画清楚了。你看一眼就能明白整体架构。

ROS2 核心概念 Apollo 映射 节点 (Node) 独立进程 Component 动态加载,共享进程 映射 话题 (Topic) 发布/订阅,DDS Channel 共享内存,无锁队列 映射 服务 (Service) 请求/响应,同步 Service 轻量级,共享内存 映射 动作 (Action) 长时间任务,反馈 Task 状态机管理 映射

这张图把四个核心概念的对应关系都标出来了。你仔细看会发现,Apollo并没有完全抛弃ROS2的设计思想,而是在性能关键的地方做了替换。比如用共享内存代替DDS,用Component代替Node。

2.6 实际项目中的选择建议

说了这么多理论,最后给点实际建议。你在做Apollo开发时,怎么选通信方式?

  • 高频数据(传感器、控制指令):用Channel。延迟低,吞吐量大。
  • 低频配置(参数查询、状态获取):用Service。简单直接。
  • 长时间任务(泊车、换道):用Task。有进度反馈,可取消。

我个人习惯是:能用Channel绝不用Service。因为自动驾驶系统里,阻塞操作是万恶之源。你想想看,如果规划模块因为等一个Service响应而卡住,那车辆可能就错过了一个决策窗口——后果很严重。

好了,这一章的内容就到这里。记住这四个概念的映射关系,你在Apollo里写代码时就不会迷路了。

可可精品资料库