最近两个月,我几乎每周都会被拉去讨论同一件事:智能驾驶域控和中央计算单元之间的通信到底用什么中间件。聊座舱方案会聊到DDS,聊智驾方案也会聊到DDS。汽车以太网协议栈发展到今天,DDS(Data Distribution Service,数据分发服务)基本上已经成了SOA架构里绕不开的选项。但你要真去搜索引擎里搜“DDS”,头几条大概率是“dds图片格式怎么打开”“texconv网页版在线dds转换器”“dds信号发生器”这类东西,跟汽车通信完全不在一个次元。
这篇文章我想把汽车以太网场景下的DDS讲透:它解决什么问题、核心机制是什么、工程上怎么选型和部署、实际量产项目中会遇到哪些坑。同时也会花一小节把网上那些同名不同义的“DDS”做个区分,免得大家搜资料时被带偏。无论你是刚转行做车载软件,还是已经在做通信中间件选型,这篇文章应该都能给你一些能直接落地的参考。
1. DDS为什么会出现在汽车以太网里:架构变化与协议选型逻辑
1.1 电子电气架构变了,通信方式也得跟着变
先看背景。过去十几年,一辆车的电子电气架构基本是分布式的,几十上百个ECU用CAN、LIN、FlexRay串在一起。CAN总线的带宽一般就500kbps,LIN更低,这种带宽决定了它只能传刹车状态、车速、车门开关这一类小报文,而且通信模型是“周期发送+信号打包”,跟“服务”两个字完全不沾边。
但现在不一样了。新一代车型基本都在往域集中式架构走,智驾域、座舱域、车身域、底盘域,再到后面中央计算加区域控制器。域控制器之间、传感器与计算单元之间要传的东西,从原来的几个字节变成了海量数据:激光雷达点云、800万像素摄像头图像、高精地图切片、传感器融合结果。这些数据动辄每秒几十MB甚至上百MB,CAN那点带宽连个零头都不够。
带宽解决之后,第二个问题是怎么组织软件。传统AUTOSAR CP走的是“信号+运行实体+静态配置”的路子,所有通信矩阵在开发阶段就要定死。但软件定义汽车时代,软件要能OTA升级、要能动态部署服务、要能跨域复用。于是整个行业都在往SOA(面向服务架构)迁移,通信不再是一对一硬编码的信号,而是“服务发布方”和“服务订阅方”之间按需发现、动态绑定。这个时候就需要一个具备服务发现、动态解耦、灵活QoS能力的通信中间件,DDS就是在这种背景下进入汽车视野的。
1.2 为什么不能只靠TCP/UDP裸奔
很多人会问:以太网底层不就是IP吗?我直接用TCP/UDP socket不行吗?说实话,小规模原型还真能这么干,但放到量产车上就会遇到一串问题。
先看UDP。UDP是尽力而为的,不保证不丢包,不保证顺序,没有流控。摄像头图像丢几帧还能忍,但刹车指令、转向控制这类报文丢一帧可能是安全事故。很多团队会在UDP上面自己写重传、排序、超时检测,最后写出来的东西就是一个残缺的可靠通信协议——而你正在重复发明一个比DDS差得多的轮子。
再看TCP。TCP解决了可靠性和顺序问题,但它是“流式”的,一帧数据和下一帧数据之间没有天然边界,接收方要自己拆包。更重要的是TCP是点对点的,车里面有20个节点要共享同一份传感器数据,TCP就得建20条连接,每条连接各自维护状态,资源开销大不说,新增一个节点还得改发送方的代码。还有一个更致命的问题:TCP的拥塞控制里包含了超时重传和滑动窗口,这在高实时性场景下会造成“线头阻塞”。某个包丢了,后面所有数据都得等它重传成功,哪怕你的控制指令只晚了10毫秒,可能就已经过了执行窗口。
DDS的设计思路跟TCP/UDP完全不同。它站在发布订阅模型上,把网络抽象成一个“全局数据空间”。任何节点想发数据,往这个空间里“写”就行;任何节点想收数据,声明自己“读”哪个主题就行。至于谁在哪儿、用什么IP、怎么发现、怎么保证可靠性、要不要缓存历史数据,这些都是DDS框架替你解决的问题。底层仍然走UDP或共享内存,但暴露给应用层的,是一个干净、松耦合、可配置的接口。
1.3 先别搞混:图片DDS、信号源DDS和总线DDS
刚才说到网上搜到的“DDS”五花八门,这里专门掰扯清楚,后面内容才不会被绕晕。
第一种是DDS图片格式。它是微软DirectDraw Surface的缩写,游戏贴图常用的一种纹理格式,扩展名通常是.dds。你看到的“texconv网页版在线dds转换器”“如何打开dds图片”都属于这一类。跟汽车通信半毛钱关系都没有。
第二种是DDS信号发生器。这里DDS是Direct Digital Synthesizer(直接数字频率合成)的缩写,是一种用数字方式生成正弦波、方波的电子技术,常见于实验室仪器、射频电路里。FPGA开发里经常用到的Vivado DDS IP核也是这个东西,用来做频率合成、调制解调,不是通信协议。
第三种才是我们要聊的DDS,Data Distribution Service,是OMG组织发布的一套分布式数据分发标准,工业物联网、机器人、自动驾驶都在用。ROS2底层的通信中间件就是DDS/RTPS,所以“ros2 dds”这个热搜词实际上也就是指它。判断一个资料是不是你要找的,看上下文就知道:如果它在聊image、texture、frequency,那都是另外两个DDS;如果它在聊domain、topic、QoS、DataWriter、DataReader,那才是汽车以太网要用的DDS。
2. DDS的核心机制:全局数据空间、发布订阅与QoS
2.1 全局数据空间:从点对点通信变成“数据黑板”
DDS最核心的抽象概念叫“全局数据空间”(Global Data Space)。你可以把它理解成一块分布在各节点内存里、但逻辑上共享的“黑板”。所有节点都可以往黑板上贴数据,也可以从黑板上取数据,不需要知道数据是谁贴的、也不需要知道有谁会来取。
在这个空间里,几个关键概念你需要先记住:
Domain(域):相当于一个独立的通信边界。只有属于同一个域的参与者才能互相通信,版本、配置都匹配才行。类比一下,就像同一套Wi-Fi网络里不同VLAN之间默认不通一样,Domain是做隔离用的第一层。
DomainParticipant(域参与者):一个进程或一个通信实体在域里的代表。你要收发数据,必须先创建一个Participant。
Topic(主题):数据分类的标识。DDS通过Topic名称来区分不同的数据流,比如/camera/raw、/vehicle/speed、/sensor/lidar。Topic不仅要名字匹配,还要数据类型匹配。
Publisher/Subscriber(发布者/订阅者):对应通信的发起方向。Publisher下挂DataWriter,Subscriber下挂DataReader。
DataWriter/DataReader(数据写入者/数据读取者):真正执行读写操作的对象。一个Topic可以有多个Writer和多个Reader,DDS负责把它们一对一、一对多、多对一地组织起来。
这套模型最大的好处是多对多通信。比如5个摄像头各自向/topics/raw_image写数据,3个不同算法模块同时订阅这个Topic,DDS会自动把5路数据分发到这3个模块,不需要谁去单独建连接。以后新增一个处理模块,只要它订阅同一个Topic,老节点完全不用改。
2.2 QoS是DDS的灵魂:每个策略到底在控制什么
只做发布订阅的话,很多消息中间件都能干。DDS真正的护城河是QoS——Quality of Service,一套非常细粒度的通信行为控制策略。我见过的很多项目事故,到最后都指向一件事:QoS策略没配好。
先说最关键的几个。
第一个是RELIABILITY(可靠性策略)。它有两个取值:BEST_EFFORT和RELIABLE。BEST_EFFORT保证“尽力送达”,丢包不会重传,速度快但可能有遗漏。RELIABLE保证“不丢”,底层会缓存和重传。注意,RELIABLE不等于“实时性好”,反而在丢包严重的网络里,延迟会更大。因为重传要时间。
第二个是DURABILITY(持久性策略)。它决定了一个晚来的订阅者,能不能收到发布者之前发出的历史数据。取值有VOLATILE(不保留历史)、TRANSIENT_LOCAL(Writer端的缓存,重启后消失)、TRANSIENT(跨Writer进程重启的存在于网络中的持久化缓存,通常需要额外服务)、PERSISTENT(落盘)。车里面很多场景需要这个:某个算法模块启动晚了,但它希望一启动就能拿到车辆当前的姿态角、速度、地图版本这些“最新状态”。如果Publisher配置的是TRANSIENT_LOCAL,它启动后无需等待下一帧,立刻能拿到最新值。
第三个是HISTORY(历史记录策略)。它配合DURABILITY使用,控制“最多保留多少条历史数据”。KEEP_LAST(N)表示保留最近N条,KEEP_ALL表示一条不丢全部保留。注意,KEEP_ALL在持续高频发布场景下,很容易把发送端缓存撑爆——因为接收端如果处理不过来,数据全堆在发送端等重传。
第四个是DEADLINE(截止时间策略)。这是很多实时系统的救命策略。它会声明:“这个Topic的数据必须在xx毫秒内至少来一次”。如果Publisher没在这个时间内发布新数据,DDS会报告错过截止时间的回调;如果Subscriber没在这个时间内收到数据,同样会触发回调。这样你就能在感知系统“还在正常工作”的时候,提前发现它已经卡了。
还有几个常用的:LIVELINESS(活性策略),检测节点是否还活着;PARTITION(分区策略),在同一个Topic里再做一层逻辑分区;RESOURCE_LIMITS(资源限制策略),限定缓存队列的长度、内存上限;TRANSPORT_PRIORITY,给不同的Topic配置网络优先级,配合TSN时可以给关键控制指令最高优先级。
我自己的经验是,QoS不是配得越严格越好,也不是统一设成RELIABLE就万事大吉。摄像头原始图像这种每秒传输几十帧的大payload,用RELIABLE+KEEP_ALL一旦网络抖动,延迟和内存都会爆炸;而控制指令这种低频但关键的数据,用BEST_EFFORT就是在拿生命开玩笑。正确做法是每个Topic按数据特征单独配QoS,而且要提前跟算法团队对齐。
2.3 动态发现机制:节点上线后怎么互相找到
DDS的另一个特点就是“去中心化的动态发现”。传统以太网通信里,A要跟B通信,要么静态配置IP和端口,要么靠一个中心化的服务注册中心。DDS不一样,它在底层实现了RTPS(Real-Time Publish-Subscribe)协议,节点之间通过多播地址自动互相发现。
简单来说,当一个新的Participant启动时,它会在固定的多播端口上发送SPDP(Simple Participant Discovery Protocol)消息,宣告自己的存在。其他Participant收到后,双方交换各自包含哪些Topic、支持哪些QoS的信息,这个过程叫SEDP(Simple Endpoint Discovery Protocol)。全部走协议自动完成,不需要人工配置。
动态发现在车间测试时非常好用,节点拔了插上,重新启动,数据自动通。但上了整车量产或者跨网段部署,就得多长个心眼:RTPS默认依赖IP多播,有些车载以太网交换机为了控制流量风暴,会禁止多播帧穿透,或者网段隔离之后发现消息根本过不来。这时候就需要配置发现的对端静态地址列表(initial peers)或者调整RTPS的发现周期。这个坑后面排查章节再细说。
3. 工程实施与选型要点:从代码到部署的参数级建议
3.1 典型车载软件栈里的DDS部署形态
在车上做DDS,跟服务器集群里做DDS,环境差异非常大。车载场景通常是每个域控制器一台“小型服务器”,上面跑Linux(或QNX),里面有多个进程,比如感知进程、规划进程、底盘控制进程。每个进程都可以作为独立的Participant接入同一个Domain。
我见过两种部署风格。一种是“每个进程一个Participant”,好处是网络结构清晰,每个进程可以独立配置不同的QoS和安全属性;坏处是Participant数量多,发现消息开销大。另一种是“一个进程只创建少量Participant,多个线程共用”,好处是资源占用低,坏处是配置和调优时相互影响,一个线程改QoS可能影响到同Participant的其他线程。
还有一个细节是传输方式。在同一个域控内部的多个进程之间传数据,完全没必要走网卡出去绕一圈。很多DDS实现会做Shared Memory传输的自动判断:发送和接收在同一个主机上时,底层自动切换到共享内存,延迟能降到微秒级。Fast DDS和Cyclone DDS都支持这个特性,配置时要把shared memory传输组件打开,量产交付检查清单里务必加上这一项。
另外,现在很多智驾方案里是DDS和SOME/IP混合使用:SOME/IP负责AUTOSAR AP的标准化服务接口、诊断、车辆控制,DDS负责高频数据流和算法模块之间的灵活通信。两者都是IP之上跑的,可以共存。具体怎么分,一般是:所有需要上AUTOSAR AP正式服务框架的走SOME/IP,感知融合、点云分发、图像共享这类大数据走DDS。
3.2 主流DDS实现对比:Fast DDS、Cyclone DDS、RTI Connext
DDS是一套规范,实际落地要看具体实现。目前车载圈用的比较多的就三家。
eProsima Fast DDS是很多自动驾驶团队的首选,原因很简单:它开源、免费、社区活跃,而且ROS2默认用的就是它(后来ROS2也支持Cyclone DDS做RMW实现)。代码用C++写的,支持QoS全集、共享内存传输、安全插件(DDS Security)。如果你们项目预算有限,又想快速上车,Fast DDS是比较稳的选择。
Cyclone DDS是Eclipse基金会下面的开源项目,源自ADLINK的Vortex系列。它的特点是架构极简、内存占用低、在某些性能测试里延迟表现很惊艳。ROS2认它,工业场景里也有不少人在用。如果你对实时性敏感、设备算力又有限,可以重点测一下Cyclone DDS。
RTI Connext DDS是商业产品,在AUTOSAR、军工、航天里占有率很高。它强在成熟度、技术支持和工具链,配套有RTI Recording Service、Monitoring Library,调试方便。缺点就是贵,License费用对很多车企的零部件供应商来说是一笔不小的成本。
还有一个需要提的是Eclipse Zenoh,严格来说它不是DDS RFC的实现,但很多人拿它来替代DDS做车云一体通信,有兴趣可以单独研究。
| 对比维度 | Fast DDS | Cyclone DDS | RTI Connext DDS |
|---|---|---|---|
| License | Apache 2.0(商业友好) | Eclipse Public License 2.0 | 商业许可 |
| ROS2支持 | 默认RMW之一,生态最广 | 支持,性能表现优秀 | 支持 |
| 目标场景 | 机器人、自动驾驶、工业 | 实时系统、嵌入式 | 航空航天、AUTOSAR、军工 |
| 工具链 | 提供fastdds工具、代码生成器 | 提供CycloneDDS源码和示例 | 提供完整IDE、监控和录制工具 |
| 功能安全认证 | 社区版未认证,商业版可谈 | 未直接认证 | 提供ASIL D相关认证支撑 |
选型时我的建议是:先拿你们最核心的两个高频Topic,在两个开源实现上各做一轮延迟、抖动、CPU占用对比,再决定要不要上商业版。商业版买的不只是代码,而是支持体系和认证材料,如果你们目标是量产且有功能安全要求,留一笔预算给许可证是值得的。
3.3 网络规划与关键参数:别等上了车再调
DDS的应用层看似简单,下面毕竟还是要走IP网络。我强烈建议在项目初期就把网络规划做进架构设计里,而不是等联调时才发现通信不通。
第一,VLAN切割。汽车以太网现在一般有多个VLAN:诊断VLAN、智驾VLAN、座舱VLAN、控制VLAN。DDS的Domain要和VLAN对应起来。没有对应的业务需求时,不要让智驾域的数据流把座舱域的控制VLAN带宽塞满。
第二,多播地址规划。RTPS默认使用固定的多播组,但不同Topic如果都走同一个多播组,会产生大量无关流量。合理做法是每个高频Topic分配独立的Topic多播地址和端口,低频服务走单播。地址规划表要维护好,我自己被多播地址填错导致“数据互不可见”的坑绊倒过两次。
第三,流量控制。DDS本身没有内置“节流”机制,Publisher按业务频率发多少就会推出去多少。如果多个传感器叠加后瞬时流量超过交换机和网卡能力,就是大规模丢包。要在网络设备和DDS两个层面都做流量管控:交换机上做带宽保证(CBS),DDS层面对非关键Topic设置发布频率上限或批量合并发送。
3.4 一套实用的QoS参数模板
下面给一套我实际项目里用过的默认QoS模板,覆盖了三类典型Topic,你们做方案时可以直接抄作业再改。
| Topic类型 | 典型示例 | Reliability | Durability | History | Deadline | Livelines | 说明 |
|---|---|---|---|---|---|---|---|
| 高频感知数据 | 摄像头图像、雷达点云 | BEST_EFFORT | VOLATILE | KEEP_LAST(1) | 100ms | AUTOMATIC | 丢帧可接受,追求最低延迟 |
| 低频控制指令 | 纵向控制、转向控制 | RELIABLE | TRANSIENT_LOCAL | KEEP_LAST(10) | 10ms | MANUAL_BY_PARTICIPANT | 必须不丢,及时性优先 |
| 状态与配置 | 车辆姿态、地图版本 | RELIABLE | TRANSIENT_LOCAL | KEEP_LAST(5) | 1000ms | AUTOMATIC | 新加入节点需拿到最新状态 |
关于LIVELINESS我多说一句。MANUAL_BY_PARTICIPANT适合控制指令这类周期性“心跳”消息,因为它的活性判断跟数据发送频率解耦,甚至可以靠单独的活性信号来维持,不容易误判。AUTOMATIC则更适合数据本身就持续高频的场景,有数据就活性正常,没数据就判定为掉线。
4. 实操中的常见问题与排查技巧实录
4.1 数据丢了但不报错:QoS不匹配是头号坑
先讲一个我自己踩过的坑。有一次联调,A进程发图像数据,B进程订阅,B端一直能收到数据,但是收到的是几秒前的旧数据。查网络、查时间同步都没问题。最后打开两端配置一对比才发现,A的DURABILITY配的是TRANSIENT_LOCAL,B的DURABILITY配的是VOLATILE,两边RELIABILITY又不一致。DDS的规则是:订阅方的QoS只能比发布方“更严格或相等”,如果订阅方要求更低级别的保障,配对时就可能出现一种“半通不通”的状态。
更坑的是,DDS的QoS不匹配并不会直接给你抛一个“禁止通信”的异常,它可能只是“安静地不给你发数据”或者在底层选了一条看起来能用但不是最优的路径。排查这个问题的标准动作就是:把发布方和订阅方各自的QoS配置全部打印出来,逐项核对匹配矩阵。Fast DDS提供了fastdds tool可以查看Topic、Writer、Reader的内部状态,强烈建议在开发环境里把工具链提前搭好。
4.2 节点发现慢、发现不到对端:多半是多播问题
前面说过RTPS依赖多播做自动发现。在实际车厂北向实验室里,网络环境千奇百怪:有的交换机开启了IGMP Snooping但配置了快速离开,多播组成员频繁变动导致发现消息反复丢失;有的网管为了“安全”把所有多播都禁了;还有的跨网段部署,两个域控在不同子网,多播包根本不在网段间转。
排查方法有几个。第一,用tcpdump抓包看交换机和网卡上有没有周期性SPDP报文到达。第二,看防火墙是不是挡了7400到7500这段的UDP多播端口,很多团队为了方便开了一堆iptables规则,结果把DDS的发现端口也拦掉了。第三,如果环境实在不允许多播,就一定要用Initial Peers配置,把对端Participant的单播地址列表显式写进去。这样DDS会直接在单播上做发现,虽然灵活性下降,但可靠性和可预测性大幅提升。
4.3 CPU占用高、延迟抖动大:别让DDS线程成了系统瓶颈
DDS实现一般会起多个后台线程去处理接收、发送、发现和重传。高频大数据的拓扑下,后台线程跑满CPU是常见事。我之前在一个项目里发现,两个图像Topic一发布,整机CPU占用直接涨了60%,后面查出来是同一台机器上多个Participant重复接收同一份镜像数据,还各自做了反序列化。优化思路是:同一进程内跨模块的数据共享,优先考虑共享内存通道+DDS的分发配置,让同一份数据只被解码一次。
另外,DDS的线程优先级在Linux下默认往往不是实时优先级。如果你的系统用了PREEMPT_RT补丁或者有实时核,记得把DDS关键线程的affinity绑定到实时核、优先级设高。这个配置看起来不起眼,但延迟抖动的影响经常就是从这里来的。
4.4 常见问题速查表
| 现象 | 可能原因 | 首选处理思路 |
|---|---|---|
| 订阅方完全收不到数据 | 两端Domain ID不一致、Topic名称不一致、QoS不匹配 | 对比Domain、Topic、类型、QoS四项配置 |
| 偶尔能收到数据但会断 | 多播被交换机丢弃、Discovery周期过长 | 打开抓包确认SPDP/SEDP,调整多播或配置Initial Peers |
| 收到数据延迟明显 | 大量数据走了RELIABLE重传、接收队列太长 | 该Topic改用BEST_EFFORT或加大KEEP_LAST容量、启用巨型帧 |
| CPU占用异常高 | 多份数据重复反序列化、接收线程无优先级 | 共享内存传输+线程亲和配置+减少跨Participant数量 |
| 新节点启动后拿不到最新状态 | DURABILITY配成VOLATILE | 发布方改配TRANSIENT_LOCAL,并确保HISTORY能保留足够数据 |
| QoS配置看起来一样但无法匹配 | 不同实现之间对默认值处理有差异 | 显式写出所有QoS字段,不要依赖任一实现的默认值 |
5. 写在后面:一个小技巧和一点体会
最后再分享一个我最近才养成的习惯:所有DDS工程的代码仓库里,一定要有一份“Topic登记表”。不要只把Topic和相关类定义写在代码注释里,单独提一个YAML文件,记录每个Topic的名字、数据类型、Publication/Subscription节点的归属、QoS模板编号、带宽预估、多播地址分配。这份登记表既是设计文档,也是排查故障的第一手依据。我遇到过太多次,新同事接手项目后花两周时间在代码里翻Topic定义,最后发现两个进程的Topic名只差一个下划线。
这几年做车载通信中间件,我最大的感受是:DDS本身不是银弹。它只是把网络通信的设计从“我该怎么把数据从A传给B”变成了“我应该怎么描述这条数据流、保障它的什么特性”。后者想清楚了,DDS的所有配置自然就有了解法。如果你们团队正准备把通信架构迁到DDS,不要上来就写代码,先花两个下午把每类数据流的QoS特性和网络边界列出来,再去动工程。这个前置投入,后面能帮你省下无数加班的夜晚。