ROS 2 Humble Nav2 生命周期教程:启动流程、状态监控、超时诊断与自愈设计

适用版本:ROS 2 Humble、Nav2 1.x。其他发行版的总体思路相同,但参数和源码实现可能不同。

适合读者:已经能启动 Nav2,但对 configureactivateget_statechange_statebondrespawn 以及偶发启动失败还不够清楚的开发者。


1. 学完本文能解决什么问题?

阅读本文后,你应该能够回答:

  1. Nav2 为什么要先 configure,再 activate
  2. Lifecycle Manager 与各个 Nav2 节点之间谁是客户端、谁是服务端?
  3. get_statechange_state 分别做什么?
  4. autostartuse_respawnbond 有什么区别?
  5. 为什么节点已经切换成功,管理器仍可能认为启动失败?
  6. Service 使用 RELIABLE,为什么仍会出现 response timeout?
  7. 遇到生命周期启动失败时,如何判断真正失败的环节?
  8. 如何设计比"单次失败就中止"更可靠的状态对账和有限重试?

本文先建立通用知识,再给出标准排障步骤,最后用一次真实测试验证结论。


2. 先分清三层:进程、生命周期、系统编排

很多误解来自把下面三层混在一起。

2.1 进程层

负责创建和销毁 Linux 进程,例如:

text 复制代码
ros2 launch
systemd
supervisor
Docker/Kubernetes
自研节点管理器

进程崩溃后是否重新创建,属于这一层。

2.2 生命周期层

负责一个已经存在的节点当前处于什么业务状态:

text 复制代码
unconfigured
inactive
active
finalized

节点进程存在,不代表它已经能够导航。例如 controller_server 进程可能存在,但仍停留在 unconfigured

2.3 系统编排层

负责:

  • 按依赖顺序启动多个节点;
  • 判断整套系统是否健康;
  • 超时后重试、清理或重启;
  • 统一提供 starting/running/failed 状态。

Nav2 的 lifecycle_manager 属于生命周期编排器,但它不是完整的进程守护系统。

text 复制代码
外层进程管理器 / ros2 launch
          │ 创建进程、进程退出后决定是否重启
          ▼
Nav2 lifecycle_manager
          │ configure / activate / deactivate / cleanup
          ▼
controller、planner、bt_navigator 等 Lifecycle Node

3. ROS 2 Lifecycle 状态机

Lifecycle Node 的常见主状态如下:

状态 ID 含义
unconfigured 1 节点进程存在,但业务资源尚未配置
inactive 2 配置完成,但尚未正式工作
active 3 已激活,可以参与导航
finalized 4 生命周期已结束

最常见的启动路径:

text 复制代码
unconfigured
    │ configure
    ▼
configuring
    │ on_configure() 成功
    ▼
inactive
    │ activate
    ▼
activating
    │ on_activate() 成功
    ▼
active

停止和清理路径:

text 复制代码
active
  │ deactivate
  ▼
inactive
  │ cleanup
  ▼
unconfigured

3.1 configure 通常做什么?

不同节点实现不同,常见工作包括:

  • 读取和校验参数;
  • 加载控制器、规划器、行为树等插件;
  • 创建 costmap、TF buffer、订阅者和发布者;
  • 申请内存和其他资源;
  • 检查依赖资源。

configure 成功后进入 inactive。此时资源基本准备好,但节点还没有正式执行导航业务。

3.2 activate 通常做什么?

常见工作包括:

  • 激活 Lifecycle Publisher;
  • 启动定时器或业务循环;
  • 开始接受 Action 请求;
  • 建立与生命周期管理器的 bond。

只有进入 active,节点才算真正可用。


Nav2 官方 Humble 导航栈通常包含:

text 复制代码
controller_server
smoother_server
planner_server
behavior_server
bt_navigator
waypoint_follower
velocity_smoother

如果没有启动 velocity_smoother,受管节点可能只有前 6 个。

Lifecycle Manager 会执行两轮串行操作。

第一轮:依次 configure

text 复制代码
controller_server    → inactive
smoother_server      → inactive
planner_server       → inactive
behavior_server      → inactive
bt_navigator         → inactive
waypoint_follower    → inactive

第二轮:依次 activate

text 复制代码
controller_server    → active
smoother_server      → active
planner_server       → active
behavior_server      → active
bt_navigator         → active
waypoint_follower    → active

任意一步失败,默认会停止当前 bringup,不再继续后面的节点。

这里的顺序由 Lifecycle Manager 的 node_names 参数决定,不是节点之间互相协商出来的。


5. 客户端和服务端如何判断?

每个 Lifecycle Node 都有两个关键 Service:

text 复制代码
/<node_name>/get_state
/<node_name>/change_state

调用它们时:

text 复制代码
lifecycle_manager:客户端
Lifecycle Node:服务端

例如:

text 复制代码
lifecycle_manager
    │ request: configure
    ▼
/controller_server/change_state
    │
    │ controller_server 执行 on_configure()
    │
    └── response ──→ lifecycle_manager

但调用下面这个 Service 时:

text 复制代码
/lifecycle_manager_navigation/manage_nodes

角色会变成:

text 复制代码
RViz、UI或外部程序:客户端
lifecycle_manager:服务端

所以"谁是客户端"必须结合当前 Service 判断。


6. get_statechange_state

6.1 get_state:只读查询

bash 复制代码
ros2 lifecycle get /controller_server

可能输出:

text 复制代码
unconfigured [1]
inactive [2]
active [3]

get_state 没有业务副作用,因此适合有限重试。

6.2 change_state:有副作用的状态迁移

bash 复制代码
ros2 lifecycle set /controller_server configure
ros2 lifecycle set /controller_server activate
ros2 lifecycle set /controller_server deactivate
ros2 lifecycle set /controller_server cleanup

change_state 会触发节点执行实际回调,例如 on_configure()on_activate(),因此不能在结果未知时无脑重复发送。

Nav2 Humble 的核心逻辑可以简化为:

text 复制代码
发送一次 change_state
        ↓
等待 response
        ↓
调用一次 get_state 验证目标状态
        ↓
成功后处理下一个节点

等待 Service 出现可能有循环,但针对一次迁移,请求本身不是默认连续发送三次。


7. 常用检查命令

7.1 列出 Lifecycle Node

bash 复制代码
ros2 lifecycle nodes

7.2 查询节点状态

bash 复制代码
ros2 lifecycle get /controller_server
ros2 lifecycle get /planner_server
ros2 lifecycle get /bt_navigator

批量检查示例:

bash 复制代码
for node in \
  controller_server \
  smoother_server \
  planner_server \
  behavior_server \
  bt_navigator \
  waypoint_follower
do
  printf '%s: ' "$node"
  ros2 lifecycle get "/$node"
done

7.3 查看可用迁移

bash 复制代码
ros2 lifecycle list /controller_server

7.4 查看生命周期 Service

bash 复制代码
ros2 service list -t | grep -E 'get_state|change_state|manage_nodes'

ManageLifecycleNodes 命令值:

text 复制代码
STARTUP  = 0
PAUSE    = 1
RESUME   = 2
RESET    = 3
SHUTDOWN = 4

启动整组:

bash 复制代码
ros2 service call \
  /lifecycle_manager_navigation/manage_nodes \
  nav2_msgs/srv/ManageLifecycleNodes \
  '{command: 0}'

调试单节点时可以使用 ros2 lifecycle set;正常启动整套 Nav2 时优先通过 Lifecycle Manager,避免绕过编排顺序。


8. 如何持续观察真实状态迁移?

只看 get_state 是状态快照,可能错过中间过程。Lifecycle Node 还会发布迁移事件:

bash 复制代码
ros2 topic echo \
  /controller_server/transition_event \
  lifecycle_msgs/msg/TransitionEvent

重点观察:

text 复制代码
start_state.label
transition.label
goal_state.label

正常 configure:

text 复制代码
unconfigured → configuring
configuring  → inactive

正常 activate:

text 复制代码
inactive   → activating
activating → active

排查偶发故障时,应在重启前就开始订阅,而不是等错误发生后才订阅。


9. 两类超时必须分开

9.1 服务端发送 response 超时

典型日志:

text 复制代码
failed to send response to /controller_server/change_state (timeout):
client will not receive response

含义是:

text 复制代码
服务端已经收到request
→ 回调可能已经执行
→ 准备发送response
→ DDS发送/端点匹配阶段超时
→ 客户端不会收到这次response

这条日志本身不能证明生命周期回调失败。

9.2 客户端等待 response 超时

典型日志:

text 复制代码
service client: async_send_request failed

含义是客户端发出请求后,等待 future 完成失败或超过调用期限。

两者可能形成因果链:

text 复制代码
服务端response发送失败
        ↓
客户端一直收不到response
        ↓
客户端等待超时

10. Service 默认是 Reliable,不是 Best Effort

ROS 2 Service 默认 QoS:

text 复制代码
History:     KEEP_LAST
Depth:       10
Reliability: RELIABLE
Durability:  VOLATILE

Lifecycle 的 /get_state/change_state 使用 Service 默认 QoS,因此通常也是 RELIABLE

Reliable 不等于调用绝对成功

Reliable 主要保证:在 Writer 和 Reader 已经正确匹配、资源可用且实体仍然存在时,对历史样本进行确认和必要的重传。

它不保证:

  • DDS 端点一定及时完成发现和匹配;
  • 写入队列永远有资源;
  • 对端在写入时仍然存在;
  • 写调用永远不返回超时;
  • Service 具备事务级"恰好一次"语义。

所以出现 response timeout,不等于 QoS 被改成了 Best Effort。


11. Fast DDS 的 max_blocking_time

Fast DDS 的 Reliable DataWriter 有一个 max_blocking_time,常见默认值约为:

text 复制代码
100 ms

它限制的不是:

text 复制代码
on_configure()最多执行100ms
客户端最多等待response 100ms

而是:

服务端回调完成后,当前 response 写入 DDS 时最多允许阻塞多久。

rmw_fastrtps 的 Service response 路径中,服务端会根据请求携带的 GUID,确认 response writer 是否已经与对应客户端 response reader 匹配。

text 复制代码
客户端发送request
        ↓
节点执行Lifecycle回调
        ↓
节点状态切换成功
        ↓
服务端准备发送response
        ↓
等待对应response reader匹配
        ↓
超过max_blocking_time
        ↓
send_response返回timeout

因此:

text 复制代码
configure执行2秒,response立即写入成功
→ 正常

configure执行10ms,response写入等待超过100ms
→ response发送失败

准确说法是"response 没有成功写入 DDS",不是"已经发送的 Reliable 数据包超过100ms后被删除"。

为什么需要这个上限?

如果客户端已经退出、Reader一直无法匹配或者写队列长期堵塞,没有上限就可能让服务端 executor 线程永久卡在 send_response()

这会影响其他 Service、订阅回调、定时器甚至控制循环。因此 Fast DDS 选择在超过上限后返回错误,避免应用线程无限阻塞。

对于低频但关键的 Lifecycle Service,适当延长该时间可能降低概率,但这属于缓解措施,不能代替上层状态对账。


12. response丢失后,如何判断节点到底成功没有?

遇到:

text 复制代码
failed to send response to /xxx/change_state (timeout)

应立即查询:

bash 复制代码
ros2 lifecycle get /xxx

并结合此前持续订阅的:

text 复制代码
/xxx/transition_event

判断表:

观察结果 说明
已进入目标状态 回调成功,response传输失败
仍在原状态 回调可能未执行或迁移被拒绝
停在 configuring/activating 回调可能耗时过长或卡住
进入 errorprocessing 回调执行过程中发生真实错误
节点消失 进程退出、被清理或DDS尚未重新发现

为了减少单次查询本身偶发失败,可以短间隔查询多次:

bash 复制代码
for i in 1 2 3 4 5; do
  date '+%H:%M:%S.%3N'
  timeout 3 ros2 lifecycle get /controller_server
  sleep 0.3
done

13. 为什么 get_state 容易恢复,change_state 更麻烦?

13.1 get_state 是幂等只读操作

text 复制代码
第一次查询失败
→ 再查一次
→ 不会改变节点状态

因此健康检查器通常可以不断轮询,直到成功或耗尽总超时。

13.2 change_state 有副作用

假设发送 configure 后没有收到 response:

text 复制代码
情况A:节点没有执行,仍是unconfigured
情况B:节点已经执行成功,当前是inactive

直接重发 configure 时:

  • 情况A下是合理重试;
  • 情况B下会对 inactive 节点请求非法迁移。

因此应该先对账:

text 复制代码
change_state response异常
        ↓
多次get_state查询真实状态
        ↓
已经到目标状态 → 按成功处理
仍在原状态     → 有条件地重发
处于过渡状态   → 等待后复查
进入error       → cleanup/reset/restart

14. autostartbonduse_respawn

机制 负责什么 不负责什么
autostart 第一次启动后自动 configure、activate 不负责进程崩溃重启
bond 检测 active 后的节点失联 不创建新进程
use_respawn 子进程退出后由 launch重新创建 不处理进程仍活着时的Service超时

14.1 autostart

autostart=true 表示 Lifecycle Manager 启动后自动执行整组 startup。

如果为 false,节点进程仍会被 launch 创建,但生命周期通常停在 unconfigured,需要外部调用 manage_nodes

14.2 bond

节点成功激活后,会与 Lifecycle Manager 建立 bond 心跳:

text 复制代码
lifecycle_manager ← 心跳 → controller_server
                  ← 心跳 → planner_server
                  ← 心跳 → bt_navigator

某个节点失联超过 bond_timeout 后,管理器会认为系统不再完整,并采取降级或关闭动作。

查看 bond:

bash 复制代码
ros2 topic info /bond

查看管理器参数:

bash 复制代码
ros2 param get /lifecycle_manager_navigation bond_timeout
ros2 param get /lifecycle_manager_navigation attempt_respawn_reconnection
ros2 param get /lifecycle_manager_navigation bond_respawn_max_duration

14.3 use_respawn

节点在 launch 文件里通常类似:

python 复制代码
Node(
    package='nav2_controller',
    executable='controller_server',
    respawn=use_respawn,
    respawn_delay=2.0,
)

只有进程真正退出时才会触发 respawn。

下面这种情况不会触发:

text 复制代码
节点进程仍然存在
→ change_state已经执行
→ response没有送达

所以打开 use_respawn 不能解决 Lifecycle Service response timeout。


15. Lifecycle不是节点互相监测、互相拉起

Nav2 使用集中式管理:

text 复制代码
                 lifecycle_manager
                         │
       ┌─────────┬───────┼────────┬─────────┐
       ▼         ▼       ▼        ▼         ▼
 controller  smoother  planner  behavior  bt_navigator ...

这些节点不会互相创建进程。

一个完整的高可用系统通常需要组合:

text 复制代码
Lifecycle状态机
+ Lifecycle Manager编排
+ Bond失联检测
+ Launch respawn或外部进程守护
+ 状态对账与有限重试
+ 整体健康检查

Lifecycle主要提供状态一致性和安全顺序,不等于 systemd 或 Kubernetes 式的完整自愈系统。


16. 通用排障教程

下面这套步骤可以用于大多数 Nav2 Lifecycle 启动问题。

步骤1:记录运行环境

bash 复制代码
echo "$ROS_DISTRO"
echo "$RMW_IMPLEMENTATION"
echo "$ROS_DOMAIN_ID"
printenv FASTRTPS_DEFAULT_PROFILES_FILE
ros2 pkg prefix nav2_lifecycle_manager

步骤2:确认管理节点名单和参数

bash 复制代码
ros2 param get /lifecycle_manager_navigation node_names
ros2 param get /lifecycle_manager_navigation autostart
ros2 param get /lifecycle_manager_navigation bond_timeout

不要假设顺序,一定要读取运行时参数。

步骤3:重启前启动迁移事件监听

bash 复制代码
ros2 topic echo \
  /controller_server/transition_event \
  lifecycle_msgs/msg/TransitionEvent

如果问题随机发生,建议同时监听全部受管节点,并给输出加时间戳。

步骤4:保存服务端和管理器日志

重点搜索:

text 复制代码
failed to send response
async_send_request failed
Failed to change state
Failed to bring up
Aborting bringup

步骤5:错误发生后立即查询真实状态

bash 复制代码
ros2 lifecycle get /发生异常的节点

连续查询多次,区分真实状态与单次查询超时。

步骤6:确认是否继续处理下一个节点

例如 controller_server/change_state 超时后:

  • 如果没有出现 smoother_server: unconfigured → configuring,说明管理器卡在 controller;
  • 如果 smoother 已经开始配置,此警告可能来自另一个客户端或旧请求。

步骤7:区分进程故障和通信故障

bash 复制代码
ps -ef | grep controller_server
ros2 node list | grep controller_server
ros2 lifecycle get /controller_server
text 复制代码
进程存在 + 节点存在 + 状态正确
→ 更像Service response或管理器认知问题

进程不存在
→ 真实进程崩溃或被外层清理

步骤8:做可控A/B测试

按优先级可以测试:

  1. 降低同时启动的节点数量;
  2. 延长启动间隔;
  3. 暂停额外的高频 get_state 健康轮询;
  4. 比较 Fast DDS 与 Cyclone DDS;
  5. 调整 Fast DDS Writer 的阻塞时间;
  6. 检查 SHM、网卡白名单、发现服务器和残留进程。

每次只改变一个变量,否则无法判断真正影响因素。


17. 更可靠的状态对账设计

17.1 get_state策略

text 复制代码
单次超时不判死
→ 重试3~5次
→ 使用200ms、500ms、1s等退避间隔
→ 连续失败才判定节点不可达

17.2 change_state策略

text 复制代码
发送一次change_state
        ↓
正常收到response → get_state验证
        ↓
response异常 → 不立即重发
        ↓
多次get_state对账
实际状态 建议
已到目标状态 视为成功,继续后续节点
仍是原状态 在次数限制内重发一次
正在过渡 等待后复查
errorprocessing cleanup/reset或重启
节点消失 交给进程守护层处理

17.3 什么时候才清理整套Nav2?

建议在以下情况才清理整栈:

  • 对账和有限重试全部耗尽;
  • 节点进入不可恢复错误状态;
  • 关键进程退出且无法 respawn;
  • TF、地图、插件等前置依赖持续不可用;
  • 多个节点状态不一致且无法安全 reset。

不要因为一次只读查询超时就立即杀掉整栈。


18. 实测案例:22轮连续重启

下面保留一个真实案例,用于说明如何用证据判断,而不是把整篇教程写成个人日志。

18.1 测试结果

类型 轮数
完全正常 15
有超时警告但最终成功 5
Nav2整体启动失败 2

一次失败由 smoother_server/get_state 客户端异常触发;另一次发生在:

text 复制代码
/controller_server/change_state

18.2 关键时间线

text 复制代码
15:23:56.737  controller_server: unconfigured → configuring
15:23:57.604  controller_server: configuring → inactive
15:23:57.704  change_state response发送timeout

随后独立查询:

text 复制代码
controller_server: inactive [2]

结论:

text 复制代码
状态切换成功
→ response写入阶段超时
→ manager没有收到确认
→ 没有继续配置smoother_server
→ 外层120秒健康检查到期
→ 整套Nav2被清理

inactive 到 response timeout 约 100 ms,也与 Fast DDS 默认 max_blocking_time 的实现路径吻合。

这个案例说明:

failed to send response 不能直接等价为"生命周期回调失败"。必须结合 transition event 和实际状态判断。


19. 常见误区

误区1:进程存在就代表导航启动成功

错误。进程可能仍是 unconfiguredinactive

误区2:change_state response超时说明状态没有改变

错误。节点可能已经到达目标状态,只是response没有送达。

误区3:Reliable保证消息绝对必达

错误。端点发现、资源限制、实体生命周期和写入超时仍可能导致API失败。

误区4:打开use_respawn可以解决Service超时

错误。respawn只在进程退出后触发。

误区5:bond会把崩溃节点重新创建

错误。bond负责检测失联,创建新进程需要launch respawn或外部守护器。

误区6:change_state超时后直接重发最可靠

错误。第一次可能已经生效,应先查询真实状态。

误区7:只增大所有超时时间就能根治

错误。延长超时只能降低部分竞态概率,还可能扩大阻塞影响。上层仍需要状态对账。


20. 排障检查清单

text 复制代码
[ ] ROS_DISTRO、RMW_IMPLEMENTATION、ROS_DOMAIN_ID是否一致
[ ] lifecycle_manager实际管理哪些node_names
[ ] autostart、bond_timeout、use_respawn运行值是什么
[ ] 所有Lifecycle Service是否已经出现
[ ] transition_event最后到达哪个状态
[ ] 服务端是否出现failed to send response
[ ] 客户端是否出现async_send_request failed
[ ] 异常后ros2 lifecycle get返回什么
[ ] 下一个节点是否开始configure/activate
[ ] 节点进程是否仍然存在
[ ] 是否有外层健康检查不断调用get_state
[ ] 是否有旧进程、旧DDS端点或快速重复launch
[ ] 是否只在某一种RMW或Fast DDS配置下出现
[ ] 是否通过单变量A/B测试验证过假设

21. 总结

Nav2 Lifecycle 的核心目标是:

text 复制代码
让多个导航节点按照确定顺序进入一致状态,
在结果不确定时停止后续操作,避免残缺系统继续运行。

它不是完整的进程自愈系统。高可用还需要:

text 复制代码
进程守护
+ bond失联检测
+ get_state有限重试
+ change_state结果对账
+ 整栈健康检查和恢复策略

排查生命周期问题时,最重要的原则是把三件事分开:

text 复制代码
节点是否执行了回调
节点真实状态是否改变
response是否成功到达管理器

只有把日志、transition_eventget_state 三类证据结合起来,才能判断究竟是业务回调失败、进程崩溃,还是"操作成功但确认消息没有送达"。


参考资料

  1. ROS 2 Managed Nodes / Lifecycle Design

    https://design.ros2.org/articles/node_lifecycle.html

  2. ROS 2 Humble Quality of Service Settings

    https://docs.ros.org/en/humble/Concepts/Intermediate/About-Quality-of-Service-Settings.html

  3. Nav2 Lifecycle Manager

    https://github.com/ros-navigation/navigation2/tree/humble/nav2_lifecycle_manager

  4. Fast DDS Reliability QoS Policy

    https://fast-dds.docs.eprosima.com/en/2.6.x/fastdds/dds_layer/core/policy/standardQosPolicies.html#reliabilityqospolicy

  5. ROS 2 Service response timeout讨论

    https://github.com/ros2/rclcpp/issues/2760

相关推荐
loong_XL37 分钟前
Windows WSL2 Ubuntu 搭建 Xvfb 虚拟桌面环境,完美支持多 Agent 隔离
linux·windows·ubuntu
大熊背38 分钟前
树莓派 libcamera IPA 框架解析
linux·ipa·isppipeline
Horn Still Sounds1 小时前
Linux进程间通信:信号、消息队列、共享内存完整笔记
linux·网络·笔记
拂拉氏1 小时前
【知识讲解】 Linux进程的认识与创建
linux·进程
路弥行至1 小时前
ROS2 通信避坑实录:从 topic/msg 底层原理到一次内存错位复盘
网络·经验分享·笔记·其他·内存·topic·ros2
myy-learn1 小时前
27-进程通信
linux·服务器·网络
按时下班accept1 小时前
ERA5生成驱动LBM模型的数据
linux
高亦真1 小时前
今天是学习嵌入式的第32天
linux·学习·算法
gx23482 小时前
k8spod管理
linux·容器·kubernetes