深入探索 Zenoh:对高速公路物联网中台数据架构的启示

深入探索 Zenoh:对高速公路物联网中台数据架构的启示

摘要 :本系列第一篇《对技术架构的启示》回答了"用什么通信"------Zenoh 如何以三类节点、三种拓扑、约 5 字节开销重构中台的接入与消息层。本篇把镜头转向数据架构 ,回答另一个更根本的问题:"数据到底该怎么存、怎么查、怎么算 ?" 传统中台的数据链路是"设备持续上报 → MQTT → Kafka → Flink → 数仓",数据被无差别地"推"上来、"囤"起来,等应用来"读"。而 Zenoh 的 Query / Queryable、Storage、Eval(按需计算) 三件套,提供了一条截然不同的路径:数据就近存储、按需查询、按需计算。本文将深入这三个原语的协议级语义,并推演它们对高速公路物联网中台数据架构的五重影响。


一、引言:数据架构的"另一半"问题

第一篇里,我们解决了"连通"的问题:设备怎么接入、消息怎么流转、边云怎么协同。但中台真正的价值,从来不在"连通"本身,而在"数据"上------采集到的数据怎么存、怎么查、怎么算,才是中台之为"中台"的根本。

先看清今天的数据链路长什么样。主流智慧高速中台的数据架构,几乎是一张定式的"流水线":设备以固定频率持续上报 → MQTT Broker 收 → Kafka 缓冲 → Flink 实时计算 → 数仓/中台落库 → 应用查库[1](#1)。这套链路在规模尚小时运转良好,但面对全国 30 万路摄像头、3 万余套 ETC 门架、2 万余套雷达、7000 余套气象站------以及广东交通集团单家就超过 101 万台 的机电设备[2](#2)------三个结构性问题日益突出:

  1. 数据被无差别地"推"上来 :不管有没有人用,所有数据都 7×24 小时地往中心搬运、存储、建索引。结果是全国高速数据平均利用率不足 40%(交通运输部 2022 统计口径,待核实)[3](#3)。
  2. 查数据要绕一大圈:即便只是想看"某隧道当前的 CO 浓度",也要走完"持续上报 → 落 Kafka → 入库 → 查库"的全程,时延和成本都被放大。
  3. 算力被钉死在中心:边缘侧的算力大多闲置,真正需要"现场计算"的场景(如隧道内本地联动)却只能等数据绕到中心算完再绕回来。

这三个问题的共同根源,是"推式全量沉淀 "这条链路本身。而 Zenoh 给出的答案,恰好是一套与之镜像相反的机制------拉式按需访问。本篇的主角,正是支撑这套机制的三件套:

  • Query / Queryable:数据"要查才有,不查不搬";
  • Storage:把"数据库"变成网络的一等公民,数据就地落库、就地应答;
  • Eval(按需计算):让"算"跟着"问"走,边端现场算完再返回。

在展开对中台的启示之前,我们需要先把这三个原语在协议层到底长什么样、怎么工作,讲得足够精确------这正是本篇比第一篇"更硬核"的地方。


二、读懂 Zenoh 的"数据面":两条流、三原语

2.1 两条独立的数据流

Zenoh 的数据面,由两条相互独立、机制迥异的流构成[4](#4):

  • 流① Pub/Sub(数据在运动中) :Publisher 通过 PUT(写入)/ DEL(删除)主动推送,Subscriber 订阅接收。这是**数据驱动、主动"推"**的模式。
  • 流② Request/Reply(数据在静止/计算中) :Querier 发起 get(协议上是 QUERY),由匹配的 Queryable/Storage 应答 REPLY,并以 RESPONSE_FINAL 结束。这是**需求驱动、主动"拉"**的模式。

#mermaid-svg-mWmEVovO1JqvzAOw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mWmEVovO1JqvzAOw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mWmEVovO1JqvzAOw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mWmEVovO1JqvzAOw .error-icon{fill:#552222;}#mermaid-svg-mWmEVovO1JqvzAOw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mWmEVovO1JqvzAOw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mWmEVovO1JqvzAOw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mWmEVovO1JqvzAOw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mWmEVovO1JqvzAOw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mWmEVovO1JqvzAOw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mWmEVovO1JqvzAOw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mWmEVovO1JqvzAOw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mWmEVovO1JqvzAOw .marker.cross{stroke:#333333;}#mermaid-svg-mWmEVovO1JqvzAOw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mWmEVovO1JqvzAOw p{margin:0;}#mermaid-svg-mWmEVovO1JqvzAOw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mWmEVovO1JqvzAOw .cluster-label text{fill:#333;}#mermaid-svg-mWmEVovO1JqvzAOw .cluster-label span{color:#333;}#mermaid-svg-mWmEVovO1JqvzAOw .cluster-label span p{background-color:transparent;}#mermaid-svg-mWmEVovO1JqvzAOw .label text,#mermaid-svg-mWmEVovO1JqvzAOw span{fill:#333;color:#333;}#mermaid-svg-mWmEVovO1JqvzAOw .node rect,#mermaid-svg-mWmEVovO1JqvzAOw .node circle,#mermaid-svg-mWmEVovO1JqvzAOw .node ellipse,#mermaid-svg-mWmEVovO1JqvzAOw .node polygon,#mermaid-svg-mWmEVovO1JqvzAOw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mWmEVovO1JqvzAOw .rough-node .label text,#mermaid-svg-mWmEVovO1JqvzAOw .node .label text,#mermaid-svg-mWmEVovO1JqvzAOw .image-shape .label,#mermaid-svg-mWmEVovO1JqvzAOw .icon-shape .label{text-anchor:middle;}#mermaid-svg-mWmEVovO1JqvzAOw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mWmEVovO1JqvzAOw .rough-node .label,#mermaid-svg-mWmEVovO1JqvzAOw .node .label,#mermaid-svg-mWmEVovO1JqvzAOw .image-shape .label,#mermaid-svg-mWmEVovO1JqvzAOw .icon-shape .label{text-align:center;}#mermaid-svg-mWmEVovO1JqvzAOw .node.clickable{cursor:pointer;}#mermaid-svg-mWmEVovO1JqvzAOw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mWmEVovO1JqvzAOw .arrowheadPath{fill:#333333;}#mermaid-svg-mWmEVovO1JqvzAOw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mWmEVovO1JqvzAOw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mWmEVovO1JqvzAOw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mWmEVovO1JqvzAOw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mWmEVovO1JqvzAOw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mWmEVovO1JqvzAOw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mWmEVovO1JqvzAOw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mWmEVovO1JqvzAOw .cluster text{fill:#333;}#mermaid-svg-mWmEVovO1JqvzAOw .cluster span{color:#333;}#mermaid-svg-mWmEVovO1JqvzAOw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mWmEVovO1JqvzAOw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mWmEVovO1JqvzAOw rect.text{fill:none;stroke-width:0;}#mermaid-svg-mWmEVovO1JqvzAOw .icon-shape,#mermaid-svg-mWmEVovO1JqvzAOw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mWmEVovO1JqvzAOw .icon-shape p,#mermaid-svg-mWmEVovO1JqvzAOw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mWmEVovO1JqvzAOw .icon-shape rect,#mermaid-svg-mWmEVovO1JqvzAOw .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mWmEVovO1JqvzAOw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mWmEVovO1JqvzAOw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mWmEVovO1JqvzAOw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 流② Query · 数据在静止/计算中(拉)
需求驱动 · 主动问
REPLY + FINAL
Querier

get(QUERY)
Queryable / Storage / Eval
流① Pub/Sub · 数据在运动中(推)
数据驱动 · 主动推
Publisher

PUT / DEL
Subscriber

两条流共用同一套 key 表达式命名与同一套声明式路由,但触发机制、数据方向、生命周期完全不同。理解"推"与"拉"的分野,是理解整篇文章的地基。

2.2 三个核心原语,其实是一件事的三种形态

很多人第一次接触 Zenoh 时,会被 Queryable、Storage、Eval 三个名词绕晕。其实在协议层,它们不是三个不同的线级原语,而是同一个"查询应答"机制的不同用法 [5](#5):

  • Queryable :注册在某个 key 表达式上的一个"应答者",收到 get 时返回结果(可现场计算、可读本地、可查库);
  • Storage :既是 Queryable 又是 Subscriber ------订阅 key 表达式把 PUT/DEL 落库,被 get 时把最新值答回去;
  • Eval :官方称之为"注册在 key 表达式上的一段计算",get 命中时触发执行,selector 参数就是它的实参[6](#6)。

换言之,"查询"是唯一的一等机制 :Storage 是"查询 + 持久化"的组合,Eval 是"查询 + 计算"的组合。这解释了后文一个看似矛盾、实则精妙的事实------Eval 在 1.x 里没有独立线级原语,它就是用 get 触发的一次函数调用。

2.3 为什么这很重要:从"消息管道"到"分布式数据库"

传统架构里,"消息"和"数据库"是两个割裂的世界:MQTT/Kafka 管消息,数仓管数据,中间靠 Flink 之类的"搬运工"缝合。Zenoh 的野心,是把这两个世界收拢到一套 key 表达式与一套查询机制里 。官方对此有一句可引用的原话[7](#7):

"Zenoh is the first technology to bring location transparency for data at rest, allowing queries to be expressed without any concerns on the actual location of data, i.e. the location of the data-bases."

(Zenoh 是首个为"静止数据"带来位置透明的技术:发起查询时,你无需关心数据到底存在哪个数据库里,Zenoh 自己会在网络中定位最优的数据集并执行查询。)

这句话,正是本篇全部"数据架构启示"的理论起点。


三、Query / Queryable 深挖:一次查询在协议层发生了什么

3.1 一次 get 的解剖

在协议层,一次 get 是两层消息的嵌套:REQUEST(0x1C) 承载 key 表达式 + request_id + 扩展项,其内部再包裹 QUERY(0x03) 承载 consolidation 一个字节 + 参数字符串 + 扩展项[8](#8)。REQUEST 的扩展里可以携带 QoS、Timestamp、NodeId、QueryTarget、Budget(预算)、Timeout(超时)等控制信息。

这套设计把"路由寻址"和"查询语义"拆成了两层:REQUEST 决定发给谁 ,QUERY 决定怎么算。

3.2 QueryTarget:问"一个",还是问"所有"

QueryTarget 决定一次查询要路由到哪些应答者[9](#9):

QueryTarget 值 含义 高速场景
BestMatching 0x00(默认) 只路由到单一"最佳匹配"的 Queryable 明确知道要查哪个隧道
All 0x01 路由到所有匹配的 Queryable 跨路段汇总同一指标
AllComplete 0x02 路由到所有声明了 complete 的匹配 需要"完整覆盖、缺一不可"

一个直观的对应:中台若想知道"全路段 1 的所有隧道当前 CO 浓度",就用 QueryTarget::All 发起一次 get 高速/路段1/**/co,查询会同时路由到每个隧道的 Queryable/Storage,各自应答、汇总返回------一次查询替代了"逐隧道轮询"或"所有隧道持续上报"两种旧方案。

3.3 ConsolidationMode:谁来"合并",怎么"合并"

当 All 命中了 N 个应答者,返回 N 份结果时,谁来合并、怎么合并?这就轮到 ConsolidationMode(一个字节的枚举)出场[8](#8):

ConsolidationMode 值 语义
Auto 0x00(默认) 由 Queryable 自身的偏好决定如何合并
None 0x01 不合并,把每份回复原样转发给查询方
Monotonic 0x02 若同一 key 已发出过更新时间戳的值,则丢弃旧值,立即转发
Latest 0x03 暂存、每个 key 只回传时间戳最高(最新)的那份

注:Latest 是 Zenoh 1.x 的官方名称;早期 0.x 版本对应概念常写作 "latest-per-key",引用时请注意版本差异[8](#8)。

这里的合并发生在网络沿途,而非中心服务器------Monotonic/Latest 让"只取每个 key 的最新值"这种聚合,在最靠近数据的地方就地完成,而不是把所有值都拉回中台再筛。

3.4 Selector 与参数:查询即"函数调用"

Selector 的完整形态是 keyexpr?arg1=val1;arg2=value%202------key 表达式之后,可以用 ? 携带参数,参数用 ; 分隔、URL 编码[10](#10)。其中 _time 是已经标准化的保留参数,用于时间范围查询(详见 Storage 一节)。

这个设计的意义在于:查询不只是"取数",还能"传参" 。官方明确说,参数的作用就是"向 Queryable 传参,可当作 RPC 的参数"[6](#6)。这意味着 get 天然是一个分布式函数调用的原语------这正是 Eval 的机制基础。

3.5 Reply 与 Final:一次查询如何"结束"

每个应答者先回 RESPONSE(0x1B),其内包裹 REPLY(0x04)(回复的值实际上是一个完整的 PUT,因此自带完整的时间戳、编码与来源语义 )或 ERR(0x05);最后每个独立源各回一条 RESPONSE_FINAL(0x1A)[11](#11)。查询方据此判断"还有没有结果在路上":当各源 FINAL 到齐、或 Timeout 到期、或 Budget 用尽,一次查询即告完成。

这套"Reply 带完整语义 + Final 明确收尾"的协议,保证了分布式查询在弱网、部分节点无响应的情况下,依然能给出确定性的结束条件------对高速公路这类链路不稳定的环境尤其关键。

下面是一段官方示例改编的极简代码,把上述机制落到"隧道 CO 浓度按需查询"这一具体场景(改编自 zenoh-python 官方示例)[12](#12)[13](#13):

python 复制代码
# 边端(隧道侧):声明一个 Queryable,收到 get 时现场读传感器
co_queryable = session.declare_queryable("高速/路段1/隧道A/co", complete=True)
for query in co_queryable.recv():
    co = read_co_sensor()                      # 现场按需读取
    query.reply(query.key_expr, json.dumps({"co": co}))

# 中台(中心侧):按需查询,不查的时候隧道侧什么都不发
replies = session.get("高速/路段1/隧道A/co")
for reply in replies:
    print(reply.ok.payload)                    # 拿到即时的 CO 值

这段代码的隐喻是:在旧架构里,"读一次 CO"意味着隧道侧常年累月地上报;在 Zenoh 里,它意味着中台问一次、隧道侧算一次。 数据流动的方向和时机,被彻底改变了。


四、Storage 深挖:把"数据库"变成网络的一等公民

4.1 既是订阅者,又是应答者

Storage 在协议上没有任何新原语------它就是一个 Subscriber 加上一个 Queryable 的组合 :以 Subscriber 身份订阅 key 表达式,把收到的 PUT/DEL 持久化;以 Queryable 身份应答 get,返回每个匹配 key 的最新值 [5](#5)[6](#6)[14](#14)。

这套"二合一"带来了一个关键性质:存储在 Zenoh 里是"透明"的。查询方不必区分"这条数据是边端设备实时产生的,还是 Storage 里存的历史值"------两者用的是同一条查询路径。这正是 2.3 节"位置透明"的具体落地。

4.2 三层模型:backend / volume / storage

Storage 由 Storage Manager 插件 统一管理,内部是一个清晰的三层模型[15](#15):

  • backend :存储引擎,以动态库 libzbackend_<name>.so 形式加载;
  • volume:同一个 backend 的"已命名实例"------例如两个 InfluxDB 实例就是两个 volume,可各自配置;
  • storage :把某个 key 表达式(可 strip_prefix 去前缀)+ 一个 volume 绑定起来。

#mermaid-svg-SkuUMJBuAJP24f1D{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SkuUMJBuAJP24f1D .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SkuUMJBuAJP24f1D .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SkuUMJBuAJP24f1D .error-icon{fill:#552222;}#mermaid-svg-SkuUMJBuAJP24f1D .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SkuUMJBuAJP24f1D .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SkuUMJBuAJP24f1D .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SkuUMJBuAJP24f1D .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SkuUMJBuAJP24f1D .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SkuUMJBuAJP24f1D .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SkuUMJBuAJP24f1D .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SkuUMJBuAJP24f1D .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SkuUMJBuAJP24f1D .marker.cross{stroke:#333333;}#mermaid-svg-SkuUMJBuAJP24f1D svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SkuUMJBuAJP24f1D p{margin:0;}#mermaid-svg-SkuUMJBuAJP24f1D .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SkuUMJBuAJP24f1D .cluster-label text{fill:#333;}#mermaid-svg-SkuUMJBuAJP24f1D .cluster-label span{color:#333;}#mermaid-svg-SkuUMJBuAJP24f1D .cluster-label span p{background-color:transparent;}#mermaid-svg-SkuUMJBuAJP24f1D .label text,#mermaid-svg-SkuUMJBuAJP24f1D span{fill:#333;color:#333;}#mermaid-svg-SkuUMJBuAJP24f1D .node rect,#mermaid-svg-SkuUMJBuAJP24f1D .node circle,#mermaid-svg-SkuUMJBuAJP24f1D .node ellipse,#mermaid-svg-SkuUMJBuAJP24f1D .node polygon,#mermaid-svg-SkuUMJBuAJP24f1D .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SkuUMJBuAJP24f1D .rough-node .label text,#mermaid-svg-SkuUMJBuAJP24f1D .node .label text,#mermaid-svg-SkuUMJBuAJP24f1D .image-shape .label,#mermaid-svg-SkuUMJBuAJP24f1D .icon-shape .label{text-anchor:middle;}#mermaid-svg-SkuUMJBuAJP24f1D .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SkuUMJBuAJP24f1D .rough-node .label,#mermaid-svg-SkuUMJBuAJP24f1D .node .label,#mermaid-svg-SkuUMJBuAJP24f1D .image-shape .label,#mermaid-svg-SkuUMJBuAJP24f1D .icon-shape .label{text-align:center;}#mermaid-svg-SkuUMJBuAJP24f1D .node.clickable{cursor:pointer;}#mermaid-svg-SkuUMJBuAJP24f1D .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SkuUMJBuAJP24f1D .arrowheadPath{fill:#333333;}#mermaid-svg-SkuUMJBuAJP24f1D .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SkuUMJBuAJP24f1D .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SkuUMJBuAJP24f1D .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SkuUMJBuAJP24f1D .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SkuUMJBuAJP24f1D .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SkuUMJBuAJP24f1D .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SkuUMJBuAJP24f1D .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SkuUMJBuAJP24f1D .cluster text{fill:#333;}#mermaid-svg-SkuUMJBuAJP24f1D .cluster span{color:#333;}#mermaid-svg-SkuUMJBuAJP24f1D div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SkuUMJBuAJP24f1D .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SkuUMJBuAJP24f1D rect.text{fill:none;stroke-width:0;}#mermaid-svg-SkuUMJBuAJP24f1D .icon-shape,#mermaid-svg-SkuUMJBuAJP24f1D .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SkuUMJBuAJP24f1D .icon-shape p,#mermaid-svg-SkuUMJBuAJP24f1D .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SkuUMJBuAJP24f1D .icon-shape rect,#mermaid-svg-SkuUMJBuAJP24f1D .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SkuUMJBuAJP24f1D .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SkuUMJBuAJP24f1D .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SkuUMJBuAJP24f1D :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Storage Manager 插件
storage 定义

key_expr + strip_prefix + volume
volume(backend 的命名实例)
backend(存储引擎)
S3
InfluxDB
RocksDB
FileSystem
Memory(内置)

backend 定位 高速场景适用
Memory(内置) 进程内最新值缓存 设备最新状态、在线信息
InfluxDB 时序数据库 CO / 车流 / 气象等时序
RocksDB 嵌入式 KV 存储 边端本地持久化
FileSystem 文件 / 对象存储 视频抽帧、雷达点云大文件
S3 云对象存储 区域 / 中心云海量归档

官方当前提供的 backend 为 S3、InfluxDB、RocksDB、FileSystem ,外加内置的 Memory[15](#15)(早期版本还出现过 MySQL/MariaDB/PostgreSQL/SQLite 等 SQL 系 backend,但已不在现行文档列表中,落地时需核实)[15](#15)。

4.3 时间范围查询:_time

Storage 最有价值的能力之一,是按时间范围取历史 。现行标准用 _time 参数(Time DSL),而非早期的 starttime/stoptime[16](#16):

  • 区间:[start..end];时长:[start;duration];
  • 时间表达式:RFC3339 格式或 now(±offset),单位支持 u/ms/s/m/h/d/w;
  • now 在 Queryable 侧求值,避免各节点时钟不一致。

例如查某隧道过去 48 小时的 CO 历史:高速/路段1/隧道A/co?_time=[now(-2d)..now]。以 InfluxDB backend 为例,官方把 Zenoh 语义映射为[17](#17):storage → 数据库、key → measurement、每个 key/value → 一个 point (tag 标记 kind=PUT/DEL,field 记录 timestamp/encoding/value),get 默认返回每个 key 的最新 point,带 _time 范围则返回时序。

这意味着一件事:时序数据不再需要"MQTT→Kafka→Flink→InfluxDB"的搬运链,Storage + InfluxDB backend 本身就构成了一条就地落库、可时序查询的链路。

4.4 副本对齐(anti-entropy)

当同一份数据有多个 Storage 副本时,Zenoh 通过 anti-entropy 机制对齐:replica_config 里的 publication_interval / propagation_delay / delta 控制对齐节奏,靠"时间戳 + key"的哈希树比对差异[18](#18)。这对跨路段、跨区域部署"多副本就近存储"提供了最终一致性保障。


五、Eval 与 Zenoh-Flow:让"算"跟着"问"走

5.1 Eval:get 即函数调用

Eval 的官方定义很简洁[6](#6):

"A computation registered at a specific key expression, triggered by a get operation on a selector matching this key expression. The computation function will receive the selector as parameters."

(一段注册在某个 key 表达式上的计算,由匹配该表达式的 get 触发;计算函数以 selector 为参数。)

结合 3.4 节的 Selector 参数机制,get 就变成了一个天然的分布式函数调用 :查询方通过参数"传实参",Queryable 现场计算、把结果作为 REPLY 返回。官方把 Queryable 定位为 "distributed computed values"(分布式计算值),并明确把 "queryables / map-reduce" 并列为 Zenoh 对"计算"的抽象[7](#7)。

在高速公路里,Eval 的一个典型应用是"按需聚合 ":中台想知道"路段 1 当前所有隧道的 CO 最大值",不必让每个隧道持续上报、再由中心 Flink 聚合,而是发一次 get 高速/路段1/**/co_max,由边端一个 Eval 现场读取各隧道值、算完最大值再返回------计算发生在数据旁边,而不是数据搬运之后。

5.2 Zenoh-Flow:声明式的边缘数据流

如果说 Eval 是"一次性的按需计算",那 Zenoh-Flow 就是"持续的数据流计算"。官方对它的定义是 "the union of Zenoh and data flow programming"------一个从云端一路延伸到"物"端的声明式分布式计算框架 [19](#19)。

它的模型是:应用 = 一张有向图(Source / Operator / Sink) ,用描述符 + 节点实现声明出来,zfctl launch 部署;相邻节点在同一 runtime 内会自动改用内存通道(零拷贝);每个 flow 有唯一 ID 做数据隔离[20](#20)。

需要澄清的是:Zenoh-Flow 并非 Flink 的替代品,其定位是"Cloud-to-Thing 的边缘原生声明式数据流",与 Flink 这类云端批/流分析引擎是互补而非替代关系(官方未作显式对比,此为引申判断)。在高速场景里,它更贴合"路段侧边缘计算的编排"------把本地视频抽帧、事件检测、数据脱敏这些算子,编排成一段就近执行的数据流。
#mermaid-svg-6e64lg3FsPq45M47{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6e64lg3FsPq45M47 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6e64lg3FsPq45M47 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6e64lg3FsPq45M47 .error-icon{fill:#552222;}#mermaid-svg-6e64lg3FsPq45M47 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6e64lg3FsPq45M47 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6e64lg3FsPq45M47 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6e64lg3FsPq45M47 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6e64lg3FsPq45M47 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6e64lg3FsPq45M47 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6e64lg3FsPq45M47 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6e64lg3FsPq45M47 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6e64lg3FsPq45M47 .marker.cross{stroke:#333333;}#mermaid-svg-6e64lg3FsPq45M47 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6e64lg3FsPq45M47 p{margin:0;}#mermaid-svg-6e64lg3FsPq45M47 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6e64lg3FsPq45M47 .cluster-label text{fill:#333;}#mermaid-svg-6e64lg3FsPq45M47 .cluster-label span{color:#333;}#mermaid-svg-6e64lg3FsPq45M47 .cluster-label span p{background-color:transparent;}#mermaid-svg-6e64lg3FsPq45M47 .label text,#mermaid-svg-6e64lg3FsPq45M47 span{fill:#333;color:#333;}#mermaid-svg-6e64lg3FsPq45M47 .node rect,#mermaid-svg-6e64lg3FsPq45M47 .node circle,#mermaid-svg-6e64lg3FsPq45M47 .node ellipse,#mermaid-svg-6e64lg3FsPq45M47 .node polygon,#mermaid-svg-6e64lg3FsPq45M47 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6e64lg3FsPq45M47 .rough-node .label text,#mermaid-svg-6e64lg3FsPq45M47 .node .label text,#mermaid-svg-6e64lg3FsPq45M47 .image-shape .label,#mermaid-svg-6e64lg3FsPq45M47 .icon-shape .label{text-anchor:middle;}#mermaid-svg-6e64lg3FsPq45M47 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6e64lg3FsPq45M47 .rough-node .label,#mermaid-svg-6e64lg3FsPq45M47 .node .label,#mermaid-svg-6e64lg3FsPq45M47 .image-shape .label,#mermaid-svg-6e64lg3FsPq45M47 .icon-shape .label{text-align:center;}#mermaid-svg-6e64lg3FsPq45M47 .node.clickable{cursor:pointer;}#mermaid-svg-6e64lg3FsPq45M47 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6e64lg3FsPq45M47 .arrowheadPath{fill:#333333;}#mermaid-svg-6e64lg3FsPq45M47 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6e64lg3FsPq45M47 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6e64lg3FsPq45M47 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6e64lg3FsPq45M47 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6e64lg3FsPq45M47 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6e64lg3FsPq45M47 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6e64lg3FsPq45M47 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6e64lg3FsPq45M47 .cluster text{fill:#333;}#mermaid-svg-6e64lg3FsPq45M47 .cluster span{color:#333;}#mermaid-svg-6e64lg3FsPq45M47 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6e64lg3FsPq45M47 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6e64lg3FsPq45M47 rect.text{fill:none;stroke-width:0;}#mermaid-svg-6e64lg3FsPq45M47 .icon-shape,#mermaid-svg-6e64lg3FsPq45M47 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6e64lg3FsPq45M47 .icon-shape p,#mermaid-svg-6e64lg3FsPq45M47 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6e64lg3FsPq45M47 .icon-shape rect,#mermaid-svg-6e64lg3FsPq45M47 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6e64lg3FsPq45M47 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6e64lg3FsPq45M47 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6e64lg3FsPq45M47 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Source

(视频流/传感器)
Operator

(抽帧/检测)
Operator

(聚合/脱敏)
Sink

(落 Storage / 上云)


六、位置透明与时间:data at rest 的两块基石

上一章讲的是"怎么查、怎么算",这一章补上让它们得以成立的两块底层基石。

6.1 位置透明:查询与存储的彻底解耦

2.3 节引用了官方"location transparency for data at rest"的原话。它的工程含义是:中台应用发起一次查询,既不知道、也不需要知道数据此刻在哪个路段、哪个节点、哪个数据库里。Zenoh 依据声明式路由自动定位最优的数据源,把查询路由过去、把结果带回来。

这对高速公路的"三级云"(路段云---区域云---中心云)意味着:数据可以自由地就近存放,而应用的查询逻辑无需随数据位置改变 。[21](#21)这是一切"边端就近存储"方案得以落地的关键------否则"数据存到了边端,中台却查不到"的噩梦会立刻出现。

6.2 HLC 时间戳:无需共识的全局时序

分布式查询与存储,绕不开"时序":多个节点各自返回一份值,谁新谁旧?Zenoh 的答案是 HLC(Hybrid Logical Clock) 时间戳[22](#22)[6](#6):

  • 64 位 NTP64 编码:高 32 位是秒,低 32 位是秒的小数部分,其中末 4 位被替换为逻辑计数器;
  • 理论分辨率约 3.5ns;
  • 保证时间戳唯一 + 满足 happened-before 全局序 ,且无需任何共识协议。

在数据层,HLC 至少有三个具体用途:① Storage 写入冲突的裁决;② Consolidation 的 Monotonic/Latest 判定"谁更新";③ 作为 InfluxDB point 的时间戳,支撑时序查询。

注:官方文档在 HLC 的"纪元"口径上存在一处不一致(规范按 RFC-5909 写 1900 年、手册与 uhlc 库按 Unix epoch 1970 年计算),引用具体换算时需留意[22](#22)。


七、启示:Zenoh 将如何重塑高速公路物联网中台的数据架构

前文把三个原语讲透了,现在回到本系列的问题原点:这些能力,会把中台的数据架构引向何方? 本节给出五重影响,主线仍是"架构升级路径"------从"推式全量沉淀的流水线",走向"拉式按需访问的分布式数据平面"。

先看现状的数据流水线:
#mermaid-svg-bEefZmSxTPCPtlk3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bEefZmSxTPCPtlk3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bEefZmSxTPCPtlk3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bEefZmSxTPCPtlk3 .error-icon{fill:#552222;}#mermaid-svg-bEefZmSxTPCPtlk3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bEefZmSxTPCPtlk3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bEefZmSxTPCPtlk3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bEefZmSxTPCPtlk3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bEefZmSxTPCPtlk3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bEefZmSxTPCPtlk3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bEefZmSxTPCPtlk3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bEefZmSxTPCPtlk3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bEefZmSxTPCPtlk3 .marker.cross{stroke:#333333;}#mermaid-svg-bEefZmSxTPCPtlk3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bEefZmSxTPCPtlk3 p{margin:0;}#mermaid-svg-bEefZmSxTPCPtlk3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bEefZmSxTPCPtlk3 .cluster-label text{fill:#333;}#mermaid-svg-bEefZmSxTPCPtlk3 .cluster-label span{color:#333;}#mermaid-svg-bEefZmSxTPCPtlk3 .cluster-label span p{background-color:transparent;}#mermaid-svg-bEefZmSxTPCPtlk3 .label text,#mermaid-svg-bEefZmSxTPCPtlk3 span{fill:#333;color:#333;}#mermaid-svg-bEefZmSxTPCPtlk3 .node rect,#mermaid-svg-bEefZmSxTPCPtlk3 .node circle,#mermaid-svg-bEefZmSxTPCPtlk3 .node ellipse,#mermaid-svg-bEefZmSxTPCPtlk3 .node polygon,#mermaid-svg-bEefZmSxTPCPtlk3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bEefZmSxTPCPtlk3 .rough-node .label text,#mermaid-svg-bEefZmSxTPCPtlk3 .node .label text,#mermaid-svg-bEefZmSxTPCPtlk3 .image-shape .label,#mermaid-svg-bEefZmSxTPCPtlk3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-bEefZmSxTPCPtlk3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bEefZmSxTPCPtlk3 .rough-node .label,#mermaid-svg-bEefZmSxTPCPtlk3 .node .label,#mermaid-svg-bEefZmSxTPCPtlk3 .image-shape .label,#mermaid-svg-bEefZmSxTPCPtlk3 .icon-shape .label{text-align:center;}#mermaid-svg-bEefZmSxTPCPtlk3 .node.clickable{cursor:pointer;}#mermaid-svg-bEefZmSxTPCPtlk3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bEefZmSxTPCPtlk3 .arrowheadPath{fill:#333333;}#mermaid-svg-bEefZmSxTPCPtlk3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bEefZmSxTPCPtlk3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bEefZmSxTPCPtlk3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bEefZmSxTPCPtlk3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bEefZmSxTPCPtlk3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bEefZmSxTPCPtlk3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bEefZmSxTPCPtlk3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bEefZmSxTPCPtlk3 .cluster text{fill:#333;}#mermaid-svg-bEefZmSxTPCPtlk3 .cluster span{color:#333;}#mermaid-svg-bEefZmSxTPCPtlk3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bEefZmSxTPCPtlk3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bEefZmSxTPCPtlk3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-bEefZmSxTPCPtlk3 .icon-shape,#mermaid-svg-bEefZmSxTPCPtlk3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bEefZmSxTPCPtlk3 .icon-shape p,#mermaid-svg-bEefZmSxTPCPtlk3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bEefZmSxTPCPtlk3 .icon-shape rect,#mermaid-svg-bEefZmSxTPCPtlk3 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bEefZmSxTPCPtlk3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bEefZmSxTPCPtlk3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bEefZmSxTPCPtlk3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 设备持续上报

(固定频率)
MQTT Broker
Kafka 缓冲
Flink 实时计算
数仓 / 中台落库
应用查库

它的所有问题,都源于"持续上报 + 集中落库 + 中心算"这三个前置假设。而 Zenoh 的数据面,恰恰逐条拆掉了它们:

数据架构痛点 Zenoh 数据面能力
数据全部上云、无效搬运 Query 按需拉取,不查不上传
查数据要绕一大圈(推式) Queryable / Storage 直接 get
中心算力集中、边端闲置 Eval 按需边端计算 + Zenoh-Flow
时序历史难获取 _time 范围查询 + InfluxDB backend
消息管道 vs 数仓割裂 keyexpr 统一动静态数据命名
跨路段数据孤岛 QueryTarget::All 分布式查询

7.1 影响一:数据访问范式------从"推式全量沉淀"到"拉式按需访问"

这是最根本的一次翻转。传统链路里,数据的流动由生产者 决定(设备按固定频率推),消费与否全看运气,于是才有了"数据利用率不足 40%"的尴尬。Zenoh 的 Query/Queryable 把流动的主导权交还给消费者 :中台要什么,就 get 什么;不查的时候,边端一分不推。

对中台的直接收益是三重的:上行带宽大幅下降 (视频、雷达点云这类大负载尤其明显)、中心存储压力锐减 (不再囤积无人问津的原始流)、实时性提升("要即得",而非"等下一轮上报")。

7.2 影响二:数据存储分布------从"中心集中落库"到"边端就近存储"

当"查"和"存"彻底解耦后,数据就没必要都挤在中心了。Storage 的"Subscriber + Queryable 二合一"让数据在最靠近产生地的地方落库成为可能:隧道侧把 CO/VI 数据就近存进边端 Storage(RocksDB 或 InfluxDB),路段侧存视频抽帧结果,中心云只保留需要全局共享的聚合数据。

配合 6.1 的位置透明,中台应用完全感觉不到数据位置的改变 ------它照旧 get 高速/路段1/隧道A/co,由 Zenoh 自动路由到边端 Storage 应答。这正是"三级云"架构下,数据从"逐级上收"转为"就近安放、按需上取"的关键。

7.3 影响三:数据计算位置------从"中心 Flink"到"Eval + Zenoh-Flow 的边缘计算"

当存储下沉后,计算自然也要跟着数据走。Eval 让"一次 get 触发一次现场计算"成为可能,Zenoh-Flow 则把"持续的数据流计算"编排到边端。于是原本必须"绕到中心 Flink 算完再绕回来"的轻量实时计算,可以在边端就地完成------算力与数据同址,既降时延,又省回程带宽。

需要强调的边界是:这不是 要立刻用 Zenoh-Flow 取代 Flink。Flink 擅长的是大规模、有状态的云端批/流分析;Zenoh-Flow 补上的是 Flink 够不着的"边缘原生"那块。合理的分工是边端 Zenoh-Flow 做实时、轻量、本地闭环的计算,中心 Flink 保留给全局、重型的分析。

7.4 影响四:数据一致性与时序------HLC + Consolidation 的分布式语义

当数据分布到边端、查询分散到各节点后,"哪个值最新""多份结果怎么合并"就成了必须正面回答的问题。Zenoh 用 HLC 时间戳 提供"无需共识的全局时序",用 ConsolidationMode(Monotonic/Latest) 在网络沿途就地聚合------这让"跨路段取每个隧道最新 CO 值"这类分布式查询,有了确定性的语义,而不是"各节点时钟各说各话"。

对中台而言,这意味着**"分布式查询"第一次成为工程上可依赖的能力**,而非需要精心对时、手动合并的苦差事。

7.5 影响五:数据模型统一------keyexpr 终结"消息管道 vs 数仓"的割裂

最后,也是最具长期价值的一点:Zenoh 用同一套 key 表达式与查询机制 ,统一了"运动中的数据"(Pub/Sub)、"静止的数据"(Storage)与"计算"(Queryable/Eval)三类东西。传统架构里"消息管道"和"数仓"是两个世界、两套模型、两套团队;而在 Zenoh 里,订阅一条流、查询一份历史、触发一次计算,用的是同一个 key、同一条网络、同一套语义。

对数据架构的意义是:"数据中台"不再是一个需要被"喂饱"的被动仓库,而成为一张可查询、可计算、就近存储的"数据平面"------中台从"数据的终点",变成了"数据的访问入口"。

7.6 演进路线图:数据架构的"三步走"

与第一篇的技术架构演进相呼应,数据架构的升级同样应是渐进式的:
#mermaid-svg-ACKufoLas3vgiAVL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ACKufoLas3vgiAVL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ACKufoLas3vgiAVL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ACKufoLas3vgiAVL .error-icon{fill:#552222;}#mermaid-svg-ACKufoLas3vgiAVL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ACKufoLas3vgiAVL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ACKufoLas3vgiAVL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ACKufoLas3vgiAVL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ACKufoLas3vgiAVL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ACKufoLas3vgiAVL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ACKufoLas3vgiAVL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ACKufoLas3vgiAVL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ACKufoLas3vgiAVL .marker.cross{stroke:#333333;}#mermaid-svg-ACKufoLas3vgiAVL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ACKufoLas3vgiAVL p{margin:0;}#mermaid-svg-ACKufoLas3vgiAVL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ACKufoLas3vgiAVL .cluster-label text{fill:#333;}#mermaid-svg-ACKufoLas3vgiAVL .cluster-label span{color:#333;}#mermaid-svg-ACKufoLas3vgiAVL .cluster-label span p{background-color:transparent;}#mermaid-svg-ACKufoLas3vgiAVL .label text,#mermaid-svg-ACKufoLas3vgiAVL span{fill:#333;color:#333;}#mermaid-svg-ACKufoLas3vgiAVL .node rect,#mermaid-svg-ACKufoLas3vgiAVL .node circle,#mermaid-svg-ACKufoLas3vgiAVL .node ellipse,#mermaid-svg-ACKufoLas3vgiAVL .node polygon,#mermaid-svg-ACKufoLas3vgiAVL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ACKufoLas3vgiAVL .rough-node .label text,#mermaid-svg-ACKufoLas3vgiAVL .node .label text,#mermaid-svg-ACKufoLas3vgiAVL .image-shape .label,#mermaid-svg-ACKufoLas3vgiAVL .icon-shape .label{text-anchor:middle;}#mermaid-svg-ACKufoLas3vgiAVL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ACKufoLas3vgiAVL .rough-node .label,#mermaid-svg-ACKufoLas3vgiAVL .node .label,#mermaid-svg-ACKufoLas3vgiAVL .image-shape .label,#mermaid-svg-ACKufoLas3vgiAVL .icon-shape .label{text-align:center;}#mermaid-svg-ACKufoLas3vgiAVL .node.clickable{cursor:pointer;}#mermaid-svg-ACKufoLas3vgiAVL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ACKufoLas3vgiAVL .arrowheadPath{fill:#333333;}#mermaid-svg-ACKufoLas3vgiAVL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ACKufoLas3vgiAVL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ACKufoLas3vgiAVL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ACKufoLas3vgiAVL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ACKufoLas3vgiAVL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ACKufoLas3vgiAVL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ACKufoLas3vgiAVL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ACKufoLas3vgiAVL .cluster text{fill:#333;}#mermaid-svg-ACKufoLas3vgiAVL .cluster span{color:#333;}#mermaid-svg-ACKufoLas3vgiAVL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ACKufoLas3vgiAVL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ACKufoLas3vgiAVL rect.text{fill:none;stroke-width:0;}#mermaid-svg-ACKufoLas3vgiAVL .icon-shape,#mermaid-svg-ACKufoLas3vgiAVL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ACKufoLas3vgiAVL .icon-shape p,#mermaid-svg-ACKufoLas3vgiAVL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ACKufoLas3vgiAVL .icon-shape rect,#mermaid-svg-ACKufoLas3vgiAVL .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ACKufoLas3vgiAVL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ACKufoLas3vgiAVL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ACKufoLas3vgiAVL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 阶段一 · 查询旁路

保留流水线 · 隧道按需查询
阶段二 · 边端存储

就近落库 · 时序查询
阶段三 · 分布式数据平面

边缘计算 · 跨路段查询

  • 阶段一(查询旁路) :不动现有链路,在"隧道 CO 查询""设备状态巡检"等高频查询、低频变化的场景,用 Queryable 提供按需旁路,验证"拉式"的收益。
  • 阶段二(边端存储) :把边端数据就地落 Storage,中台通过位置透明的 get 访问;_time 打通时序历史。此时"数据全部上云"开始松动。
  • 阶段三(分布式数据平面) :引入 Eval/Zenoh-Flow 做边缘计算,用 QueryTarget::All 做跨路段分布式查询,中台从"数据仓库"转型为"数据平面"。

八、落地风险与建议:把"启示"变成"可执行"

与第一篇一脉相承,落地前需要正视几个现实约束:

  1. backend 成熟度要逐一核实 :当前官方文档只列了 S3 / InfluxDB / RocksDB / FileSystem / Memory 五类;SQL 系(MySQL/PostgreSQL/SQLite)曾出现但已不在现行列表。若中台强依赖 SQL 关系库,需先验证目标 backend 的能力边界。
  2. _time 是"标准化的保留参数",但覆盖度待验证:它是 RFC 规划的标准化项,各 backend 对时间范围查询的支持程度可能不一,落地前需逐 backend 压测确认。
  3. 与存量数仓/湖仓的边界 :Zenoh 数据面解决的是"实时、就近、按需"这一层,不应也无需替代已有的湖仓一体、批处理数仓 。正确的做法是让 Zenoh 数据面与既有数仓分层协作:边端/实时走 Zenoh,全局/历史深度分析走数仓。
  4. 运维与人才:分布式数据平面意味着"数据不再有一个集中的'家'",对可观测性(数据在哪里、谁在查、副本是否对齐)提出了更高要求,需配套指标与元数据体系。

建议的落地切口 :从"高频查询、低频变化"的场景切入(隧道 CO/VI 巡检、情报板状态、设备在线状态),先以阶段一的"查询旁路"验证 Query/Storage 的按需收益,再逐步向"边端存储 + 时序查询"推进------这条路径风险最低、收益最快、与既有系统冲突最小。


结语

第一篇讲清了 Zenoh 如何用"约 5 字节"和"三类节点三种拓扑"重构中台的通信架构 ;本篇则深入了它的数据面 ,看清了另一层更深的意图:把"数据库"和"计算"从中心化的后台,下沉、溶解到整个网络的每一处,用同一套 key 表达式与查询机制把它们统一起来。

对高速公路物联网中台而言,这指向一个清晰的演进方向:从"设备持续上报、中心集中落库、应用查库"的推式流水线,走向"数据就近存储、按需查询、按需计算"的拉式数据平面。 它不会一蹴而就,也不应盲目替代既有的 Kafka/Flink/数仓,但它为那个长期困扰行业的"数据利用率不足"问题,提供了一个结构性的、而非修修补补的解药。

这,就是 Zenoh 带给高速公路物联网中台"数据架构"的启示。


参考资料


  1. CSDN《数字底座数据流动机制》(MQTT/CoAP/Modbus+Kafka+Flink 技术栈) --- https://blog.csdn.net/zero_forever_lzm/article/details/158009815 ↩︎

  2. 广东交通集团接入 101 万台机电设备 --- https://www.sohu.com/a/907612407_649849 ↩︎

  3. 数据利用率不足 40%(交通运输部 2022 统计口径,待核实)--- http://jtdh.rmjtxw.com/anli/3/557.html ↩︎

  4. Zenoh 数据面总览(两条流、消息 ID、三层嵌套)--- https://spec.zenoh.io/spec/1.0.0/data-plane/index.html ↩︎

  5. Storage=Subscriber+Queryable、push vs pull --- https://spec.zenoh.io/spec/1.0.0/concepts/entities.html ↩︎ ↩︎

  6. Queryable/Storage/Selector/Eval/HLC 概念与 3.5ns 口径 --- https://zenoh.io/docs/manual/abstractions/ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  7. data at rest 位置透明原句 + queryables/map-reduce 定位 --- https://zenoh.io/docs/overview/what-is-zenoh/ ↩︎ ↩︎

  8. Query/QUERY/ConsolidationMode/REQUEST 线级定义 --- https://spec.zenoh.io/spec/1.0.0/data-plane/query.html ↩︎ ↩︎ ↩︎

  9. QueryTarget 枚举 --- https://spec.zenoh.io/spec/1.0.0/architecture/routing.html ↩︎

  10. Selector 语法与参数(RFC)--- https://github.com/eclipse-zenoh/roadmap/blob/main/rfcs/ALL/Selectors/README.md ↩︎

  11. RESPONSE/RESPONSE_FINAL/REPLY/ERR --- https://spec.zenoh.io/spec/1.0.0/data-plane/reply.html ↩︎

  12. Queryable 官方 Python 示例(z_queryable.py)--- https://github.com/eclipse-zenoh/zenoh-python/blob/main/examples/z_queryable.py ↩︎

  13. session.get 查询示例 --- https://zenoh.io/docs/getting-started/first-app/ ↩︎

  14. Storage 官方 Python 示例(z_storage.py)--- https://github.com/eclipse-zenoh/zenoh-python/blob/main/examples/z_storage.py ↩︎

  15. Storage Manager backend/volume/storage 三层模型与 backend 清单 --- https://zenoh.io/docs/manual/plugin-storage-manager/ ↩︎ ↩︎ ↩︎

  16. _time Time DSL 规范(RFC)--- https://github.com/eclipse-zenoh/roadmap/blob/main/rfcs/ALL/Selectors/_time.md ↩︎

  17. InfluxDB backend 映射与 _time 示例 --- https://github.com/eclipse-zenoh/zenoh-backend-influxdb ↩︎

  18. Storage 副本对齐(anti-entropy)--- https://zenoh.io/blog/2022-11-29-zenoh-alignment/ ↩︎

  19. Zenoh-Flow 定义与模型 --- https://github.com/eclipse-zenoh/zenoh-flow ↩︎

  20. Zenoh-Flow 官方博客(Source/Operator/Sink)--- https://zenoh.io/blog/2023-02-10-zenoh-flow/ ↩︎

  21. 中国公路学会《智慧高速公路系统"端-边-云"一体化架构技术指南》(征求意见稿) --- http://www.chts.cn/cms_files/filemanager/1389253025/attach/202412/f6b70c2d281241a28cb9ebd5b085ce54.pdf ↩︎

  22. HLC/NTP64 线级编码 --- https://spec.zenoh.io/spec/1.0.0/wire/primitives.html ↩︎ ↩︎

相关推荐
code_slave(码畜)1 小时前
微服务架构落地:读写分离架构 —— 主从延迟引发的“刚开单查不到”灾难与破局
spring boot·分布式·spring cloud·微服务·云原生·架构·读写分离
四季豆331 小时前
多点落地:大数据赋能各行各业
大数据
乐橙开放平台1 小时前
不用换摄像头,如何用乐橙把海康、大华、宇视统一到一个界面
笔记·物联网·自动化·音视频·智能家居
zyseo81 小时前
谷歌SEO 抓取日志分析脚本:解析访问日志,看穿 Googlebot 的抓取行为
大数据·数据库·搜索引擎·自动化
CServer_011 小时前
源码交付!AI+重型装备制造企业数智化平台
人工智能·物联网·制造
FII工业富联科技服务2 小时前
AI Factory 技术架构解析:数字孪生、自动化、机器人与 AI Agent 如何协同?
人工智能·架构·自动化
跨境数据猎手2 小时前
跨境电商和跨境独立站的最新业务模式
大数据·人工智能·物联网
花间相见2 小时前
【计算基础|MySQL08】索引(上):为什么是 B+ 树
数据结构·mysql·算法·架构
Nefu_lyh2 小时前
【Spark】02Spark 下载和入门:环境配置、简单的Spark独立应用(Scala版)
大数据·spark·scala