3、DDS协议详解:数据分发服务的基本概念、QoS策略(可靠性、时效性、持久性)、DDS在自动驾驶中的角色
好,咱们进入第三章。这一章我打算把DDS协议掰开了揉碎了讲清楚。
说实话,DDS这个协议,我刚接触的时候也觉得挺玄乎的。什么全局数据空间、什么QoS策略,听着就头大。但后来在几个量产项目里摸爬滚打之后,我慢慢发现——它其实就是一套非常聪明的“数据快递系统”。
你想想看,自动驾驶域控里,传感器数据、规划结果、控制指令,这些数据要在几十个甚至上百个节点之间跑来跑去。谁发给谁?什么时候发?丢了怎么办?迟到了行不行?这些问题,DDS都帮你管好了。
3.1 DDS的基本概念:全局数据空间
DDS最核心的思想,就是“全局数据空间”。
什么意思呢?说白了,就是所有节点都把自己要发布的数据“扔”到一个虚拟的共享空间里,其他节点按需订阅。发布者不需要知道谁在收,订阅者也不需要知道谁在发。
我记得第一次在项目里用DDS的时候,团队里有人问:“这不就是消息队列吗?”其实不一样。消息队列是中心化的,有个Broker在中间转;DDS是去中心化的,节点之间直接通信。少了中间商赚差价,延迟自然就低了。
DDS里有两个基本角色:
- Publisher(发布者):负责把数据写进全局数据空间。比如激光雷达节点,每秒发布10帧点云数据。
- Subscriber(订阅者):从全局数据空间里读数据。比如感知融合节点,订阅激光雷达和摄像头的数据。
还有一个概念叫Topic(主题),你可以把它理解成数据的“标签”。比如“/sensor/lidar/points”就是一个Topic,所有跟激光雷达点云相关的数据都打这个标签。发布者和订阅者通过Topic匹配,就像收音机调频一样。
重要概念:DDS的通信模型是“多对多”的。一个发布者可以对应多个订阅者,一个订阅者也可以订阅多个Topic。这种灵活性在自动驾驶里特别有用——比如障碍物检测结果,规划模块要订阅,控制模块也要订阅,甚至HMI显示也要订阅。
3.2 QoS策略:DDS的灵魂
如果DDS只是解决了“谁发给谁”的问题,那它跟普通的消息中间件也没什么区别。DDS真正厉害的地方,是它的QoS策略。
QoS,全称是Quality of Service,服务质量。你可以把它理解成“数据快递的配送规则”。不同的数据,需要不同的配送规则。
举个例子:
- 刹车指令:必须100%送达,晚1毫秒都不行。
- GPS位置:偶尔丢一帧没关系,但必须是最新的。
- 高精地图:可以慢慢传,但不能丢。
DDS提供了20多种QoS策略,但咱们做自动驾驶的,最常用的就三个:可靠性、时效性、持久性。
3.2.1 可靠性(Reliability)
可靠性策略决定了数据要不要“确保送达”。
有两种模式:
- RELIABLE(可靠模式):数据必须送达,如果丢了就重传。适合控制指令、标定参数这类关键数据。
- BEST_EFFORT(尽力模式):能送就送,丢了不补。适合传感器数据,比如摄像头图像,丢一帧没关系,下一帧马上就来。
我在项目里遇到过一个问题:一开始把所有Topic都设成了RELIABLE模式,结果发现网络负载特别高,延迟也上去了。后来才意识到,激光雷达点云数据根本不需要可靠传输——你想想看,每秒10帧的数据,丢一帧根本看不出来。改成BEST_EFFORT之后,系统性能明显提升了。
我的建议:控制类、状态类、配置类数据用RELIABLE;传感器原始数据、日志数据用BEST_EFFORT。这个原则基本不会出错。
3.2.2 时效性(Durability & Lifespan)
时效性策略解决的是“数据能活多久”的问题。
DDS里有两个相关的QoS:
- DURABILITY(持久性):数据要不要保存下来,给后来的订阅者用?
- LIFESPAN(生命周期):数据保存多久之后过期?
DURABILITY有几种设置:
| 策略 | 含义 | 自动驾驶场景 |
|---|---|---|
| VOLATILE | 不保存,只发给当前在线的订阅者 | 实时传感器数据 |
| TRANSIENT_LOCAL | 保存在发布者本地,新订阅者加入时重发 | 车辆状态信息 |
| TRANSIENT | 保存在DDS中间件中,持久化 | 高精地图、配置文件 |
| PERSISTENT | 保存在磁盘上,重启后还在 | 标定参数、固件版本 |
嗯,这里要注意:PERSISTENT模式在嵌入式域控里用得不多,因为写磁盘太慢了。我一般用TRANSIENT_LOCAL就够了。
3.2.3 持久性(History)
持久性策略决定的是“保存多少历史数据”。
有两种模式:
- KEEP_LAST:只保留最近N个样本。比如KEEP_LAST 5,就是保留最近5帧数据。
- KEEP_ALL:保留所有数据,直到资源耗尽。
我建议你慎用KEEP_ALL。曾经有个同事在调试时把History设成了KEEP_ALL,结果内存直接爆了——因为激光雷达每秒产生几十兆数据,几分钟就把内存吃光了。
避坑指南:我曾经在一个项目里,把感知结果的Topic设成了KEEP_ALL + RELIABLE。结果网络稍微一波动,数据就开始积压,延迟从10ms飙到了500ms。后来改成KEEP_LAST 1 + BEST_EFFORT,问题就解决了。记住:实时系统里,旧数据就是垃圾数据。
3.3 DDS在自动驾驶中的角色
好了,前面讲了这么多概念,咱们回到实际场景:DDS在自动驾驶域控里到底扮演什么角色?
我个人觉得,DDS在自动驾驶里主要干三件事:
- 解耦模块:感知、规划、控制、定位,这些模块之间不需要知道对方的存在。只要定义好Topic和数据类型,各模块独立开发、独立测试。这在敏捷开发里特别重要。
- 保证实时性:DDS的QoS策略可以精确控制数据的延迟和可靠性。比如控制指令必须10ms内送达,DDS可以通过优先级和带宽控制来保证。
- 支持动态发现:节点上线、下线,DDS自动发现。这在自动驾驶里很有用——比如某个传感器故障了,DDS会自动把它的数据流断开,其他模块不会收到脏数据。
我举个例子吧。在一个典型的自动驾驶域控里,数据流大概是这样的:
Topic: /sensor/lidar/points
Publisher: 激光雷达驱动
Subscriber: 感知融合模块
QoS: BEST_EFFORT, KEEP_LAST 1
Topic: /planning/trajectory
Publisher: 规划模块
Subscriber: 控制模块
QoS: RELIABLE, KEEP_LAST 1, DEADLINE 20ms
Topic: /vehicle/status
Publisher: 车辆接口
Subscriber: 规划模块, HMI, 日志记录
QoS: RELIABLE, TRANSIENT_LOCAL, KEEP_LAST 5
你看,每个Topic的QoS都不一样。这就是DDS的灵活性所在。
总结一下:DDS在自动驾驶里,就是那个“让数据在正确的时间、以正确的方式、到达正确的地方”的中间件。没有它,模块之间的通信会变得极其复杂;有了它,你可以专注于业务逻辑,不用操心数据怎么传。
下一章,我会讲DDS在域控里的具体部署方案,包括如何配置QoS、如何做性能调优。到时候我会分享一些实际项目里的踩坑经验,敬请期待。