ROS2架构设计哲学——DDS与去中心化
面试的时候被问过:"ROS2为什么要用DDS?不用别的中间件?"
我当时说了句"因为DDS比较成熟"。面试官显然不满意,追问:"成熟体现在哪?它解决了什么具体问题?"
这个问题考的是你对ROS2底层架构的理解深度。光会用ROS2的API不够,你得知道它为什么这么设计,以及这个设计带来了什么好处。
什么是DDS
DDS的全称是Data Distribution Service,数据分发服务。它是OMG(Object Management Group)组织在2004年发布的一个通信中间件标准。注意,这是一个"标准",不是某个具体的软件实现。就像HTTP是协议标准,Apache和Nginx是不同的实现。
DDS最初是为国防和航空航天领域设计的。美国国防部搞了一个叫JTRS(联合战术无线电系统)的项目,需要几十种不同类型的设备互相通信,有的在地面,有的在空中,有的在水下。网络环境复杂,设备随时加入和离开,数据有优先级之分。DDS就是为这种场景设计的。
后来DDS被广泛应用到自动驾驶、医疗设备、智能电网、金融交易这些对可靠性和实时性要求很高的领域。
DDS的核心概念
DDS有几个核心概念,理解了这些就能明白ROS2为什么选它。
以数据为中心。DDS不关心"谁发给谁",它关心的是"数据是什么"。发布者声明"我要发布什么数据",订阅者声明"我要接收什么数据",DDS负责把数据从发布者送到订阅者。发布者不知道订阅者是谁,订阅者也不知道发布者是谁。这种解耦是去中心化的基础。
举个例子,在机器人系统里,激光雷达节点发布点云数据。它不需要知道谁在接收——可能是导航模块,可能是避障模块,可能是可视化模块。激光雷达只管发,谁需要谁来订阅。如果将来加了一个新模块也要用点云数据,不需要改激光雷达的代码,新模块直接订阅就行。
自动发现。节点启动后通过UDP组播自动发现网络中的其他节点。不需要中心服务器,不需要手动配置。你启动十个节点,它们会在几秒钟内互相发现并建立连接。发现协议会交换每个节点的信息:它发布什么topic、订阅什么topic、数据的类型是什么。
QoS策略。这是DDS最强大的特性之一。你可以为每种数据流单独配置传输策略。可靠性方面,可以选择RELIABLE(保证送达,用TCP类似的重传机制)或者BEST_EFFORT(尽力而为,不保证送达但更快)。历史深度方面,可以配置缓存最近几份数据,新加入的订阅者可以收到之前的数据。超时时间方面,可以设置消息的有效期,超时的自动丢弃。
分布式系统模型。DDS把整个系统看作一个"全局数据空间"。所有节点往这个空间里写数据和读数据,不需要知道对方的地址。这跟ROS1的"点对点连接"模型完全不同。
为什么选DDS而不是其他中间件
面试的时候这个问题也很常见。通信中间件不止DDS一个,为什么ROS2偏偏选了它?
ZeroMQ是个轻量级的消息队列库,性能好、使用简单。但它缺乏QoS策略,也没有标准化的数据模型。对于机器人系统来说,不同模块的数据格式和传输需求差异很大,ZeroMQ太底层了。
MQTT是物联网领域常用的协议,轻量、省电。但它基于broker模式,需要一个中心服务器来转发消息。这和ROS1的master问题类似,又是单点故障。
gRPC是Google的远程调用框架,适合服务间的同步通信。但机器人系统大量使用的是发布-订阅模式的数据流,gRPC不太适合这个场景。
DDS的优势在于:它同时支持发布-订阅和请求-响应两种模式,内建QoS策略,去中心化,有标准化的数据模型,而且经过了国防和工业领域几十年的验证。虽然配置复杂一些,但对于机器人这种需要高可靠性的场景来说,这些特性是值得的。
ROS2是怎么用DDS的
ROS2在DDS之上封装了一层叫rcl(ROS Client Library)的接口。rcl再往上就是rclcpp(C++)和rclpy(Python),也就是你写ROS2节点时用的API。
架构层次是这样的:
你的代码(rclcpp/rclpy)
↓
rcl(ROS Client Library,C语言实现)
↓
rmw(ROS Middleware,抽象接口层)
↓
DDS实现(Fast DDS / Cyclone DDS / Connext DDS)
↓
网络传输(UDP/TCP/共享内存)
注意中间那个rmw层。它是一个抽象接口,下面可以接不同的DDS实现。这意味着你不用改一行代码,就能切换底层用的DDS。只需要在编译时指定:
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
这个设计非常聪明。它让ROS2不会被绑定在某一个DDS实现上,哪个DDS有bug或者性能不好,换一个就行。
去中心化意味着什么
ROS1有一个rosmaster,所有节点通过它来发现彼此。ROS2没有这个master。每个节点启动后自己通过DDS的发现协议找到其他节点。
去中心化带来的好处:
没有单点故障。一个节点挂了不影响其他节点通信。新节点加入也不需要通知谁,它自己就能融入网络。
支持多机通信。不同机器上的节点只要在同一个网络里,就能自动发现和通信。不需要配置master的地址,不需要设置ROS_MASTER_URI。
支持动态拓扑。节点可以随时加入和离开,系统自动调整。这对机器人系统特别重要,因为传感器可能随时掉线重连,节点可能需要动态重启。
但去中心化也有代价。发现过程需要几秒钟,不像ROS1的master那样即时。在节点数量特别多的时候(比如几百个),发现过程可能更慢。另外,调试也更复杂,因为没有中心节点可以查询系统状态,得用ros2 cli工具来排查。
面试中怎么聊架构
面试官问ROS2架构,你可以这样回答:"ROS2选择DDS作为底层通信中间件,主要考虑三点:一是去中心化,解决了ROS1的master单点故障问题;二是QoS策略,让开发者能精细控制不同数据流的传输行为;三是DDS本身在工业领域经过了几十年的验证,可靠性有保障。ROS2在DDS之上加了rmw抽象层,支持切换不同的DDS实现,这个设计让系统很灵活。"
这种回答好在:有选择原因、有技术细节、有架构理解。
组件化设计:把多个Node放进一个进程
ROS2还有一个重要的架构特性值得了解:组件(Component)。默认情况下每个Node运行在独立进程里,进程间通信有序列化和反序列化的开销。如果你的系统有很多Node需要频繁交换大数据(比如点云、图像),可以把它们加载到同一个进程里,通过共享内存直接传递数据,零拷贝。
// 把Node注册为组件
#include <rclcpp_components/register_node_macro.hpp>
RCLCPP_COMPONENTS_REGISTER_NODE(MyProcessor, my_package::MyProcessor)
然后用ros2 component load把组件加载到容器进程里。这种方式在性能敏感的场景下非常有用,比如视觉pipeline里的图像预处理和目标检测节点之间,省掉序列化开销后延迟能降低一半。
架构理解对开发的影响
很多初学者只是机械地使用ROS2的API,不理解底层架构。但当你遇到节点发现不了、话题消息丢失等问题时,架构知识就派上用场了。比如理解了DDS的QoS机制,你就能判断该用RELIABLE还是BEST_EFFORT;理解了executor的工作方式,你就能合理分配回调函数到不同的回调组。面试中如果能从架构层面分析问题,会让面试官刮目相看。
给你的建议
不需要去读DDS的规范文档,那太厚了。但建议了解一下DDS的几个核心概念:Topic、DataWriter、DataReader、QoS。理解了这些,你再看ROS2的API就知道它为什么长这样了。
试试切换不同的DDS实现。装一个Cyclone DDS(sudo apt install ros-humble-rmw-cyclonedds-cpp),然后设置环境变量切换过去,跑跑你的程序,感受一下有没有区别。在节点数量多的时候,不同DDS实现的性能差异会比较明显。Fast DDS是ROS2的默认实现,功能最全但内存占用较大;Cyclone DDS更轻量,适合嵌入式平台;Connext DDS是商业产品,性能最好但要付费。了解这些差异,面试的时候能聊得更深入。
上一篇:第108篇 ROS2安装与第一个Hello World程序
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)