MCAP + Zenoh + Protobuf:应用层统一数据交换与记录方案

2026/06/01

核心问题:MCAP(记录格式)、Zenoh(传输协议)、Protobuf(序列化格式)三者能否整合成一套应用层统一的数据交换与记录方案?


一、三者定位速览

维度ProtobufZenohMCAP
角色数据序列化格式传输协议/中间件持久化容器格式
解决什么数据"长什么样"数据"怎么流动"数据"怎么存、怎么查"
类比JSON 的二进制替代DDS/MQTT 的下一代替代rosbag 的标准化替代
所属方GoogleEclipse 基金会 / ZettaScaleFoxglove
成熟度极成熟(2008年起)1.0 版 2024 发布,ROS 2 采纳2021 年推出,ROS 2 Iron 起默认
开源协议BSDEPL 2.0 / Apache 2.0MIT

三者在设计哲学上没有冲突,反而高度互补。


二、Protobuf:统一的 Schema 层

2.1 核心能力

2.2 在整合方案中的角色

统一 Schema 定义层——所有应用层消息用 .proto 定义:

// sensor.proto
message ImuData {
  uint64 timestamp_ns = 1;
  float accel_x = 2;
  float accel_y = 3;
  float accel_z = 4;
  float gyro_x = 5;
  float gyro_y = 6;
  float gyro_z = 7;
}

message LidarScan {
  uint64 timestamp_ns = 1;
  repeated float points = 2;  // x1,y1,z1,intensity1, x2,y2,...
  uint32 num_points = 3;
}

同一个 .proto 定义驱动三个环节:


三、Zenoh:统一的数据传输层

3.1 核心机制

Zenoh 是一个"以数据为中心"的协议,核心概念:

概念说明类比
Key Expression层级化资源标识,支持通配符增强版 MQTT Topic
Pub/Sub发布/订阅,单向数据流MQTT Pub/Sub
Query/Get查询/响应,支持多播聚合分布式 RPC
Storage声明为某 Key 的持久化存储自动记录+回放
Session节点入网入口,自动发现、协商传输—

3.2 关键性能指标

3.3 Storage 插件机制(重点)

这是 Zenoh 与 MCAP 整合的关键。Zenoh 的 Storage 插件接口:

pub trait Storage: Send + Sync {
    async fn put(&mut self, key: Option<OwnedKeyExpr>, value: Value,
                 timestamp: Timestamp) -> ZResult<StorageInsertionResult>;
    async fn delete(&mut self, key: Option<OwnedKeyExpr>,
                    timestamp: Timestamp) -> ZResult<StorageInsertionResult>;
    async fn get(&mut self, key: Option<OwnedKeyExpr>,
                 parameters: &str) -> ZResult<Vec<StoredData>>;
    async fn get_all_entries(&mut self) -> ZResult<Vec<(OwnedKeyExpr, Timestamp)>>;
}

机制:Storage 内部自动订阅匹配的 Key Expression,所有 Pub 的数据自动写入存储;同时注册为 Queryable,响应外部查询。

已有后端:memory、filesystem、S3、InfluxDB、RocksDB、SQLite 等。

3.4 在整合方案中的角色


四、MCAP:统一的数据记录层

4.1 核心机制

MCAP 是一种追加写入的容器文件格式,专为 pub/sub 消息设计。

文件结构:

[Magic: 8 bytes]
[Header: Record]
[Channel Info: Record × N]   ← 声明有哪些通道(topic/schema)
[Message: Record × M]         ← 时间戳消息数据
[Chunk: Record × K]           ← 批量压缩块(可选)
[Message Index: Record]       ← 时间→偏移量索引(可选)
[Chunk Index: Record]         ← 块索引(可选)
[Attachment: Record × P]      ← 附件(可选)
[Data End: Record]
[Summary Offset: Record]      ← 尾部索引快速定位
[Magic: 8 bytes]

Record 类型:Header、Footer、Schema、Channel、Message、Chunk、MessageIndex、ChunkIndex、Attachment、Statistics、Metadata 等。

4.2 关键特性

特性说明
序列化无关原生支持 Protobuf、DDS(CDR)、ROS、JSON、MessagePack 等
追加写入流式写入,不回溯,崩溃时已写入数据可恢复
自包含Schema 嵌入文件,无需外部依赖即可解码
索引支持 Message Index + Chunk Index,远程低带宽下也可高效读取
压缩Chunk 级 zstd/lz4 压缩
CRC可选校验和,数据完整性保障

4.3 性能数据

指标MCAPROS Bag (rosbag2)提升
写入吞吐量217 MB/s82 MB/s+164%
文件体积 (1GB 原始)0.93 GB1.15 GB-19%
随机读取延迟8 ms23 ms-65%

4.4 在整合方案中的角色


五、三者整合架构

5.1 数据流全景

┌──────────────────────────────────────────────────────────────────┐
│                        应用层 (.proto Schema)                      │
│   ImuData, LidarScan, CameraFrame, ControlCmd, ...                │
└────────┬──────────────────────┬───────────────────────┬──────────┘
         │ serialize            │ serialize             │ serialize
         ▼                      ▼                       ▼
┌──────────────────────────────────────────────────────────────────┐
│                  Zenoh 传输层 (Pub/Sub + Query)                   │
│                                                                   │
│  ┌─────────┐    ┌─────────────┐    ┌─────────┐    ┌──────────┐  │
│  │ Sensor  │───▶│ Zenoh       │───▶│ Control │    │ Monitor  │  │
│  │ Node    │    │ Router/Mesh │    │ Node    │    │ Dashboard│  │
│  └─────────┘    └──────┬──────┘    └─────────┘    └──────────┘  │
│                        │                                          │
│                        ▼ Storage Plugin                           │
│               ┌────────────────┐                                  │
│               │ MCAP Writer    │  ← 新增:Zenoh→MCAP 桥接插件     │
│               │ (追加写入)      │                                  │
│               └───────┬────────┘                                  │
│                       │                                           │
└───────────────────────┼───────────────────────────────────────────┘
                        ▼
┌──────────────────────────────────────────────────────────────────┐
│                   MCAP 持久化层                                   │
│                                                                   │
│  data_2026-06-01_mission_001.mcap                                 │
│  ├── Channel: /robot/sensor/imu      (ImuData)                   │
│  ├── Channel: /robot/sensor/lidar    (LidarScan)                 │
│  ├── Channel: /robot/cmd/velocity    (VelocityCmd)               │
│  └── Channel: /robot/status/battery  (BatteryStatus)             │
│                                                                   │
│  ◀── Foxglove Studio 直接加载可视化                               │
│  ◀── mcap-cli 命令行查询/导出                                     │
│  ◀── Python/C++ SDK 离线分析                                      │
└──────────────────────────────────────────────────────────────────┘

5.2 关键实现:Zenoh MCAP Storage Plugin

这是整合方案的核心胶水代码,需要新开发(目前不存在官方实现):

// 伪代码:Zenoh Storage Plugin 写入 MCAP
impl Storage for McapStorage {
    async fn put(&mut self, key: Option<OwnedKeyExpr>,
                 value: Value, timestamp: Timestamp) -> ZResult<StorageInsertionResult> {
        let channel_name = key.as_ref().unwrap().as_str();

        // 首次写入该 channel 时注册 Schema + Channel
        if !self.channels.contains(channel_name) {
            self.writer.register_channel(
                channel_name,
                "protobuf",                         // encoding
                self.schema_registry.get(channel_name), // schema bytes
            )?;
        }

        // 追加写入消息
        self.writer.write_message(
            channel_name,
            timestamp.to_nanos(),
            &value.payload.bytes(),
        )?;

        // 定期刷盘 + 建索引
        if self.buffered_count % 1000 == 0 {
            self.writer.flush()?;
        }

        Ok(StorageInsertionResult::Inserted)
    }

    async fn get(&mut self, key: Option<OwnedKeyExpr>,
                 parameters: &str) -> ZResult<Vec<StoredData>> {
        // 从 MCAP 索引中读取历史数据
        let reader = McapReader::open(&self.file_path)?;
        let msgs = reader.read_messages_by_channel(
            key.unwrap().as_str(),
            parse_time_range(parameters),  // start_ns..end_ns
        )?;
        Ok(msgs.into_iter().map(|m| StoredData {
            value: Value::from(m.data),
            timestamp: m.log_time,
        }).collect())
    }
}

5.3 分层职责总结

┌─────────────────────┬──────────────────────────────────────┐
│ 层                   │ 职责                                  │
├─────────────────────┼──────────────────────────────────────┤
│ .proto IDL          │ 定义数据结构,代码生成,版本演进       │
│ Zenoh Session       │ 服务发现、传输协商、路由               │
│ Zenoh Pub/Sub       │ 实时数据分发 (Data in Motion)         │
│ Zenoh Query         │ 按需查询最新/历史数据 (Data at Rest)   │
│ Zenoh Storage 插件   │ 桥接 → MCAP 持久化                    │
│ MCAP Container      │ 通道管理、追加写入、压缩、索引、CRC    │
│ MCAP File           │ 自包含的持久化文件,含完整 Schema       │
│ Foxglove Studio     │ 可视化、回放、调试                     │
│ mcap-cli            │ 命令行查/导/转                         │
└─────────────────────┴──────────────────────────────────────┘

六、可行性评估

6.1 天然兼容

6.2 已有实践

6.3 缺失环节

缺失项影响工作量估计
Zenoh→MCAP Storage Plugin需要自己开发,是胶水代码关键1-2 人周(Rust)
MCAP Chunk 级写入与 Zenoh batching 的对接性能优化的关键需要设计
时间同步(多传感器纳秒对齐)PTP/gPTP 集成硬件依赖,非软件问题
嵌入式端 MCAP 写入MCAP 目前主要面向 Linux 端C 库可用,需适配 RTOS

七、与其他方案对比

维度本方案 (Proto+Zenoh+MCAP)ROS 2 原生 (DDS + rosbag2)MQTT + ParquetgRPC + 自定义日志
实时传输延迟~10μs (LAN)~30-100μs~ms 级~100μs-ms
嵌入式适配极好 (300B 最小)差 (10-50MB 内存)中 (1-5MB)中
离线记录MCAP (高性能)rosbag2 (依赖 ROS)Parquet (无实时)各写各的
Schema 演进Protobuf (一流)ROS IDL (ROS 绑定)Avro/手写Protobuf
跨域组网原生支持DDS 需桥接Broker 星型需网关
可视化Foxglove StudioRviz2需自建需自建
去中心化是是否 (Broker)否
行业采用快速增长ROS 生态主流IoT 主流大厂内部

八、落地建议

8.1 推荐架构

Proto Schema Repo (git)
  ├── sensor/
  │   ├── imu.proto
  │   ├── lidar.proto
  │   └── camera.proto
  ├── control/
  │   └── cmd.proto
  └── status/
      └── health.proto

运行时:
  每个节点 = Zenoh Session + Proto 序列化 + (可选)MCAP 记录
  路由器 = Zenoh Router + MCAP Storage Plugin
  地面站 = Foxglove Studio(实时 Zenoh 连接 + MCAP 回放)

8.2 分阶段实施

  1. Phase 1:Schema 统一

    • 用 .proto 定义所有消息
    • CI 自动生成多语言代码
  2. Phase 2:Zenoh 传输

    • 部署 Zenoh Router
    • 现有节点改用 Zenoh Pub/Sub
    • 传输层从 TCP/UDP → SHM 优化板内 IPC
  3. Phase 3:MCAP 记录

    • 开发 Zenoh MCAP Storage Plugin
    • 路由器自动将指定 key 的数据落盘
    • 集成 Foxglove Studio 做可视化
  4. Phase 4:离线分析

    • Python SDK 批量处理 MCAP 文件
    • 训练数据 pipeline 对接

8.3 风险与注意事项


九、结论

可以整合,而且理论上非常契合。

三者形成了数据生命周期的完整闭环:

核心缺失的胶水代码是 Zenoh MCAP Storage Plugin(约 1-2 人周工作量),其余都是成熟可用的现成组件。

如果应用场景是机器人/自动驾驶/分布式嵌入式系统,这套组合是目前可见的最优雅的应用层数据架构方案之一——比 ROS 2 原生方案更轻量、更解耦,比 MQTT + 自定义日志更结构化、更易分析和可视化。


十、MCU 接入方案:Zenoh-Pico + Nanopb

补充问题:MCU 能否通过 Protobuf 融入这套生态?Linux 节点能否订阅 MCU 数据,同时下发执行命令?

10.1 结论:可以,而且极其轻量

MCU 不需要碰 MCAP。只需要两个嵌入式专用库:

组件作用资源占用适用平台
Zenoh-Pico通信协议栈~30KB RAM, ~100KB FlashSTM32, ESP32, nRF52, RP2040, …
NanopbProtobuf 序列化~8-15KB Flash (Cortex-M4 -Os)任意 C 编译器

两者组合总 Flash 占用约 110-130KB,可在中端 Cortex-M 及以上 MCU 上轻松运行。

10.2 架构:两端对称,中间透明

    MCU 侧 (STM32 / ESP32 / RP2040)          Linux 侧 (网关 / 地面站)
    ┌──────────────────────────────┐       ┌──────────────────────────────┐
    │                              │       │                              │
    │  .proto 定义 (共用)           │       │  .proto 定义 (共用)           │
    │  ↓  nanopb 生成 C 结构体      │       │  ↓  protoc 生成 C++/Python    │
    │                              │       │                              │
    │  ┌─────────────────────┐     │       │  ┌─────────────────────┐     │
    │  │ 传感器采集 → Nanopb │     │       │  │ Zenoh Subscriber    │     │
    │  │ 序列化 → Zenoh-Pico │─────┼───Zenoh Pub/Sub ───▶│ 解码 Protobuf      │     │
    │  │ publish()           │     │       │  │ → 可视化/分析       │     │
    │  └─────────────────────┘     │       │  └─────────────────────┘     │
    │                              │       │                              │
    │  ┌─────────────────────┐     │       │  ┌─────────────────────┐     │
    │  │ Zenoh-Pico ◀────────┼─────┼───Zenoh Pub/Sub ────│ Zenoh Publisher     │     │
    │  │ subscribe()         │     │       │  │ 编码 Protobuf       │     │
    │  │ Nanopb 解码 → 执行   │     │       │  │ → 控制命令下发       │     │
    │  └─────────────────────┘     │       │  └─────────────────────┘     │
    │                              │       │                              │
    └──────────────────────────────┘       │  ┌─────────────────────┐     │
                                           │  │ Zenoh Storage       │     │
                                           │  │ Plugin → MCAP 文件   │     │
                                           │  └─────────────────────┘     │
                                           └──────────────────────────────┘

10.3 关键组件详解

Zenoh-Pico:MCU 上的 Zenoh 协议栈

ESP32 示例(发布传感器数据):

#include <zenoh-pico.h>
#include "sensor.pb.h"  // nanopb 生成

void setup() {
    // WiFi 连接
    WiFi.begin(SSID, PASS);

    // 打开 Zenoh session,连到局域网 Router
    z_owned_config_t config;
    z_config_default(&config);
    zp_config_insert(z_config_loan_mut(&config),
                     Z_CONFIG_MODE_KEY, "client");
    zp_config_insert(z_config_loan_mut(&config),
                     Z_CONFIG_CONNECT_KEY, "tcp/192.168.1.100:7447");

    z_owned_session_t s;
    s = z_open(z_move(config));

    // 声明 Publisher
    z_owned_publisher_t pub;
    pub = z_declare_publisher(z_loan(s),
            z_keyexpr("robot/sensor/imu"), NULL);

    // 声明 Subscriber(接收命令)
    z_owned_subscriber_t sub;
    sub = z_declare_subscriber(z_loan(s),
            z_keyexpr("robot/cmd/motor"),
            z_move(callback), NULL);
}

void loop() {
    // 1. 采集传感器
    ImuData imu = {.timestamp_ns = now_ns(),
                   .accel_x = read_accel_x(), ...};

    // 2. Nanopb 序列化
    uint8_t buf[128];
    pb_ostream_t stream = pb_ostream_from_buffer(buf, sizeof(buf));
    pb_encode(&stream, ImuData_fields, &imu);

    // 3. Zenoh-Pico 发布
    z_publisher_put(z_loan(pub),
        stream.state,  // protobuf 二进制字节
        stream.bytes_written, NULL);

    delay(10);  // 100Hz
}

// 接收命令回调
void callback(const z_sample_t* sample, void* context) {
    MotorCmd cmd;
    pb_istream_t stream = pb_istream_from_buffer(
        sample->payload.start, sample->payload.len);
    pb_decode(&stream, MotorCmd_fields, &cmd);

    // 执行
    set_motor_speed(cmd.left_speed, cmd.right_speed);
}

Nanopb:MCU 上的 Protobuf

与 Linux 侧共享同一个 .proto:

// 这个文件两端共用
syntax = "proto2";

message ImuData {
  required uint64 timestamp_ns = 1;
  required float accel_x = 2;
  required float accel_y = 3;
  required float accel_z = 4;
  required float gyro_x = 5;
  required float gyro_y = 6;
  required float gyro_z = 7;
}

message MotorCmd {
  required float left_speed = 1;
  required float right_speed = 2;
}

MCU 侧:protoc --nanopb_out=. sensor.proto → 生成 sensor.pb.c / sensor.pb.h Linux 侧:protoc --cpp_out=. sensor.proto → 生成 C++ 类

10.4 网络拓扑选项

 选项 A:最小部署(单 Router)

   MCU(ESP32) ──WiFi──▶ Zenoh Router (Linux 网关)
                              │
                   ┌──────────┼──────────┐
                   ▼          ▼          ▼
              MCAP 记录   Foxglove   控制面板
              (自动落盘)  Studio     (Python)


 选项 B:无 Router(Peer 模式)

   MCU(ESP32) ◀──UDP Multicast──▶ Linux 节点
      │                                │
      └── 直接 Pub/Sub,无需中心节点 ──┘


 选项 C:串口桥接(无网络 MCU)

   MCU(STM32) ──UART──▶ Linux 网关 (运行 Zenoh Serial Bridge)
                              │
                              ▼
                         Zenoh 网络

10.5 资源占用对比

方案MCU FlashMCU RAM实时性协议兼容性
JSON + MQTT~80KB~15KB差 (ms级)文本,无 Schema
自定义二进制 + raw socket~40KB~5KB好差,每次改协议手写解析
Nanopb + Zenoh-Pico~120KB~35KB极好 (μs级)强,protobuf 向前兼容
ROS 2 micro-ROS~200KB+~50KB+好强 (ROS IDL)

10.6 与 Linux 侧的无缝对接

MCU 发布的数据在 Linux 侧(用前面调研的三件套)表现为:

  1. Zenoh Subscriber 直接收到 Protobuf 二进制,解码后处理
  2. Zenoh Storage Plugin → MCAP 自动将指定 key 的数据持久化
  3. Foxglove Studio 通过 Zenoh WebSocket 实时观看,也可打开 .mcap 回放

Linux 侧无需任何格式转换或协议适配——它看到的就是跟 MCU 一样的东西,只是传输路径和持久化方式不同。

10.7 已知限制


十一、参考资料

A 级:官方规范/站点

编号文档组织说明
[MCAP-SPEC]MCAP SpecificationFoxglove文件格式规范, https://mcap.dev/spec
[MCAP-GH]foxglove/mcapFoxglove (GitHub)参考实现,https://github.com/foxglove/mcap
[ZENOH]Eclipse ZenohEclipse Foundation协议规范与实现,https://zenoh.io
[ZENOH-GH]eclipse-zenoh/zenohZettaScale (GitHub)核心实现 (Rust),https://github.com/eclipse-zenoh/zenoh
[PROTO]Protocol BuffersGoogle序列化规范,https://protobuf.dev

B 级:行业标准/实现

编号文档说明
[ROS2-ZENOH]ROS 2 采纳 Zenoh 作为非 DDS RMWhttps://zenoh.io/blog/2021-04-28-ros2-integration/
[ROS2-MCAP]ROS 2 Iron 起默认 MCAP 存储ROS 2 官方文档
[ZENOH-STORAGE]Zenoh Storage Plugin 架构zenoh-plugin-storage-manager (GitHub)
[ZENOH-BACKEND-FS]zenoh-backend-filesystemGitHub eclipse-zenoh/zenoh-backend-filesystem
[IEEE-P3196]IEEE 机器人数据标准(制定中)MCAP 被纳入候选

C 级:辅助参考(非权威)