
在上一篇文章中我们详细讨论了 ROS 2 中 Topic、Service 和 Action 三种通信机制。很多人在第一次接触 ROS 2 时会发现一个非常特别的概念QoSQuality of Service服务质量。在传统的软件开发中我们发送一条消息通常只需要关心“消息有没有发出去”和“对方有没有收到”。但机器人系统并不是一个普通的软件通信系统。一台移动机器人上的摄像头可能每秒产生几十帧图像激光雷达可能持续输出扫描数据IMU 可能以数百 Hz 甚至更高频率输出数据而关节控制系统则可能要求严格按照固定周期运行。这些数据对于系统而言并不具有完全相同的重要性。例如一帧已经过时的摄像头图像还要不要丢失一帧激光雷达数据是否可以接受关节状态数据应该保留多少条一个控制命令如果延迟 100 ms 才到达还有没有意义一个新启动的节点是否需要拿到之前已经发布的数据消息发送失败后是应该重传还是直接丢弃一个周期性数据如果长时间没有到达系统应该怎么办这些问题实际上都属于通信服务质量问题。这也是 ROS 2 与 ROS 1 在设计理念上的一个重要区别。ROS 2 底层通信建立在 DDSData 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 最简单的方法是先假设我们正在设计一台移动机器人。机器人上有摄像头激光雷达IMUGPS轮速计电机控制器关节状态路径规划模块定位模块控制模块。这些模块之间都需要通信。例如摄像头 │ ▼ 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、HistoryQoS最核心的三个概念ROS 2 QoS 有很多策略但如果刚开始学习最值得优先理解的是Reliability、Durability、History。它们分别回答三个不同的问题Reliability消息要不要尽可能可靠地送达 Durability新加入的节点要不要看到过去的数据 History过去的数据到底保存多少1. Reliability数据到底要不要“可靠送达”Reliability 主要有两种常见策略BEST_EFFORT RELIABLEBEST_EFFORTBest Effort 可以简单理解为能发就发丢了就丢了。它不强调一定要把每一条数据送到。例如Publisher Frame 1 ───────────── Subscriber Frame 2 ───────────── Subscriber Frame 3 ─────X Frame 4 ───────────── Subscriber Frame 5 ───────────── SubscriberFrame 3 丢失。Subscriber 不会为了 Frame 3 一直等待也不会要求发送方重新发送。这种机制非常适合某些高频传感器数据。比如摄像头30 FPS Frame 1 Frame 2 Frame 3 Frame 4 Frame 5 ...如果偶尔丢一帧后面的新帧很快就会到达。相比于为了保证 Frame 3一直等待重传。很多情况下系统更希望Frame 3 不要了直接处理 Frame 4。这就是 Best Effort 的典型使用场景。RELIABLEReliable 则更加关注消息应该可靠地交付。如果发生丢包底层通信机制可能采取确认、重传等机制来保证可靠传输。可以简单理解为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_LOCALVOLATILEVolatile 可以理解为我只关心现在的数据过去的数据不负责保存给后来者。例如Publisher │ ├── Data 1 ├── Data 2 ├── Data 3 │ ▼ Subscriber后来才启动Subscriber 通常不会因为自己晚启动就自动获得之前的数据。这种模式非常适合持续变化的数据。例如IMU Camera LiDAR因为这些数据本身就是连续产生的。TRANSIENT_LOCALTransient Local 则可以理解为Publisher 在本地保留一定的数据状态让后来加入的 Subscriber 有机会获得。例如Map Publisher 发布 Map │ ▼ 保存状态 │ ▼ Navigation Node启动 │ ▼ 获得之前的Map对于地图、配置、状态类数据这种机制可能更加有意义。这里需要注意Durability 并不等于数据库。它解决的是 DDS/ROS 2 通信层面的数据持久性策略而不是把数据永久存储到磁盘。三、History、Depth为什么ROS 2消息队列不能无限增长再来看 History。机器人系统里面有一个非常典型的问题如果消息产生得比处理得快会发生什么例如Publisher1000 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_LASTKEEP_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_ALLKEEP_ALL 则意味着尽可能保留所有历史消息。听起来很安全但代价也很明显。如果生产速度长期高于消费速度Producer 1000 Hz Consumer 500 Hz ↓ Queue不断增长最终会造成内存压力调度压力延迟增加系统资源竞争。因此QoS 的设计本质上也是一种资源管理策略。四、Deadline、Lifespan、LivelinessQoS开始进入“实时系统”领域前面讲的 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消息到达 T1DDS收到消息 T2Executor发现可执行Callback T3Callback进入等待执行状态 T4Linux调度器选择线程 T5线程真正获得CPU T6Callback开始执行那么实时响应延迟 ≈ 通信延迟 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竞争就可能变成周期10.8ms 周期20.9ms 周期31.1ms 周期40.8ms 周期52.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任务调度 │ ▼ CPUQoS解决的问题是数据应该以什么方式传递、保存和管理。而实时操作系统解决的问题则更加底层任务什么时候执行、执行多久、能不能及时抢占、资源会不会互相干扰。所以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 的“软件框架”和“实时操作系统”真正开始发生深度交汇。