文章目录
基本概念
zenoh是一个开放的协议,与传统协议仅能覆盖单一协议栈层级不同,Zenoh 的设计从微控制器端无缝跨越至多数据中心云部署------全程仅需一套统一的 API,且线路开销仅为 5 字节,其具有以下特点:
- 动态数据:基于位置透明的键值表达式实现订阅/发布消息机制。
- 静态数据与计算:一种位置透明的查询/可查询抽象层,基于数据库或按需计算,或两者协作,支持地理分布式的查询。
**动态压缩:**头部字段根据实际值动态省略。例如,若没有附件(Attachment)或时间戳,相关字节完全不发送。
**分片机制:**大数据分片,接收端重组,避免大包阻塞小包
数据格式:
rust
[ Header (1-2 bytes) ] [ KeyExpr ID (varint) ] [ Payload Length (varint) ] [ Payload ] [ Timestamp (optional) ] [ Attachment (optional) ]
核心
分层设计
传输层之上构建了一层抽象层,支持多协议同时运行。
TCP/UDP、WebSocket、共享内存(当发布和订阅在同机器,自动切换到共享内存,实现零拷贝)
路由算法
Zenoh 使用基于Key Expression Tree的路由表。每个节点维护一个键空间树,当收到数据包时,根据 KeyExpr 的前缀匹配快速转发。支持多路径路由,自动选择延迟最低或带宽最大的路径。
节点分类
Zenoh 节点分为三种角色,可动态切换:
- Client (客户端):普通应用节点。
- Peer (对等节点):局域网内自动发现,直连通信,无中心依赖。
- Router (路由器):负责跨网段、跨云的路由聚合。
Zenoh通信模型
- Pub/Sub
发布订阅模式,支持Qos,支持历史数据回放
- Query/Reply
请求/响应模式
机制:客户端发送 Get(key),所有匹配该 Key 的 Queryable 节点返回数据。
优势:支持多播查询(一次请求,多个节点回复),天然适合服务发现和配置读取。
场景:读取最新配置、查询设备状态、联邦学习参数聚合。
- Storage/Compute(数据存储)
Storage:节点可以声明自己为某个 Key 的"存储器"。即使发布者离线,订阅者仍可从存储器获取最新值或历史序列。
Compute (Downsampling/Aggregation):可以在路由路径上插入"计算插件"。例如,在网关处自动将 100Hz 的传感器数据降采样为 10Hz 再上传云端,无需修改业务代码。
例子
发布端
rust
use zenoh::{key_expr::KeyExpr, Config};
use zenoh::bytes::ZBytes;
#[async_std::main]
async fn main() {
// 配置共享内存
let mut config = zenoh::Config::default();
config.insert_json5("transport/shared_memory/enabled", "true");
let image_data: Vec<u8> = vec![0; 1920*1080*3];
// 创建session
let session = zenoh::open(config).await.unwrap();
// let bytes = ZBytes::from(image_data);
let bytes: ZBytes = image_data.into();
// session.put("example/topic", bytes)
// .await
// .unwrap();
session.put("example/topic", "hello world")
.await
.unwrap();
}
订阅端:
rust
// use zenoh::prelude::*;
use zenoh::{key_expr::KeyExpr, Config};
use zenoh::bytes::ZBytes;
// 声明在编译期把async fn main展开
#[async_std::main]
async fn main() {
// config::peer() 生成节点配置
// unwrap() 解包成功拿到session 失败panic
let session = zenoh::open(Config::default()).await.unwrap();
let sub = session.declare_subscriber("example/topic")
.await
.unwrap();
while let Ok(sample) = sub.recv_async().await {
// |e|转换失败的错误对象
let payload = sample
.payload()
.try_to_string()
.unwrap_or_else(|e| e.to_string().into());
println!(
"Recv {} on {}: {}",
sample.kind(),
sample.key_expr().as_str(),
payload,
);
}
}
官方性能数据
