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

通信原语:Topic、Service、Action 的代价边界

从背压、失败可见性与可取消性选原语;演示能通的选择,产品期会卡死。

通信原语:Topic、Service、Action 的代价边界

1. 名词都会,语义仍用错

配置用 latched topic「谁最后发谁赢」、长任务忙等 service、控制环里同步 call——演示能通,产品期变成卡死与不可观测。选原语前先问三句:失败是否对调用方可见?是否可取消?背压打在谁身上?答不清就先别开接口评审。

底盘曾用 service 做模式切换:实验室快,导航与遥控并发后切换卡在服务队列,轮速环出毛刺。改成短 topic 设定点 + 带状态反馈后,毛刺消失。问题不在「service 坏」,而在把同步阻塞放进了硬实时路径。

2. Topic:扇出便宜,不保证处理完

发布-订阅适合传感器流、高频设定点、状态广播。扇出成本低,但不保证对端算完——压力表现为丢样或延迟,不是抛异常给发布者。

cpp
cmd_pub_ = create_publisher<geometry_msgs::msg::Twist>("cmd_vel", 10);

调用方看不到「对方收到没有」,必须用看门狗或状态话题补闭环。把配置当 latched topic 时,要写清仲裁:谁有权最后写入,冲突如何可见。三个节点各发一次「最大速度」,谁赢全靠启动顺序,是配置灾难。

传感器侧常用 SensorDataQoS()(Best Effort + KeepLast 小 depth);订阅者若用 Reliable,可能根本收不到——这是 QoS 合约问题,不是 topic 「坏了」。

3. Service:短、幂等、可超时

适合「问一次得一次答案」:查参数快照、触发单次标定、切换布尔模式。同步 call 在 100 Hz 环里死等,等于把对端抖动写进力矩。

cpp
auto future = client_->async_send_request(request);
if (future.wait_for(200ms) != std::future_status::ready) {
  RCLCPP_WARN(get_logger(), "service timeout");
  return;
}

超时后的重试必须幂等——「再调一次启动扫描」可能开两个冲突任务。需要进度与取消的长任务,别硬塞 service。

4. Action:长任务的标准契约

导航、抓取、长标定、轨迹执行——需要 goal、feedback、cancel 语义,用 Action:

bash
ros2 action send_goal /follow_path nav2_msgs/action/FollowPath "{...}"

用 topic 模拟 action(发 goal topic、听 result topic、另发 cancel topic),最后仍要自建状态机,还少了标准取消语义。Action 服务器的 cancel 回调必须落到执行器安全态,否则面板上显示已取消,轮子还在转。

5. 画同步边界:跨进程要显式失败

跨进程、跨机、跨责任边界:用 service/action 把失败显式化(超时、拒绝、abort)。边界内高频交换:用 topic 流式推。同一能力不要同时提供「阻塞 service」和「无超时 topic」两套入口,除非写清优先级——双入口是现场「到底听谁的」的根源。

lifecycle 与原语正交:节点 inactive 时,service 应拒绝或 unavailable;action 应 abort 在途目标。把「图还在」当成「契约还在」会误导上层重试风暴。

6. 节点图与 executor 的隐性代价

单节点内 topic 回调共享 executor;一个回调阻塞,同节点其他订阅也饿死。service 回调里做巨型计算,会拖死同进程 topic——这不是原语问题,是调度问题,但选型时要把「谁同进程」算进去。

Composable 容器里多组件共享进程:topic 快、service 慢可以共存,但要在架构上隔离硬实时组件(独立 executor 或独立进程)。

7. 迁移与故障注入验收

注入对端崩溃:topic 侧看门狗应响;service 应超时/失败返回;action 应 aborted/canceled 且执行器停。迁移期:长任务先加超时与取消,再迁 Action——不要同一周既改原语又改算法。

  • 新接口设计文档写明:原语选择、超时、幂等、取消语义。
  • CI 冒烟:关键 service 带超时调用;action cancel 后执行器归零。
  • 配置类能力禁止无仲裁的多 publisher topic。

流用 topic,短契约用 service,长且可取消用 action。例外写进设计说明,而不是口口相传。原语选错的成本,在并发与故障注入时才爆炸——演示路径永远偏乐观。

8. 案例:导航目标的三条错误路径

曾见过同一「去目标点」能力提供:同步 service(阻塞 30 s)、无 ACK 的 topic goal、以及后来才加的 Action。现场三套客户端并存,取消语义互不兼容,录包显示 goal 已 cancel 但 base 仍在滑。收敛到单一 Action 后,故障单数量降了一个量级——不是 Action 魔法,是契约统一。

← 全部文章

johan's blog