Zenoh:互联网计算领域的下一个重大突破
Zenoh 官方在 What is Zenoh? 开篇直接指出,Zenoh 将是互联网计算领域的下一个重大突破。


从技术角度看,Zenoh 是一种发布 / 订阅 / 查询协议(pub/sub/query protocol),它把流动中的数据(data in motion)、静态数据(data at rest)和计算(computations)统一到同一套通信抽象中。换一种更形象的说法,Zenoh 可以被理解为一种"数据解放"协议:它试图从多个维度打破数据被位置、传输方式和系统边界束缚的状态。
| 项目 | 环境 |
|---|---|
| OS | Ubuntu 24.04 (wsl) && Ubuntu 22.04 (virtual box) |
| Examples | eclipse-zenoh/zenoh-cpp 官方 examples |
目录
- [0. 什么是 Zenoh](#0. 什么是 Zenoh)
- [Zenoh 的诞生背景](#Zenoh 的诞生背景)
- 现有协议的问题
- [Zenoh 的优势](#Zenoh 的优势)
- [1. Zenoh 提出的核心概念](#1. Zenoh 提出的核心概念)
- [1.1 Key、Value 和 Key Expression](#1.1 Key、Value 和 Key Expression)
- [1.2 Data in Motion、Data at Rest 和 Computations](#1.2 Data in Motion、Data at Rest 和 Computations)
- [1.3 Put、Pub/Sub、Get/Queryable](#1.3 Put、Pub/Sub、Get/Queryable)
- [1.4 Peer、Client 和 Router](#1.4 Peer、Client 和 Router)
- [1.5 Scouting、Endpoint 和 Router Connect](#1.5 Scouting、Endpoint 和 Router Connect)
- [1.6 Liveliness、Storage 和性能示例](#1.6 Liveliness、Storage 和性能示例)
- [2. 用 C++ examples 展示 Zenoh 能力](#2. 用 C++ examples 展示 Zenoh 能力)
- [2.1 准备 C++ examples](#2.1 准备 C++ examples)
- [2.2 实验 1:一次性写入和订阅](#2.2 实验 1:一次性写入和订阅)
- [2.3 实验 2:持续数据流](#2.3 实验 2:持续数据流)
- [2.4 实验 3:查询和计算响应](#2.4 实验 3:查询和计算响应)
- [2.5 实验 4:观察 Data at Rest](#2.5 实验 4:观察 Data at Rest)
- [2.6 实验 5:Scouting、Router 和显式 Endpoint](#2.6 实验 5:Scouting、Router 和显式 Endpoint)
- [2.7 实验 6:通过 REST 读写 Zenoh 数据](#2.7 实验 6:通过 REST 读写 Zenoh 数据)
- [3. 常见问题](#3. 常见问题)
- 总结
- 参考
0. 什么是 Zenoh
Zenoh 这个名字包含两层含义:
- 哲学含义:致敬埃利亚的芝诺(前苏格拉底哲学家,以关于运动和无限的悖论闻名)和基提翁的芝诺(斯多葛学派创始人,代表极简、克制和有纪律的设计)。
- 缩写含义:Zero Endpoint Network Overhead Handover,表达的是在每一层都尽量减少不必要开销的设计目标。

Zenoh 的诞生背景
Zenoh 由 Angelo Corsaro 构思和设计。当时他担任 PrismTech CTO,同时也是 OMG Data Distribution Service(DDS)规范工作组的联合主席。在参与军工、航天和智慧城市等超大规模分布式系统项目时,他开始思考一种能够跨越不同系统尺度和网络边界的数据通信方式。
现有协议的问题
在 Zenoh 官方的叙述中,当时还没有一种协议可以覆盖分布式系统的完整跨度:
- DDS 在流动数据(data in motion)的发布 / 订阅场景中提供了很好的位置透明性,但既难以向上扩展到互联网尺度,也难以向下覆盖微控制器。
- CoAP 天然偏向云中心和 client/server 模型。
- MQTT 存在所谓的 broker paradox:即使两台设备位于同一个本地网络,通信仍然可能需要绕到远端云 broker 再返回。
这些系统就像一个东拼西凑的"数字怪物",各种协议片段被硬生生缝在一起,缺乏统一、透明的架构。
Zenoh 的优势
Zenoh 官方把它的优势概括为几个方向:
- 从云到微控制器的通信跨度:Zenoh 面向从服务器级硬件和网络,到嵌入式微控制器和受限网络的统一通信场景。官方用"释放数据"来形容这种能力:数据可以在 microcontroller、edge、cloud、data-center 之间纵向和横向流动,开发者也不必为了打通企业系统和嵌入式系统而拼接多套通信技术。
- 面向数据的位置透明性:发布 / 订阅让"流动中的数据"(data in motion)具备位置透明性,订阅者只表达兴趣,不需要知道发布者在哪里。Zenoh 进一步把这种思想扩展到"静态数据"(data at rest):查询可以不关心数据库的实际位置,由 Zenoh 选择网络中合适的数据源执行查询。
- 易用和高性能设计:官方强调 Zenoh 从设计之初就面向易用和高性能,目标是在适用范围内提供较高吞吐、较低延迟,并减少为追求性能而写出脆弱、难维护代码的需求。
- 能效设计:Zenoh 官方指出通信本身会消耗大量能源,因此协议设计时关注能效;这一点体现在 4-6 bytes 的最小 wire overhead,以及对通信局部性的利用上。
- 面向下一代应用 :官方列举了 Robotics、Autonomous Vehicles、Internet Gaming、Telecommunications 等方向。在机器人领域,Zenoh 被用于 robot-to-robot communication、互联网尺度监控管理和实时遥操作;在自动驾驶与移动出行方向,CARMA 和 Indy Autonomous Challenge 等项目也采用了 Zenoh。
总结来说,Zenoh 试图把互联网尺度的发布 / 订阅与地理分布式查询结合到同一套协议中。对机器人、车联网、边缘计算和云端监控这类系统来说,跨主机、跨网络边界、跨边缘与云端的数据流动,正是越来越常见的通信需求。
1. Zenoh 提出的核心概念
Zenoh 的核心不是把某一种已有协议重新包装,而是把数据流、存储查询和远端计算放进同一个命名空间和通信模型里。下面几个概念贯穿后面的实验。
1.1 Key、Value 和 Key Expression
Zenoh 使用 key 标识数据,例如:
text
xiaoha/robot/zenoh/temp
robot/alpha/camera/front
factory/line1/status
value 是 key 对应的数据内容。key expression 则用于表达对一组 key 的兴趣,例如:
text
xiaoha/robot/zenoh/**
robot/*/camera/**
factory/**/status
这种写法让发布者、订阅者、查询者和存储组件可以围绕同一套数据命名空间工作。发布 / 订阅关注"数据发生了变化",查询关注"现在有什么数据",二者不再是完全割裂的两套系统。
1.2 Data in Motion、Data at Rest 和 Computations
Zenoh 官方 overview 把它的目标概括为统一三类对象:
- data in motion:正在流动的数据,典型接口是 pub/sub。
- data at rest:已经存在于存储里的数据,典型接口是 get/query。
- computations:由 queryable 暴露出的计算或服务能力,查询请求到达后可以动态生成回复。
在传统系统里,这三类能力经常由不同协议和组件分别承担:消息总线负责流数据,数据库负责静态数据,RPC 或服务框架负责计算。Zenoh 试图用同一套 key expression 和通信抽象把它们连起来。
1.3 Put、Pub/Sub、Get/Queryable
后面的 C++ examples 会反复用到几类操作:
| 操作 | 含义 | 实验入口 |
|---|---|---|
put |
写入一个 key/value,并通知匹配的订阅者 | z_put |
pub/sub |
持续发布和订阅数据流 | z_pub / z_sub |
get/queryable |
发起查询,由匹配的 queryable 回复 | z_get / z_queryable |
storage |
在进程内保存数据,再通过查询取回 | z_storage |
这里有一个重要区别:订阅者看到的是"之后发生的数据变化",查询者看到的是"当前可查询的数据或计算结果"。Zenoh 把二者放在同一套 key 空间里,减少了应用层在多种协议之间搬运数据的工作。
1.4 Peer、Client 和 Router
Zenoh deployment 文档把部署形态分成几类:
- peer:应用之间直接发现和通信,适合简单局域网实验。
- client:应用连接到 router,由 router 帮助路由数据。
- router :负责连接多个应用或多个网络区域,跨主机、跨网段、跨区域时通常需要显式配置 endpoint。

默认局域网实验可以先使用 peer 模式。跨主机或跨网络边界时,更推荐显式启动 zenohd,再让应用通过 -e tcp/<router-ip>:7447 连接到 router。
1.5 Scouting、Endpoint 和 Router Connect
Zenoh 通过 scouting 发现网络中的其他 Zenoh 节点。局域网内可以依赖自动发现;跨网段、云主机、容器或防火墙环境中,自动发现往往不可靠,此时需要显式 endpoint:
text
tcp/192.168.1.10:7447
本文仓库保留了两个 JSON5 router 示例:
text
src/06_zenoh/config/router_host_a.example.json5
src/06_zenoh/config/router_host_b.example.json5
Host A 监听 tcp/0.0.0.0:7447,Host B 通过 connect/endpoints 连接 Host A。这种模式适合把两个网络区域接起来。
1.6 Liveliness、Storage 和性能示例
Zenoh examples 里还包含 liveliness、storage、roundtrip、throughput 等能力入口。本文把它们作为"观察 Zenoh 能力边界"的实验工具,不把一次本地测试结果扩展成通用性能结论。性能对比需要固定硬件、网络、消息大小、QoS、版本和统计方法。
2. 用 C++ examples 展示 Zenoh 能力
本节只使用 eclipse-zenoh/zenoh-cpp 官方 examples,不在仓库中额外维护 C++ 源码。zenoh-cpp README 说明,C++ API 是基于 zenoh-c 和 zenoh-pico 的 header-only C++ bindings,构建 examples 需要 C++17 编译器,并至少启用一个后端。
2.1 准备 C++ examples
安装基础工具:
bash
sudo apt update
sudo apt install -y git cmake g++ pkg-config
获取官方仓库:
bash
cd ~
git clone https://github.com/eclipse-zenoh/zenoh-cpp.git
cd zenoh-cpp
git checkout 1.9.0
git submodule update --init --recursive
本文的命令和实验结果以 1.9.0 为准。按 zenoh-cpp README 的说明,examples 默认使用 zenoh-c 后端,因此需要先安装 zenoh-c。下面把 zenoh-c 安装到用户目录 $HOME/.local,避免写入系统路径:
bash
# 安装 Rust/Cargo
sudo apt install -y curl build-essential cmake ninja-build pkg-config git ca-certificates
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
# 编译并安装 zenoh-c
cd ~/zenoh-cpp
cmake -S zenoh-c -B build-zenoh-c \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$HOME/.local"
cmake --build build-zenoh-c --target install -j"$(nproc)"
# 配置并编译 zenoh-cpp examples
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$HOME/.local" \
-DCMAKE_PREFIX_PATH="$HOME/.local" \
-DZENOHCXX_ZENOHC=ON \
-DZENOHCXX_ZENOHPICO=OFF
cmake --build build --target examples -j"$(nproc)"
examples 通常会生成在:
text
build/examples/zenohc
build/examples/zenohpico
如果只启用了 zenoh-c 后端,后续命令中的 ./z_sub、./z_put 等程序位于:
bash
cd ~/zenoh-cpp/build/examples/zenohc
ls
不同版本的 examples 名称和参数可能有少量变化,运行任意示例前可先查看帮助:
bash
./z_sub --help
./z_put --help
跨主机 router 实验还需要 zenohd。它不是 zenoh-cpp examples 的一部分,可按 Zenoh 官方 Getting Started / Deployment 文档安装或下载对应平台的 Zenoh 二进制包。安装后先确认:
bash
zenohd --version
2.2 实验 1:一次性写入和订阅
第一个终端启动订阅者,订阅 xiaoha/robot/zenoh/**:
bash
cd ~/zenoh-cpp/build/examples/zenohc
./z_sub -k 'xiaoha/robot/zenoh/**'
第二个终端写入一个 key/value:
bash
cd ~/zenoh-cpp/build/examples/zenohc
./z_put -k 'xiaoha/robot/zenoh/hello' -p 'Hello Zenoh'
预期现象:
text
z_sub 收到 xiaoha/robot/zenoh/hello 对应的数据

这个实验展示的是 Zenoh 的 key expression 匹配能力。订阅者并不关心发布者在哪里,只表达"对 xiaoha/robot/zenoh/** 感兴趣";只要写入的 key 落在这个范围内,就能被订阅者接收。
2.3 实验 2:持续数据流
第一个终端继续订阅:
bash
cd ~/zenoh-cpp/build/examples/zenohc
./z_sub -k 'xiaoha/robot/zenoh/stream'
第二个终端启动持续发布:
bash
cd ~/zenoh-cpp/build/examples/zenohc
./z_pub -k 'xiaoha/robot/zenoh/stream' -p 'robot status'
预期现象:

text
z_sub 持续收到 xiaoha/robot/zenoh/stream 上的新数据
这个实验对应 data in motion:数据不断产生、不断流动,订阅者按照 key expression 接收后续更新。传感器数据、状态上报、遥操作指令等都可以抽象成这类数据流。
2.4 实验 3:查询和计算响应
这个实验模拟客户端向机器人查询电池状态。第一个终端代表机器人端,启动 queryable,并把电池百分比设置为 85%:
bash
cd ~/zenoh-cpp/build/examples/zenohc
./z_queryable \
-k 'xiaoha/robot/zenoh/battery/status' \
-p '85%'
第二个终端代表客户端,查询机器人当前的电池状态:
bash
cd ~/zenoh-cpp/build/examples/zenohc
./z_get -s 'xiaoha/robot/zenoh/battery/status'
客户端预期收到:

text
Received ('xiaoha/robot/zenoh/battery/status' : '85%')
机器人端也会打印收到查询并返回 85% 的日志。这个实验对应 computations:查询命中 queryable 后,回复不一定来自数据库,也可以由程序动态计算生成。
2.5 实验 4:观察 Data at Rest
第一个终端启动进程内 storage 示例,保存 xiaoha/robot/zenoh/storage/** 范围内的数据:
bash
cd ~/zenoh-cpp/build/examples/zenohc
./z_storage -k 'xiaoha/robot/zenoh/storage/**'
第二个终端写入数据:
bash
cd ~/zenoh-cpp/build/examples/zenohc
./z_put -k 'xiaoha/robot/zenoh/storage/temp1' -p '25.0'
./z_put -k 'xiaoha/robot/zenoh/storage/temp2' -p '26.0'
第三个终端查询已保存的数据:
bash
cd ~/zenoh-cpp/build/examples/zenohc
./z_get -s 'xiaoha/robot/zenoh/storage/**'
预期现象:

text
z_get 可以取回前面写入的 xiaoha/robot/zenoh/storage/**
这个实验对应 data at rest:数据不只是被订阅者实时看到,也可以被存储组件保存下来,再通过查询接口读取。Zenoh 官方强调的位置透明性,也包括这类"查询时不需要关心数据实际存在哪里"的场景。
需要注意,z_storage 官方示例使用进程内存保存数据,程序退出后数据会丢失;它用于演示 storage/query 语义,不代表磁盘持久化。需要持久化时,应使用 zenoh-plugin-storage-manager 及对应的 filesystem、RocksDB、S3 等 backend。
2.6 实验 5:Scouting、Router 和显式 Endpoint
单机实验里,peer discovery 往往已经足够。跨主机实验更适合使用 router。本实验在两台独立主机上各启动一个 Zenoh router,并使用 JSON5 文件描述 router 之间的连接关系:

其中,z_put 和 z_sub 都以 client 模式连接本机 router;Router B 再通过显式 endpoint 主动连接 Router A。数据链路不依赖跨主机 multicast scouting。
txt
我的环境:
Host A: 192.168.3.70
Host B: 192.168.3.82
Host A 的 config/router_host_a.example.json5 监听所有网络接口上的 TCP 7447 端口。在项目根目录启动 Router A:
bash
# zenohd的安装参考:https://zenoh.io/docs/getting-started/installation/
# 终端1
zenohd -c src/06_zenoh/config/router_host_a.example.json5
在 Host B 启动 Router B:
bash
# 终端2
zenohd -c src/06_zenoh/config/router_host_b.example.json5
Router B 会监听本机 TCP 7447 端口,同时主动连接 tcp/<HOST_A_IP>:7447。
接下来验证跨 router 发布 / 订阅。Host B 启动订阅者,并以 client 模式连接本机 Router B:
bash
# 终端3
cd ~/zenoh-cpp/build/examples/zenohc
./z_sub -m client -e tcp/127.0.0.1:7447 -k 'xiaoha/robot/zenoh/**'
Host A 写入数据,并以 client 模式连接本机 Router A:
bash
# 终端4
cd ~/zenoh-cpp/build/examples/zenohc
./z_put -m client -e tcp/127.0.0.1:7447 \
-k 'xiaoha/robot/zenoh/router' \
-p 'through two routers'
预期现象:

这个实验展示了 router 和显式 endpoint 的作用:Router A 接受来自 Router B 的连接,Router B 负责把本地 client 接入 Router A 所在的网络区域。即使跨主机自动发现不可用,两个 router 仍然可以按照 JSON5 配置建立稳定的数据路径。
2.7 实验 6:通过 REST 读写 Zenoh 数据
Zenoh 官方 REST 插件把 HTTP PUT 和 DELETE 映射到 Zenoh pub/sub,把 HTTP GET 映射到 query/reply。这样,浏览器、脚本或不方便集成 Zenoh SDK 的程序也可以通过 HTTP 访问同一个 Zenoh 数据空间。
安装 zenohd、REST 插件和 curl:
bash
sudo apt update
sudo apt install -y zenohd zenoh-plugin-rest curl
第一个终端启动 Zenoh router,并在 TCP 8000 端口启用 REST API:
bash
zenohd --rest-http-port 8000
使用 REST PUT 发布数据
第二个终端启动 C++ subscriber,并以 client 模式连接本机 router:
bash
cd ~/zenoh-cpp/build/examples/zenohc
./z_sub -m client -e tcp/127.0.0.1:7447 \
-k 'xiaoha/robot/zenoh/rest/**'
第三个终端通过 HTTP PUT 发布机器人在线状态:
bash
curl -i -X PUT \
-H 'Content-Type: text/plain' \
--data 'online' \
http://127.0.0.1:8000/xiaoha/robot/zenoh/rest/status
REST API 返回 HTTP 200 OK,C++ subscriber 应收到:
text
>> [Subscriber] Received PUT ('xiaoha/robot/zenoh/rest/status' : 'online')

这说明 REST 写入会进入 Zenoh pub/sub 数据流,普通 Zenoh subscriber 不需要知道数据来自 HTTP 客户端。
使用 REST GET 查询电池状态
第四个终端启动实验 3 使用过的 C++ queryable,并返回固定电量 85%:
bash
cd ~/zenoh-cpp/build/examples/zenohc
./z_queryable -m client -e tcp/127.0.0.1:7447 \
-k 'xiaoha/robot/zenoh/battery/status' \
-p '85%'
第三个终端改用 HTTP GET 查询电池状态:
bash
curl -s \
http://127.0.0.1:8000/xiaoha/robot/zenoh/battery/status
在 Zenoh 1.9.0 环境中,返回结果如下:
json
[{"key":"xiaoha/robot/zenoh/battery/status","value":"ODUl","encoding":"zenoh/bytes","timestamp":null}]

REST API 的 JSON 响应会把 bytes 类型的 payload 编码为 Base64;其中 ODUl 解码后就是 85%。这个实验说明 REST client 可以查询由 C++ queryable 提供的数据,HTTP 与 C++ API 访问的是同一套 Zenoh key space。
3. 常见问题
1. 找不到 z_sub、z_put 等程序
先确认 examples 已经构建成功:
bash
cd ~/zenoh-cpp/build
cmake --build . --target examples
find examples -maxdepth 3 -type f -executable | sort
zenoh-cpp README 说明 examples 会放在 build/examples/zenohc 和 build/examples/zenohpico 目录下。实际目录取决于构建时启用的后端。
2. cmake 找不到后端库
zenoh-cpp 是 C++ bindings,使用时需要 zenoh-c 或 zenoh-pico 后端。使用本文默认的 zenoh-c 后端时,先确认它已经安装到 $HOME/.local,再重新配置:
bash
cmake -S . -B build \
-DCMAKE_INSTALL_PREFIX="$HOME/.local" \
-DCMAKE_PREFIX_PATH="$HOME/.local" \
-DZENOHCXX_ZENOHC=ON \
-DZENOHCXX_ZENOHPICO=OFF
cmake --build build --target examples
如果改用 zenoh-pico 后端,需要先安装开发包,并明确关闭默认启用的 zenoh-c:
bash
sudo apt install -y libzenohpico-dev
cmake -S . -B build-pico \
-DZENOHCXX_ZENOHC=OFF \
-DZENOHCXX_ZENOHPICO=ON
cmake --build build-pico --target examples
3. 跨主机收不到数据
先确认 Host A 已使用 JSON5 配置启动 Router A:
bash
zenohd -c src/06_zenoh/config/router_host_a.example.json5
再确认 Host B 能访问 Router A 的端口:
bash
nc -vz <HOST_A_IP> 7447
如果网络不通,检查防火墙、虚拟机网络模式和路由,并确认 router_host_b.example.json5 中的 Host A IP 已替换为实际地址。网络可达但仍然收不到数据时,继续检查 Router B 的启动日志,以及 z_put、z_sub 是否使用 -m client -e tcp/127.0.0.1:7447 连接了各自的本地 router。
4. z_get 没有返回数据
z_get 需要匹配到 queryable 或 storage。仅运行 z_put 并不一定意味着后续查询能取回数据,除非同时运行了 storage 示例或其他 queryable。可以先用最小组合验证:
bash
./z_queryable -k 'xiaoha/robot/zenoh/battery/status' -p '85%'
./z_get -s 'xiaoha/robot/zenoh/battery/status'
总结
本文围绕 Zenoh 的核心概念与基础通信能力展开,并通过 zenoh-cpp 官方 examples 验证了一次性写入、持续发布 / 订阅、查询响应、数据存储、跨主机 Router 通信以及 REST/C++ 互操作。通过这些实验可以看到,Zenoh 使用统一的 key expression 和通信模型,将 data in motion、data at rest 与 computations 连接在同一套数据空间中。
本文重点关注 Zenoh 的功能与通信模型,未对不同协议的性能作定量比较。若希望进一步了解 Zenoh 与 MQTT、Kafka、DDS 在吞吐量和时延方面的表现,可参考国立台湾大学发布的论文 《A Performance Study on the Throughput and Latency of Zenoh, MQTT, Kafka, and DDS》。后续文章将继续介绍 Zenoh 在 ROS 2 中的适配实现 rmw_zenoh 及其实际使用方式。
参考
- Zenoh 官方概述:What is Zenoh? https://zenoh.io/docs/overview/what-is-zenoh/
- Zenoh First App:https://zenoh.io/docs/getting-started/first-app/
- Zenoh Deployment:https://zenoh.io/docs/getting-started/deployment/
- Zenoh Abstractions:https://zenoh.io/docs/manual/abstractions/
- Zenoh Configuration:https://zenoh.io/docs/manual/configuration/
- Zenoh REST API:https://zenoh.io/docs/apis/rest/
eclipse-zenoh/zenoh-cppREADME:https://github.com/eclipse-zenoh/zenoh-cppzenoh-cppexamples:https://github.com/eclipse-zenoh/zenoh-cpp/tree/main/examples- CARMA 迁移讨论:https://discourse.ros.org/t/carma-migrating-to-ros-2-with-cyclonedds-and-zenoh/17541
- Indy Autonomous Challenge:https://www.indyautonomouschallenge.com/