在上一篇文章中,我们详细讨论了 ROS 2 中 Topic、Service 和 Action 三种通信机制。

很多人在第一次接触 ROS 2 时,会发现一个非常特别的概念:QoS(Quality of Service,服务质量)。

在传统的软件开发中,我们发送一条消息,通常只需要关心“消息有没有发出去”和“对方有没有收到”。

但机器人系统并不是一个普通的软件通信系统。

一台移动机器人上的摄像头可能每秒产生几十帧图像,激光雷达可能持续输出扫描数据,IMU 可能以数百 Hz 甚至更高频率输出数据,而关节控制系统则可能要求严格按照固定周期运行。

这些数据对于系统而言,并不具有完全相同的重要性。

例如:

  • 一帧已经过时的摄像头图像,还要不要?

  • 丢失一帧激光雷达数据是否可以接受?

  • 关节状态数据应该保留多少条?

  • 一个控制命令如果延迟 100 ms 才到达,还有没有意义?

  • 一个新启动的节点,是否需要拿到之前已经发布的数据?

  • 消息发送失败后,是应该重传,还是直接丢弃?

  • 一个周期性数据如果长时间没有到达,系统应该怎么办?

这些问题实际上都属于通信服务质量问题。

这也是 ROS 2 与 ROS 1 在设计理念上的一个重要区别。

ROS 2 底层通信建立在 DDS(Data Distribution Service)等中间件机制之上,通过 QoS 对数据传输行为进行更加精细的控制。

因此,如果真正想理解 ROS 2 的通信机制,不能只停留在:

Publisher 发布消息 → Subscriber 接收消息。

还需要继续往下理解:

ROS 2 → DDS → QoS → 网络传输 → Executor → Callback → Linux调度。

更重要的是,QoS解决的是“数据怎么传”的问题,而实时操作系统解决的是“任务什么时候运行”的问题。

这两个问题看起来相似,实际上属于不同层次。

本文就从 QoS 开始,把 ROS 2 中最重要的 Reliability、Durability、History、Depth、Deadline 等机制一次讲清楚,并进一步分析:

为什么 QoS 配置得再好,也不等于机器人系统就具备真正的实时性?


一、ROS 2为什么需要QoS?机器人通信并不是“消息到了就行”

理解 QoS 最简单的方法,是先假设我们正在设计一台移动机器人。

机器人上有:

  • 摄像头;

  • 激光雷达;

  • IMU;

  • GPS;

  • 轮速计;

  • 电机控制器;

  • 关节状态;

  • 路径规划模块;

  • 定位模块;

  • 控制模块。

这些模块之间都需要通信。

例如:

摄像头
   │
   ▼
Camera Node
   │
   │ Image
   ▼
视觉算法 Node
   │
   ▼
目标检测
   │
   ▼
路径规划
   │
   ▼
运动控制

再比如:

IMU ───────────────┐
                   │
LiDAR ─────────────┼──> 定位 Node
                   │
Wheel Odometry ────┘
                         │
                         ▼
                    Robot Pose
                         │
                         ▼
                    Path Planner
                         │
                         ▼
                    Controller

表面看起来,这只是几个 Publisher 和 Subscriber。

但是,如果进一步思考,就会发现不同数据对于通信的要求完全不同。

例如摄像头:

Frame 100
Frame 101
Frame 102
Frame 103
Frame 104

如果 Frame 101 丢失了,系统通常可以继续处理 Frame 102。

但对于某些控制命令:

停止
前进
停止

如果“停止”命令发生异常延迟,问题可能就完全不同。

因此,机器人通信不能简单地使用一种统一的传输策略。

这就是 QoS 存在的意义。

QoS 本质上是在定义:某一种数据,在通信过程中应该遵守什么规则。

例如:

数据必须可靠到达吗?

数据允许丢失吗?

新加入的节点需要历史数据吗?

数据最多保存多少条?

多久没有收到数据就应该认为异常?

这些问题都可以通过 QoS 进行描述。

从架构上看,可以简单理解为:

        ROS 2应用层
             │
       Publisher / Subscriber
             │
             ▼
        ROS 2 Middleware
             │
             ▼
          DDS / RMW
             │
       ┌─────┴─────┐
       │    QoS    │
       └─────┬─────┘
             │
             ▼
        网络 / 进程间通信
             │
             ▼
       接收端 Middleware
             │
             ▼
          Executor
             │
             ▼
          Callback
             │
             ▼
       Linux任务调度
             │
             ▼
            CPU

这个结构非常重要。

因为它告诉我们:

QoS 位于 ROS 2 通信链路中,但它并没有直接控制 CPU 上的任务什么时候获得执行机会。

这也是后面理解 ROS 2 实时性的关键。


二、Reliability、Durability、History:QoS最核心的三个概念

ROS 2 QoS 有很多策略,但如果刚开始学习,最值得优先理解的是:

Reliability、Durability、History。

它们分别回答三个不同的问题:

Reliability:消息要不要尽可能可靠地送达?

Durability:新加入的节点要不要看到过去的数据?

History:过去的数据到底保存多少?

1. Reliability:数据到底要不要“可靠送达”?

Reliability 主要有两种常见策略:

BEST_EFFORT
RELIABLE

BEST_EFFORT

Best Effort 可以简单理解为:

能发就发,丢了就丢了。

它不强调一定要把每一条数据送到。

例如:

Publisher

Frame 1 ─────────────> Subscriber
Frame 2 ─────────────> Subscriber
Frame 3 ─────X
Frame 4 ─────────────> Subscriber
Frame 5 ─────────────> Subscriber

Frame 3 丢失。

Subscriber 不会为了 Frame 3 一直等待,也不会要求发送方重新发送。

这种机制非常适合某些高频传感器数据。

比如摄像头:

30 FPS

Frame 1
Frame 2
Frame 3
Frame 4
Frame 5
...

如果偶尔丢一帧,后面的新帧很快就会到达。

相比于:

为了保证 Frame 3,一直等待重传。

很多情况下,系统更希望:

Frame 3 不要了,直接处理 Frame 4。

这就是 Best Effort 的典型使用场景。


RELIABLE

Reliable 则更加关注:

消息应该可靠地交付。

如果发生丢包,底层通信机制可能采取确认、重传等机制来保证可靠传输。

可以简单理解为:

Frame 1 ─────────────> 收到
Frame 2 ─────────────> 收到
Frame 3 ─────X
              │
              └── 重传
                   │
                   ▼
                收到 Frame 3

这样做的好处很明显:

消息可靠性更高。

但代价同样明显:

  • 可能产生额外通信开销;

  • 可能产生重传;

  • 可能增加队列压力;

  • 网络异常时可能产生等待;

  • 在高负载场景下可能增加系统复杂度。

因此:

Reliable 并不意味着一定更适合机器人实时控制。

这是学习 ROS 2 QoS 时非常容易出现的误区。

很多人会自然地认为:

Reliable 比 Best Effort 更可靠,所以实时系统应该全部使用 Reliable。

实际上并不是这样。

假设一个机器人控制器每 1 ms 更新一次:

T0     T1     T2     T3     T4
│      │      │      │      │
CMD1   CMD2   CMD3   CMD4   CMD5

如果 CMD2 因网络问题迟迟没有到达。

此时系统到底应该:

等待 CMD2

还是:

CMD2已经过时
直接处理最新命令 CMD3

需要结合具体业务判断。

这说明:

可靠性和实时性不是同一个指标。

Reliability 解决的是:

“消息有没有可靠地传递。”

实时性更关注:

“任务有没有在规定时间内完成。”

二者不能简单画等号。


2. Durability:新加入的节点要不要看到过去的数据?

第二个非常重要的 QoS 策略是 Durability。

它主要解决一个问题:

Subscriber 在 Publisher 已经发布过一些数据之后才启动,它要不要看到之前的数据?

最容易理解的例子是:

地图 Map

假设系统已经生成了一张地图:

Map Node
   │
   └──────> /map

地图发布以后,导航节点才启动。

如果地图只在过去发布过一次,那么新启动的导航节点还能不能拿到地图?

这时候 Durability 就很重要。

常见策略包括:

VOLATILE
TRANSIENT_LOCAL

VOLATILE

Volatile 可以理解为:

我只关心现在的数据,过去的数据不负责保存给后来者。

例如:

Publisher
   │
   ├── Data 1
   ├── Data 2
   ├── Data 3
   │
   ▼
Subscriber后来才启动

Subscriber 通常不会因为自己晚启动,就自动获得之前的数据。

这种模式非常适合持续变化的数据。

例如:

IMU
Camera
LiDAR

因为这些数据本身就是连续产生的。


TRANSIENT_LOCAL

Transient Local 则可以理解为:

Publisher 在本地保留一定的数据状态,让后来加入的 Subscriber 有机会获得。

例如:

Map Publisher

发布 Map
   │
   ▼
保存状态
   │
   ▼
Navigation Node启动
   │
   ▼
获得之前的Map

对于地图、配置、状态类数据,这种机制可能更加有意义。

这里需要注意:

Durability 并不等于数据库。

它解决的是 DDS/ROS 2 通信层面的数据持久性策略,而不是把数据永久存储到磁盘。


三、History、Depth:为什么ROS 2消息队列不能无限增长?

再来看 History。

机器人系统里面有一个非常典型的问题:

如果消息产生得比处理得快,会发生什么?

例如:

Publisher:1000 Hz

Subscriber处理速度:500 Hz

那么:

产生:
1000条/s

处理:
500条/s

每秒就会多出约 500 条待处理数据。

如果无限保存:

Queue
│
├── Message 1
├── Message 2
├── Message 3
├── ...
├── Message 10000
├── Message 10001
└── Message 10002

最终系统的内存和处理压力都会不断增加。

因此 QoS 中需要定义:

历史数据到底保存多少?

这就是 History 和 Depth 的意义。


1. KEEP_LAST

KEEP_LAST 是机器人系统里非常常见的一种策略。

例如:

Depth = 10

可以简单理解为:

最多保留最近的 10 条消息。

如果:

Message 1
Message 2
...
Message 10

队列已经满了。

此时来了:

Message 11

那么旧的数据可能被淘汰:

Message 2
Message 3
...
Message 11

这样系统始终保持一个有限长度的消息队列。

对于实时机器人系统而言,这种设计非常重要。

因为很多机器人控制数据具有明显的“新鲜度”。

例如:

cmd_vel

0.0 m/s
0.1 m/s
0.2 m/s
0.3 m/s
0.4 m/s

假设控制器已经处理到了:

0.4 m/s

这时候再去处理几百毫秒之前的:

0.1 m/s

很多情况下并没有意义。

因此,对于部分实时控制数据:

最新数据可能比完整历史数据更重要。


2. KEEP_ALL

KEEP_ALL 则意味着:

尽可能保留所有历史消息。

听起来很安全,但代价也很明显。

如果生产速度长期高于消费速度:

Producer
1000 Hz

Consumer
500 Hz

↓

Queue不断增长

最终会造成:

  • 内存压力;

  • 调度压力;

  • 延迟增加;

  • 系统资源竞争。

因此,QoS 的设计本质上也是一种:

资源管理策略。


四、Deadline、Lifespan、Liveliness:QoS开始进入“实时系统”领域

前面讲的 Reliability、Durability、History 更多是在解决:

消息应该如何传递和保存。

而 Deadline、Lifespan、Liveliness 则开始涉及:

数据是不是按预期产生?

这时候,QoS 就开始与机器人实时系统产生更强的联系。


1. Deadline:数据多久没有更新,就算异常?

假设 IMU 应该:

100 Hz

那么理论上:

每10ms产生一次数据

正常情况:

T0
  ↓ 10ms
T1
  ↓ 10ms
T2
  ↓ 10ms
T3

如果突然变成:

T0
  ↓ 10ms
T1
  ↓ 10ms
T2
  ↓ 100ms
T3

那么系统就应该意识到:

IMU 数据是不是出现异常?

Deadline 就可以用于描述这种通信周期要求。

例如:

Deadline = 10ms

意味着:

数据发布之间不应该超过这个预期时间。

如果超过,就可能触发对应的 QoS 事件机制,让系统知道:

数据更新超时

这对于机器人系统很有价值。

比如:

IMU
   │
   ▼
状态估计
   │
   ▼
控制器

如果 IMU 长时间没有数据:

IMU ─────X──────> 状态估计

控制系统就不能继续假设:

“数据一定正常。”

它需要进入异常处理逻辑。

所以 Deadline 可以帮助系统回答:

数据有没有按照预期的节奏出现?


2. Lifespan:消息过期之后还有没有意义?

Lifespan 解决的是另一个问题:

一条消息最多允许“活”多久?

例如:

cmd_vel

假设一条速度控制指令:

前进 0.5 m/s

发布之后经过:

1ms

可能仍然有效。

但如果因为系统阻塞:

500ms

之后才真正处理这条消息。

那么这个控制命令可能已经过时。

因此对于一些具有明显时间敏感性的数据:

Message
   │
   ├── 10ms:有效
   ├── 20ms:可能有效
   ├── 50ms:开始过期
   └── 500ms:基本失去实时意义

Lifespan 可以帮助定义:

超过生命周期之后,这条消息不再应该被认为是有效数据。

这与实时控制系统的设计非常相关。

因为实时系统不是简单追求:

“每条数据都不能丢。”

而是需要考虑:

数据到了的时候,它是不是还具有使用价值?


3. Liveliness:对方还活着吗?

机器人系统还有一个经常被忽视的问题:

一个节点到底还活着吗?

例如:

Controller Node
      │
      ▼
Motor Driver

如果 Controller Node 崩溃了:

Controller
    X

下游系统是否能够及时发现?

Liveliness 可以用于描述通信参与者的“存活状态”。

这类机制对于:

  • 故障检测;

  • 节点健康状态;

  • 分布式机器人系统;

  • 安全机制;

都有一定意义。

但同样要强调:

Liveliness 是通信层面的存活状态,不等于整个机器人控制链路的安全认证。

真正的机器人安全系统还需要结合:

  • 看门狗;

  • 故障检测;

  • 安全状态机;

  • 硬件保护;

  • 控制器保护;

  • 独立安全机制。


五、QoS配置好了,ROS 2就实时了吗?真正的关键还在Executor和操作系统

到这里,我们已经可以把 ROS 2 QoS 的核心逻辑串起来。

可以把一个机器人数据链路抽象成:

                    ROS 2
                      │
          ┌───────────┴───────────┐
          │                       │
     Publisher              Subscriber
          │                       │
          ▼                       ▲
        QoS                      QoS
          │                       │
          └────────── DDS ────────┘
                      │
                      ▼
                  数据传输
                      │
                      ▼
                  Executor
                      │
                      ▼
                  Callback
                      │
                      ▼
              Linux Scheduler
                      │
                      ▼
                     CPU

这里最容易产生一个错误认识:

QoS配置得越严格,系统就越实时。

实际上,这是不成立的。

例如:

Reliable
+
Deadline
+
Keep Last

并不能自动保证:

Callback一定在1ms内执行

为什么?

因为 QoS 主要解决的是:

数据通信层的问题。

而 Callback 到底什么时候获得 CPU,需要经过:

Executor + Thread + Linux Scheduler + CPU。

假设:

T0:消息到达
T1:DDS收到消息
T2:Executor发现可执行Callback
T3:Callback进入等待执行状态
T4:Linux调度器选择线程
T5:线程真正获得CPU
T6:Callback开始执行

那么:

实时响应延迟
≈
通信延迟
+
Executor调度延迟
+
线程调度延迟
+
CPU资源竞争
+
Callback执行时间

即使:

通信延迟 = 0.1ms

如果后面发生:

Executor排队
+
高优先级任务抢占
+
CPU竞争
+
中断干扰
+
锁等待

最终:

Callback实际执行时间

仍然可能远远超过预期。

因此:

QoS ≠ 实时性。

更准确地说:

QoS 是 ROS 2 实时系统设计的重要组成部分,但它无法单独决定整个系统的实时确定性。

这也是为什么当 ROS 2 开始进入:

  • 机械臂控制;

  • 人形机器人运动控制;

  • 移动机器人底盘控制;

  • 无人系统;

  • 工业机器人;

  • 高速运动控制;

这些场景之后,开发者最终都会逐渐从:

“ROS 2通信是否正常?”

继续深入到:

“Callback到底什么时候执行?”

再继续深入到:

“Linux线程什么时候获得CPU?”

最后进入:

“操作系统能不能提供确定性的调度和资源隔离?”

这就从 ROS 2 的通信问题,进入了实时操作系统问题。


一个典型的机器人控制链路

假设一台机械臂控制周期为:

1ms

那么整个链路可能是:

        关节编码器
             │
             ▼
        Joint State
             │
             ▼
          ROS 2
             │
             ▼
        Controller
             │
             ▼
       read-update-write
             │
             ▼
        Motor Driver

理想状态下:

1ms
│
├── 数据采集
├── 数据传输
├── Callback触发
├── 控制算法
├── 输出控制命令
└── 周期结束

如果其中任何一个环节出现不可控延迟:

通信延迟
Executor延迟
线程调度延迟
锁竞争
中断
CPU竞争

就可能变成:

周期1:0.8ms
周期2:0.9ms
周期3:1.1ms
周期4:0.8ms
周期5:2.4ms

平均值看起来可能并不严重。

例如:

平均延迟:0.9ms

但是对于实时控制来说,更值得关注的是:

Worst Case Latency

也就是:

最坏情况下到底会延迟多少?

这也是实时系统和普通软件系统思维方式上的重要区别。

普通应用可能更加关注:

平均性能
吞吐量
平均响应时间

而实时控制系统通常更加关注:

最大延迟
抖动
周期确定性
任务优先级
资源隔离

因此,当 ROS 2 应用进入高实时性场景之后,仅仅优化 QoS 往往是不够的。

还需要继续分析:

ROS 2通信
      ↓
DDS
      ↓
Executor
      ↓
Callback Group
      ↓
线程
      ↓
Linux Scheduler
      ↓
CPU
      ↓
中断 / IRQ
      ↓
硬件

这条链路上的任何一个环节,都可能成为实时性的瓶颈。


QoS应该怎么选?不要追求“最强配置”,而应该根据数据类型设计

实际项目中,并不存在一个:

“所有 Topic 都应该使用的最佳 QoS。”

不同的数据应该使用不同策略。

例如可以进行这样的思考:

数据类型ReliabilityHistory典型关注点
摄像头图像Best Effort常见Keep Last最新数据
LiDARBest Effort常见Keep Last数据新鲜度
IMU视场景选择Keep Last周期性、延迟
Joint State视场景选择Keep Last最新状态
控制命令根据系统设计Keep Last实时性、过期数据
地图Reliable常见视场景选择数据完整性
配置数据Reliable常见视场景选择新节点获取状态
状态/诊断根据需求Keep Last异常检测

这里不能简单理解成:

某种数据永远必须采用某种 QoS。

真正合理的方法应该是:

从业务语义出发设计 QoS。

例如:

高频传感器数据

重点可能是:

新鲜
快速
允许一定丢包
不要积压大量历史数据

那么:

Best Effort
+
Keep Last
+
合理Depth

可能更加符合需求。

配置/状态数据

重点可能是:

可靠
新加入节点可以获取状态

那么:

Reliable
+
Transient Local

可能更加合适。

控制数据

重点则可能变成:

低延迟
数据新鲜
避免历史命令堆积
异常时及时发现

这时就需要进一步结合:

QoS
+
Executor
+
线程优先级
+
CPU隔离
+
实时调度
+
控制周期

一起设计。

这才是真正的机器人实时系统架构。


结语:QoS解决的是“数据怎么传”,实时操作系统解决的是“任务什么时候跑”

如果只记住本文几个最重要的结论,可以把它浓缩成下面这张图:

                    ROS 2
                      │
                      ▼
              ┌──────────────┐
              │      QoS     │
              └──────────────┘
                      │
          ┌───────────┼───────────┐
          ▼           ▼           ▼
     Reliability   Durability   History
          │           │           │
          └───────────┼───────────┘
                      │
                      ▼
                    DDS
                      │
                      ▼
                  消息到达
                      │
                      ▼
                 Executor
                      │
                      ▼
                  Callback
                      │
                      ▼
              Linux任务调度
                      │
                      ▼
                     CPU

QoS解决的问题是:

数据应该以什么方式传递、保存和管理。

而实时操作系统解决的问题则更加底层:

任务什么时候执行、执行多久、能不能及时抢占、资源会不会互相干扰。

所以:

Reliable ≠ 实时

Deadline ≠ 实时

QoS优化 ≠ 操作系统实时化

真正的 ROS 2 实时系统,需要把通信、执行器、线程、调度器和 CPU 资源放在一起考虑。

对于普通机器人应用来说,ROS 2 + Linux 已经能够满足大量开发需求。

但当系统进入:

  • 高速机械臂控制;

  • 工业机器人;

  • 人形机器人运动控制;

  • 高实时底盘控制;

  • 多轴同步控制;

  • 高可靠嵌入式设备;

这类对低延迟、低抖动、确定性和资源隔离要求更高的场景时,开发者需要进一步关注底层实时操作系统。

对于这类系统,望获rtLinux可以作为底层实时操作系统的一种技术选择,将 ROS 2 上层的软件通信与底层实时调度、资源隔离能力结合起来。

但真正值得继续研究的问题已经不是:

“QoS参数怎么配置?”

而是:

一条 ROS 2 消息到达之后,到底是谁决定它什么时候执行?

这就要进入 ROS 2 的另一个核心机制:

Executor。

下一篇我们继续深入:

《ROS 2 Executor到底是什么?从回调函数到机器人任务调度机制》

从一个简单的 Callback 开始,一路分析到:

Node
 ↓
Callback
 ↓
Callback Group
 ↓
Executor
 ↓
Thread
 ↓
Linux Scheduler
 ↓
CPU

也正是在这里,ROS 2 的“软件框架”和“实时操作系统”真正开始发生深度交汇。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐