返回专辑
·Johan·4 分钟阅读

QoS 深读:兼容矩阵、持久性与数据类剖面

按数据类建剖面表;用 deadline 让沉默可观测;不兼容匹配常常是静默的。

QoS 深读:兼容矩阵、持久性与数据类剖面

1. QoS 是分布式合约,不是预设别名

发布者 Reliable + Volatile + KeepLast(10),订阅者 Best Effort + Transient Local + KeepLast(1)——图里「有话题、无回调」,ros2 topic hz 永远 zero,日志无弹窗。DDS 兼容规则:reliability 必须一致;durability 订阅不能低于发布;部分 history 组合非法。不匹配常常是静默的,这是联调地狱之源。

只记 SensorDataQoSSystemDefaultsQoS 两套别名不够;团队要为每类数据写剖面行,MR 填行号,否则 QoS 会按话题数平方腐烂。

2. 五个旋钮各自解决什么

  • Reliability:传感流常用 Best Effort(丢旧帧换低延迟);命令/地图用 Reliable
  • DurabilityTransient Local 让晚订阅者拿到发布者最近样本(静态 TF、地图、参数事件);Volatile 只收订阅后的新样本。
  • History + depthKeepLast(N) 控制缓存深度;传感几乎总是小 N,大 N 常意味着延迟堆积而非更安全。
  • Deadline:周期内无新样本则触发回调——把「话题还在、数据已死」变成可观测事件,比人工盯 hz 适合上车。
  • Lifespan:样本超过 TTL 不再投递,防止把 stale 数据当新数据。

rclcpp::SensorDataQoS() 是便捷入口,不等于团队剖面——它仍是 BE + Volatile + KeepLast(5) 的打包,改 depth 仍要显式链式调用。自定义剖面用具名工厂函数,禁止每个节点 copy-paste 五行。

cpp
rclcpp::QoS qos(1);
qos.reliability(RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT);
qos.durability(RMW_QOS_POLICY_DURABILITY_VOLATILE);
qos.deadline(std::chrono::milliseconds(100));
pub_ = create_publisher<sensor_msgs::msg::LaserScan>("scan", qos);

3. 读兼容矩阵,不要背口诀

验收时用 verbose 看双方合约:

bash
ros2 topic info /scan -v

输出里 Publisher count、Subscription count 同为 1 但 hz 为零,几乎总是 QoS 或类型不匹配。故意改订阅为 Reliable 复现一次静默失败,全组见过就不至于每次从零猜。

常见合法组合:激光 BE + Volatile + KeepLast(5);地图 Reliable + Transient Local + KeepLast(1);里程计 Reliable + Volatile + KeepLast(10) + deadline 500ms。

4. 剖面表:教程会被忘,门禁不会

团队维护 qos_profiles.yaml 或表格,列至少包括:

行号数据类reliabilitydurabilityhistorydepthdeadline示例话题
P1激光流BEVolatileKeepLast5100ms/scan
P2静态 TFReliableTransient LocalKeepLast1/tf_static
P3占用栅格ReliableTransient LocalKeepLast1/map

MR 模板:「本次 QoS 变更对应 P?_」。没有行号的不合并。代码里引用具名常量,禁止 magic number 散落:

cpp
inline rclcpp::QoS kProfileLidar() { /* P1 */ }

5. 录包、桥接与降级

ros2 bag record 默认 QoS 可能与 live 图不同——回放「有影无回调」时先 ros2 bag info 看存储 QoS。ROS 1 桥接、zenoh 桥 frequently 只支持 reliability 子集,迁移时按话题清单逐条验证,不能假设「能 echo 就能算法」。

上车允许对非安全话题降级(BE、减 depth),但必须在剖面表标注「录包模式」行,与 live 行成对,避免调试与现场行为分裂。回放时用 --qos-profile-overrides-path 显式对齐 live 剖面,而不是依赖 bag 内嵌的旧合约。

6. Deadline 与诊断联动

订阅端注册 deadline 回调:

cpp
sub_->set_on_requested_deadline_callback(
  [](rclcpp::QOSRequestedDeadlineInfo & info) {
    RCLCPP_WARN(rclcpp::get_logger("qos"), "missed deadline, total %zu",
                info.total_count);
  });

配合 diagnostic_updater 上报,HMI 可显示「激光超时」而非神秘停走。注意:deadline 过紧在负载高时误报,应基于 P99 间隔 + 余量标定。发布端也可设 deadline:双方都有 periodic 合约时,违反能定位是发布慢还是订阅处理慢。

7. 迁移与验收

现有仓库按话题清单填剖面行号,一次改一类(先所有激光,再所有地图)。完成标志:任意故障单可贴出匹配对的 Offered/Requested;关键 topic 在负载测试下 deadline 零误报或误报可解释。

新节点 default 构造函数里不要写私有 QoS——从剖面表 #include 工厂。Code review 看到裸 create_subscription(..., 10) 即问对应哪一行。老节点迁移时先加 -v 快照进 wiki,再改代码,避免「改完不知道以前长什么样」。

8. 案例:Transient Local 救晚订阅

Nav2 起得比 map_server 晚,没有 TL 时 /map 永远空。发布改 Transient Local + KeepLast(1) 后晚订阅立即拿到最后一帧。但若激光也误设 TL,带宽与内存徒增——按数据类选,不是越持久越好

先分类,再选预设;用 deadline 管理沉默;禁止私有 QoS 传说。兼容矩阵读一次,胜过十篇教程背诵。

← 全部文章

johan's blog