从零搭建自动驾驶中间件(一):为什么自动驾驶需要自研中间件

前言

如果你正在读这篇文章,大概率你已经接触过 ROS2 或 CyberRT,甚至可能正在维护一套自研的中间件系统。我也是这样------从事自动驾驶行业三年,从 ROS2 起步,到深度使用 CyberRT,再到参与 HyperFlow 的设计开发,对自动驾驶中间件有了不少踩坑和思考。

这个系列博客,我想把这些经验整理出来,聊聊如何从零搭建一套自动驾驶中间件。不是纸上谈兵的架构图,而是每个设计决策背后的工程考量和踩坑经验。


1. 先聊聊自动驾驶的"特殊性"

很多人会问:自动驾驶不就是机器人吗?为什么不能直接用 ROS2?

答案的关键在于自动驾驶对"确定性"的要求完全不同。

1.1 一个刹车数据的生命周期

考虑这个场景:前方 50 米有障碍物,自车时速 120km/h。

复制代码
激光雷达检测到障碍物
    → 感知模块识别+分类         (~30ms)
    → 预测模块推演轨迹          (~10ms)
    → 规划模块生成避障路径       (~20ms)
    → 控制模块输出制动指令       (~5ms)
    → CAN 总线执行刹车           (~5ms)
──────────────────────────────────
总延迟:~70ms

在 120km/h 下,70ms = 车辆行驶 2.3 米

如果中间件的通信延迟再增加 10ms,刹车距离就多出 0.33 米。 在紧急场景下,这可能就是事故和安全的分界线。

这不是 ROS2 的 DDS 能轻松应对的------DDS 的序列化、发现协议、QoS 协商,每一层都在吃延迟。

1.2 自动驾驶的数据特征

特征 说明 对中间件的要求
高频传感器数据 激光雷达 10Hz、摄像头 30Hz、IMU 100Hz+ 高吞吐、低延迟
固定拓扑 传感器→感知→预测→规划→控制,拓扑固定 不需要动态发现
多消费者 同一帧点云可能被感知、定位、可视化同时消费 零拷贝、一写多读
大尺寸数据 一帧点云 100KB~2MB,一帧图像 5~20MB 避免数据拷贝
跨进程 安全隔离要求模块运行在不同进程 跨进程零拷贝通信
实时性 控制环路延迟 < 100ms 可预测的调度延迟

1.3 嵌入式部署的现实

自动驾驶计算平台不是你的开发机:

  • 算力受限:Orin NX 8GB 内存、8 核 A78,不像服务器可以随便开线程
  • 温度约束:仪表盘下 85°C 环境,CPU 不能长时间满载
  • 资源隔离:感知进程不能因为一个 bug 把控制进程的 CPU 抢光
  • 车载诊断:需要对接 MCU 的 DTC 故障码体系

这些需求,ROS2 一个都不原生支持,CyberRT 覆盖了一部分,但没有一家能 100% 满足你的项目需求。


2. 三大框架的真实对比

我深度使用过这三个框架,说说我的真实感受(不是官方文档的搬运)。

2.1 ROS2 --- 优雅但不适合上车

优点:

  • 生态无敌。rviz2、moveit2、nav2、rosbag2......你需要的工具基本都有
  • DDS 标准化,理论上可以替换底层实现
  • 社区活跃,遇到问题 Stack Overflow 能搜到

痛点:

  • 延迟不可控:DDS 的发现协议(SDP)在启动时可能花数秒,运行中的通信延迟在 1~10ms 量级波动
  • 序列化是硬伤:CDR 序列化对高频大数据(点云、图像)的开销不可忽视
  • 进程内通信也要过 DDS:同一个进程的两个 Node 通信,数据还是要序列化→DDS→反序列化
  • 没有调度:每个 Node 自己管线程,无法做全局调度优化
  • 没有诊断:车载 DTC 体系完全需要自建

我的经历:在 ROS2 上做过原型验证,很爽。但一到性能优化阶段,就开始和各种 DDS 参数搏斗------SHM 传输配置、QoS 策略选择、零拷贝配置......每个 DDS 实现还不一样。

2.2 CyberRT --- 强大但封闭

优点:

  • 共享内存通信:Intra-process 直接回调,跨进程走 SHM Transport,延迟很低
  • 协程调度:Croutine 调度器,支持 Classic/Choreography 两种策略
  • Protobuf:比 CDR 高效,代码生成也好用
  • DAG 配置:可以通过 DAG 文件定义模块拓扑和调度策略
  • 优先级调度:Choreography 策略支持优先级和 CPU 亲和性

痛点:

  • 序列化仍然存在:Protobuf 序列化/反序列化在跨进程通信中仍有开销
  • 闭源工具链:cyber_monitor、cyber_recorder 等工具不开源,出了问题没法调试
  • 依赖 Apollo 全家桶:很难脱离 Apollo 生态独立使用
  • 没有诊断系统:DTC 诊断需要自建
  • 没有交互终端:运行时观测只能靠 cyber_monitor,无法交互操控
  • 没有进程管理:依赖 Docker 容器做隔离,资源控制不够细粒度

我的经历:CyberRT 的调度设计确实牛,特别是 Choreography 策略。但 Protobuf 序列化在处理点云数据时还是能感受到开销,而且脱离 Apollo 单独使用 CyberRT 的文档几乎为零。

2.3 自研中间件 --- 适合你吗?

在决定自研之前,先问自己几个问题:

  1. 你的系统是否运行在嵌入式平台? 如果是 x86 服务器,ROS2 可能就够了
  2. 你是否需要 DTC 诊断? 如果不需要对接 MCU,CyberRT 可能就够了
  3. 你是否需要运行时交互调试? 如果能接受只有日志,ROS2 可能就够了
  4. 你的团队有多少人? 自研中间件至少需要 2-3 人持续维护
  5. 你是否需要极致的通信延迟? 如果 1ms 的波动可以接受,不需要自研

如果上述问题中你有 3 个以上的答案是"是",那么自研中间件值得考虑。


3. 自研中间件的设计哲学

在 HyperFlow 的设计中,我们坚持了三个核心原则:

3.1 零序列化

序列化是延迟的万恶之源。 在自动驾驶系统中,模块间传递的数据结构是固定的、平台是一致的,完全没有必要做序列化/反序列化。

复制代码
传统方案:                          HyperFlow:
  struct → serialize → bytes        struct → 直接写入共享内存
  bytes → deserialize → struct      共享内存 → 直接读取指针

代价是什么?跨平台兼容性。但自动驾驶的部署环境是固定的(Linux + ARM64/x86_64),这个代价完全可以接受。

3.2 零侵入

框架不应该侵入业务代码。 算法工程师不应该关心框架的存在。

  • 数据读写:Write<T>("data_name", data) / Read<T>("data_name", data)
  • 数据回灌:只需配置 "enable_snapshot": true,业务代码零改动
  • 时间同步:回灌模式下 GetTimestamp() 自动返回录制时间,业务代码无感知

3.3 可观测优先

运行时不知道系统在干什么是不可接受的。 嵌入式系统不像 Web 服务可以随时加日志。

  • 内置 Telnet 终端:运行时可直接查看模块状态、数据统计、调整参数
  • 内置诊断系统:DTC 故障码管理,直接对接 MCU
  • 内置进程监控:CPU/内存实时统计,异常自动重启

4. 中间件的核心模块拆解

一个完整的自动驾驶中间件,至少需要以下模块:

复制代码
┌─────────────────────────────────────────────────────┐
│                   自动驾驶中间件                       │
│                                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────┐  │
│  │  通信层       │  │  调度层       │  │  运维层    │  │
│  │              │  │              │  │           │  │
│  │ • 共享内存    │  │ • 协程调度    │  │ • 终端    │  │
│  │ • 零拷贝     │  │ • 事件驱动    │  │ • 诊断    │  │
│  │ • 跨进程     │  │ • 定时触发    │  │ • 监控    │  │
│  │ • 信号通知    │  │ • 触发器      │  │ • cgroup  │  │
│  └──────────────┘  └──────────────┘  └───────────┘  │
│                                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────┐  │
│  │  模块层       │  │  数据层       │  │  扩展层    │  │
│  │              │  │              │  │           │  │
│  │ • Module基类  │  │ • 数据录制    │  │ • 状态机  │  │
│  │ • 插件加载    │  │ • 数据回灌    │  │ • RPC    │  │
│  │ • 工厂注册    │  │ • 数据快照    │  │ • 存储    │  │
│  └──────────────┘  └──────────────┘  └───────────┘  │
└─────────────────────────────────────────────────────┘

这个系列博客将逐一展开每个模块的设计与实现:

篇章 主题 核心内容
(二) 共享内存通信 DataQueue 环形缓冲区、零拷贝读写、跨进程通知
(三) 事件驱动与协程调度 marl 协程、Trigger 机制、定时/事件双模式
(四) 数据录制与回灌 Snapshot 机制、MCAP 格式、frameid 匹配、时间源切换
(五) 状态机与诊断 层次化状态机、DTC 管理、进程监控与 cgroup
(六) 交互式终端 Telnet Server、命令树、按键状态机

5. 性能对比:数据会说话

为了给出一个直观的感受,我们在同一台机器上(Intel i7-12700H, 32GB DDR5, Ubuntu 22.04)做了简单的延迟对比测试:

注:以下数据为进程内通信的单次 round-trip 延迟,仅供参考。

框架 通信方式 平均延迟 P99 延迟
ROS2 (Humble) Intra-process (FastDDS) ~850μs ~3.2ms
CyberRT Intra-process (Intra) ~45μs ~120μs
HyperFlow 共享内存零拷贝 ~8μs ~25μs

差距的核心原因:

  1. ROS2:数据需要经过 DDS 层的序列化/反序列化,即使是进程内通信
  2. CyberRT:进程内通过直接回调避免序列化,但仍有调度开销
  3. HyperFlow:直接读写共享内存指针,无序列化、无额外调度层

当然,这个对比不完全公平------ROS2 有 QoS、CyberRT 有优先级调度,它们做的事情更多。但在自动驾驶的核心链路上,延迟就是安全。


6. 写在最后

自研中间件不是炫技,而是需求驱动。如果你的项目对延迟、资源、诊断有极致要求,现有方案无法满足,那就值得投入。

但也要清醒地认识到代价:没有生态、没有社区、所有工具都要自建。我的建议是从最小可用版本开始,逐步演进------先搞定通信和调度,再扩展诊断和终端。

下一篇,我们将深入 HyperFlow 的通信层,聊聊如何从零设计一套共享内存零拷贝通信机制。


下期预告:《从零搭建自动驾驶中间件(二):共享内存零拷贝通信的工程实践》

相关推荐
回眸&啤酒鸭12 小时前
【回眸】OpenSwarm 多智能体协作系统实战指南
大数据·前端·人工智能
博图光电12 小时前
Libra 27105相关技术参数
人工智能·数码相机
IT_陈寒12 小时前
SpringBoot自动配置差点让我加班到凌晨
前端·人工智能·后端
yumgpkpm12 小时前
Acceldata ODP(Open Data Platform)3.3.6.4(RHEL9)保姆级完整安装手册
大数据·人工智能·hive·hadoop·kafka·hbase·cloudera
程序员cxuan12 小时前
腾讯又来一王炸,开源版 WorkBuddy 太夯了!
人工智能·后端·程序员
邓工说电13 小时前
智慧断路器安全吗?数据加密、离线保护与合规认证全解读
大数据·数据库·人工智能·智能断路器·炜晔科技
阿里云大数据AI技术13 小时前
Lance 数据检索怎么选,当然阿里云 Milvus 向量湖
人工智能
出海客13 小时前
跨境电商多语言客服知识库怎么建:资料结构、检索边界与人工升级
大数据·人工智能
xsd2024111813 小时前
从自主导航到视觉读表:一台工业巡检机器人的全栈技术链路拆解
人工智能
袁哥大话安全13 小时前
巡隐WEBSHELL扫描软件
人工智能·安全·web