网络遥测(gNMI / Telemetry)接入与向量化索引实战
引言
在网络工程领域,"遥测"这个词并不新。
从 SNMP 时代开始,工程师就习惯通过设备暴露的计数器和状态,去理解网络正在发生什么。
但当网络规模扩大到成百上千台设备、控制面和数据面的耦合越来越复杂、业务变化节奏加快之后,"能看到"已经不再等于"能理解"。
gNMI 和 Streaming Telemetry 解决的是采集问题:
数据更多、更快、更细。
但它们并没有自动解决另一个更本质的问题------这些数据如何才能成为可被推理、可被检索、可被回溯的工程证据。
对于希望真正把 AI 引入网络工程实践的团队来说,遥测不是一个可选模块,而是整个系统的地基:
训练数据来自它,实时推理输入来自它,事故复盘和责任追溯也必须依赖它。
这一章不讨论"概念上的 AIOps",而是从工程实现角度,完整拆解一条现实可落地的路径:
如何把设备端的 gNMI / Streaming Telemetry,转化为结构化时序、事件流,并最终构建可用于相似态检索的向量索引体系。
1、目标与边界
1.1 本章做什么
这一章的目标非常明确,只聚焦三件事:
第一,工程化接入遥测数据 。
包括 gNMI / Streaming Telemetry 的订阅模型、时间戳处理、collector 设计,以及主流厂商设备在实现上的差异点和坑位。
第二,规范化遥测数据的表达方式 。
把来自不同厂商、不同设备、不同模型的原始遥测,映射到一个可演进、可审计的统一 schema 中,解决时间对齐、语义不一致和上下文缺失的问题。
第三,将规范化后的遥测构造成向量索引 。
不是为了炫技,而是为了支持真实工程需求:历史相似态检索、故障候选压缩、AI 模型输入与样本召回。
1.2 本章不做什么
为了避免章节失焦,这里明确不覆盖以下内容:
- 不深入展开异常检测算法、模型训练或数学推导(这些内容属于后续章节)。
- 不把向量化描述为"万能解法",也不假设某个模型或数据库能解决一切问题。
- 不做商业产品选型对比,也不讨论采购与成本问题,只讨论工程能力本身。
2、为什么必须工程化遥测,而不是"接上就算了"
2.1 遥测在网络工程链路中的真实位置
在真实网络中,遥测是唯一持续、客观记录网络状态变化的证据来源。
配置可以回滚,拓扑可以重建,但事故发生时:
- 接口 counters 是否异常跳变
- 队列深度是否持续累积
- BGP 会话是否频繁 flap
- 控制面资源是否被打满
这些都只能通过遥测来证明。
如果这些数据只是被画成曲线,或者在告警系统里触发一次性通知,那么它们的价值是被严重低估的。
真正有价值的遥测,必须能够:
- 被时间对齐
- 被语义理解
- 被回溯验证
- 被模型和检索系统消费
2.2 从 SNMP 到 gNMI:工程能力的变化点
SNMP 时代的问题并不仅仅是"老",而是工程能力的天然上限:
- 轮询模型导致数据稀疏且不可控
- 指标粒度固定,无法反映瞬态变化
- 时间语义模糊,难以对齐跨设备事件
gNMI 和基于 gRPC 的 Streaming Telemetry 在工程上带来的改变主要体现在三点:
第一,数据从拉取变成推送 ,设备开始主动描述自身状态变化。
第二,数据模型结构化 ,OpenConfig 等模型让路径本身携带语义。
第三,时间分辨率显著提升,支持亚秒级采样。
但这并不意味着"接上 gNMI 就万事大吉"。
这些能力如果不被正确建模和规范化,只会让后端系统更复杂。
2.3 为什么单靠 TSDB 不够
传统时序数据库非常擅长做一件事:
回答"某个指标在某段时间内发生了什么变化"。
但网络工程中,很多问题的本质不是单指标,而是状态组合:
- 多个接口同时丢包
- 控制面负载上升伴随路由震荡
- 变更后出现与历史事故"高度相似"的整体状态
这些问题更接近于:
"当前网络状态,像不像过去的哪一次?"
这正是向量索引存在的工程意义。
它不是替代 TSDB,而是补齐 TSDB 无法处理的"高维状态相似性"问题。
3、总体架构:一条可落地的工程流水线
3.1 端到端流程概览
一条完整、可演进的遥测到向量索引的流水线,通常包含以下阶段:
设备端遥测 → Collector → 原始存储 → Normalizer → Featureizer → Vectorizer → Vector Store → 上层应用
每一层都有清晰职责,不能混用,也不能省略。
3.2 各模块的工程职责
Collector
负责 gNMI 解码、订阅管理、时间戳处理、最基础的去重与流控。
Raw Store
保存未经处理的原始遥测数据,通常采用冷热分层:
热路径用于短期分析,冷路径用于审计、回放与训练。
Normalizer
解决厂商差异、字段对齐、时间对齐问题,把原始遥测映射为统一语义模型。
Featureizer
将连续时序和事件流转化为"状态片段",构造可用于相似性判断的特征。
Vectorizer
将特征映射为固定维度向量,并附带完整的元数据指针。
Vector Store
提供高效的近邻检索能力,支持时间过滤、标签过滤和回溯。
3.3 非功能需求必须一开始就设计
很多遥测系统失败,并不是功能不够,而是忽略了非功能需求:
- 时延目标是否明确
- 数据是否可审计、可回放
- schema 是否支持演进
- 大规模数据是否可治理
这些问题如果在系统上线后才考虑,几乎一定要推翻重来。
4、Collector 实战:gNMI 接入不是"连上就行"
4.1 gNMI 订阅模型的工程含义
从表面看,gNMI 的订阅类型只是配置选项:
- SAMPLE
- ON_CHANGE
- TARGET_DEFINED
但在工程上,它们代表的是完全不同的数据语义
4.2 时间戳:遥测系统的第一条生命线
一个非常常见、也非常危险的假设是:
"设备发来的时间戳就是准确的。"
在生产网络中,这个假设经常不成立:
- 设备本地时钟漂移
- 不同板卡采样周期不同
- 传输链路引入抖动
因此,Collector 必须同时保留:
- device timestamp
- collector ingest timestamp
并在后端根据漂移情况决定采用哪一个。
时间一旦错位,向量化和相似态检索就会建立在错误的因果关系之上。
4.3 Collector 的工程级要求
一个可用的 Collector,至少要具备以下能力:
- 并发管理大量订阅
- 支持断连重试与幂等恢复
- 支持回放原始 protobuf
- 支持滚动升级而不中断采集
这不是"锦上添花",而是长期可运维的前提。
5、Normalizer:语义统一与时间对齐的工程细节
5.1 为什么 Normalizer 是整条链路里"最脏、最累、却最关键"的一层
如果说 Collector 是体力活,Featureizer 是经验活,那么 Normalizer 就是那种没人爱做,但做不好一切都会塌掉的苦差事。
在现实网络中,你几乎不可能遇到"干净"的遥测数据:
- 同一个指标,不同厂商字段名不同
- 同一个字段,不同版本语义不完全一致
- 同一时间点,不同设备时间戳不可比
- 同一个对象,在不同模型下路径结构不同
Normalizer 的职责不是"清洗数据",而是完成一件更严格的事情:
把"厂商实现"还原成"工程语义"。
只要这一步失败,后续 Featureizer 和向量化做得再漂亮,都是在堆沙子。
5.2 语义统一不是字段映射,而是"语义对齐"
一个非常常见的错误,是把 Normalizer 理解为:
path A → path B
field_x → field_y
这种字段级映射,只解决了最表层的问题。
真正的难点在于,很多指标在不同设备上的"工程含义"并不完全相同,例如:
- interface counters 是否包含子接口
- queue depth 是瞬时值、平均值,还是滑动窗口
- CPU 利用率是否包含 control-plane 以外的线程
因此,一个工程级 Normalizer 至少要做三层对齐:
第一层,结构对齐
把不同模型、不同路径,映射到统一的逻辑对象,例如"接口""邻居""队列"。
第二层,语义对齐
明确每一个字段代表的工程含义,而不仅是名字相同。
第三层,可比性标注
当某些指标在不同设备之间天然不可比时,必须显式标注,而不是假装一致。
5.3 时间对齐:比你想象中更容易制造假因果
时间对齐是 Normalizer 中最容易被低估、但破坏性极强的一部分。
在生产网络中,以下情况几乎一定存在:
- 不同设备 NTP 漂移不同
- 设备上报时间是"采样时间"还是"发送时间"
- 不同板卡、不同子系统采样周期不同
如果你直接用 device timestamp 去拼跨设备状态,很容易得到一个看似合理、实际上完全错误的因果链。
工程上常见的做法是:
- 同时保留 device time 和 ingest time
- 对关键设备做漂移评估
- 在 Normalizer 中统一生成"工程时间轴"
这个"工程时间轴"不追求绝对精确,但必须在整个系统内自洽。
5.4 Normalizer 的输出契约
一个合格的 Normalizer 输出,必须满足以下条件:
- 每一条数据都有明确的工程对象归属
- 每一个字段都有稳定、可演进的语义定义
- 每一个时间戳都有明确来源说明
- 可以在未来版本中向后兼容
Normalizer 的输出,不是给人看的,而是给后续所有系统长期依赖的"事实层"。
5.5 设计统一 schema 的原则
- 最小可用性(Minimally Viable Schema):第一次不要把所有可能字段都纳入,先覆盖关键指标(interface counters、queue depth、cpu/mem、BGP adj/state)。
- 扩展性(Extensible Tagging):每条时序都应至少有 {device_id, role, site, region, vendor, software_version, topo_node_id} 这类标签。
- 语义保持(Preserve Semantics):对不同厂商的 semantically equivalent 字段不要盲目合并(例如不同厂商对 "drops" 的定义可能不同),需要在 metadata 中注明源解释。
5.6 时间对齐与窗口策略
- 事件 vs 指标:把"事件"视为瞬时发生(interface down、config-change),而"指标"应有固定采样窗口(rate, p50, p95)。
- 对齐策略:
- 固定窗口(例如 10s):适用于 rate 计算与短周期检测。
- 滑动窗口(例如 60s 滑动步长 10s):提高平滑性与短时突发感知。
- Missing data 处理:对短时间丢失使用插值或前向填充(仅在度量合理时),否则标注为缺失以避免误判。
5.7 标准化字段示例(JSON schema 片段)
下面示例展示了一个标准化后的 telemetry event(简化后):
{
"timestamp": "2025-12-16T08:30:12.123Z",
"device_id": "leaf-12",
"vendor": "cisco",
"site": "dc1",
"role": "leaf",
"path": "/interfaces/interface[name=Ethernet1]/state/counters",
"values": {
"in_octets": 123456789,
"out_octets": 234567890,
"in_unicast_pkts": 123400,
"out_unicast_pkts": 234500,
"in_errors": 12,
"out_errors": 0
},
"meta": {
"software_version": "17.3.1",
"time_source": "device",
"raw_message_id": "abc123def"
}
}
这个结构中的 meta 和 tags,将直接成为后续向量数据库中的 Filter 索引字段。
5.8 特殊处理:BGP 与路由表的规范化
- BGP RIB/NLRI 是高基数(prefix count 巨大)的数据,需要做两层处理:
- 对控制面语义(peer up/down、route withdrawal)做事件化记录;
- 对实际 prefix 集合做 summary(prefix count per peer, prefixes learned, bestpath changes)而非单纯存全量 prefix(除非有特殊需求并且有能力存储)。
- 对 prefix 的历史判定要保留 origin AS、communities、local-pref 等关键 attributes。
对于 BGP Flap 等复合震荡,Normalizer 只负责记录原子事件,真正的'震荡状态判定'将交由下一节 Featureizer 处理。
6、Featureizer:把"遥测噪声"压缩成可推理的工程状态
6.1 Featureizer 的真实角色:不是算特征,而是"切状态"
在绝大多数工程团队中,"特征工程"这个词很容易被误解为算法问题,仿佛只要喂给模型足够多的指标,模型自然会学会一切。
在网络遥测体系里,这种思路几乎一定失败。
原因很简单:
遥测本身不是"样本",而是连续、嘈杂、语义混杂的状态流。
Featureizer 的首要职责,不是计算统计量,而是完成一件更基础、也更工程化的事情:
把连续时间上的遥测流,切分成"可被比较、可被召回、可被复用"的状态片段(state slice)。
只有当"状态"被明确切出来,后续的向量化、相似性检索、因果回放才有意义。
6.2 为什么"按指标算特征"在网络里行不通
一个常见但危险的实现方式是:
- 每个指标算 avg / max / min / p95
- 固定时间窗口(比如 5 分钟)
- 所有指标拼成一个大向量
这种方式在日志、业务监控中尚且勉强可用,但在网络工程中会迅速失效,原因包括:
第一,网络问题是强相关、多指标联动的
单个接口的丢包并不说明问题,但如果同时出现:
- 队列积压
- ECMP 链路负载失衡
- 上游 BGP 邻居 flap
那才是一个"状态"。
第二,固定时间窗口会切断因果链
真实故障往往表现为:
- 某个指标先异常
- 若干秒或几十秒后,另一些指标跟随变化
粗暴的窗口统计会直接抹平这种时序关系。
第三,不同遥测类型本就不该被同等对待
SAMPLE 型指标和 ON_CHANGE 型事件,语义完全不同,却常常被强行拼在一起。
6.3 Featureizer 的第一原则:以"状态变化"为中心,而不是时间
一个工程可用的 Featureizer,必须遵循一个核心原则:状态片段的边界,应由"网络本身的物理状态变化"决定,而非人为划定的"钟表时间"。 例如,接口 Flap 导致的路由震荡可能持续 13 秒,那么这 13 秒就是一个完整的状态片段。如果强行用 1 分钟的固定窗口切割,这个因果链就会被切碎在两个不同的时间窗里,导致模型无法识别。
在实践中,这通常通过三类触发条件来实现:
- 事件触发:接口 up/down、邻居状态变化、协议状态迁移
- 阈值触发:速率、利用率、队列深度越过工程阈值
- 结构触发:拓扑、ECMP 组、策略生效路径发生变化
当触发发生时,Featureizer 会:
- 回溯一段"前置稳定期"
- 向前覆盖一段"后续演化期"
- 形成一个完整的状态切片
这比任何固定窗口都更贴近工程现实。
6.4 状态切片的最小工程单元
一个"状态片段"不是随意拼凑的数据块,而是一个有明确边界和语义的对象,至少应包含:
- 时间范围
- 起点、终点
- 是否完整、是否截断
- 参与对象
- 设备
- 接口 / 邻居 / VRF / 实例
- 关键指标轨迹
- 原始序列或降采样序列
- 触发原因
- 事件 / 阈值 / 结构变化
- 上下文引用
- 拓扑快照
- 配置版本指针
Featureizer 的输出,应该已经是一个"工程事件级对象",而不再是散乱的点。
6.5核心原则:将工程经验固化于 Featureizer。
**把工程经验固化进 Featureizer,而不是模型里,**一个成熟的 Featureizer,往往隐含了大量工程经验,例如:
- 接口 flap 前后,重点关注哪些指标
- BGP 会话异常时,哪些 counters 有判别力
- 控制面压力问题,CPU 之外必须联动哪些遥测
这些经验不应该寄希望于模型自动学会,而是应该被明确编码进 Featureizer 的规则中。
原因只有一个:**工程经验是稀缺且可解释的,而模型权重不是。**Featureizer 是把"工程直觉"系统化、可复制的关键位置。
6.6 Featureizer 与上游 Normalizer 的边界
需要特别强调的是:
- Featureizer 不解决厂商差异
- Featureizer 不修正字段语义
- Featureizer 不做时间对齐基础工作
这些都是 Normalizer 的职责。
Featureizer 假定输入已经是:
- 语义一致的
- 时间可对齐的
- 上下文可引用的
一旦 Featureizer 被迫去"补救"这些问题,系统复杂度会指数级上升。
7、Featureizer 输出的工程价值,而不仅是模型输入
7.1 不做向量,也依然有价值
即便你暂时不做向量化,Featureizer 的输出依然可以直接支撑多种工程能力:
- 故障候选状态的自动聚合
- 人工排障时的状态对齐视图
- 事故复盘中的"关键阶段"标注
很多团队低估了这一层,直接把遥测送进模型,结果是:
- 不可解释
- 不可复盘
- 不可复用
7.2 Featureizer 是"工程规模化"的关键门槛
在小规模网络中,资深工程师可以靠经验脑补状态变化。
但在大规模网络中:
- 不可能靠人脑记住所有组合
- 不可能每次事故都从零开始看曲线
Featureizer 的存在,本质上是在回答一个问题:
如何把少数人的工程经验,扩展成系统能力。
7.3、为向量化做准备:Featureizer 必须留下"可追溯性"
在进入向量化之前,Featureizer 还有一个不可妥协的要求:
任何被压缩、被聚合的特征,都必须能追溯回原始遥测。
这意味着:
- 特征 → 状态片段 → 原始遥测
- 每一层都有明确指针
- 没有"黑盒统计值"
否则,一旦向量检索结果被用于决策,却无法解释"为什么像",系统将无法在工程组织中生存。
8、向量化:从状态片段到可检索空间
8.1 为什么要向量化,而不是"再多写点规则"
到这里,很多工程师会自然地产生一个疑问:
既然 Featureizer 已经切出了状态,Normalizer 也统一了语义,为什么还非要搞向量?
原因是:规则只能回答"是不是这个问题",但向量可以回答"像不像某类问题"。
在真实网络中,你经常面对的是:
- 没见过的故障形态
- 多种问题叠加
- 边界条件触发的非典型表现
向量化的价值,不在于"预测",而在于压缩搜索空间。
8.2 向量化的输入,不是指标,而是"状态描述"
一个严重的设计错误是:
直接把指标数值送进向量模型。
正确的向量化输入,应该来自 Featureizer 输出的"状态片段",例如:
- 指标变化趋势(上升、下降、震荡)
- 多指标之间的相对关系
- 状态持续时间与阶段划分
- 触发事件的类型组合
换句话说,向量描述的不是"网络长什么样",而是:
"网络正在经历什么"。
8.3 固定维度不是目的,可比较性才是
向量化并不要求你使用多复杂的模型。
在工程实践中,更重要的是:
- 同类状态映射到相近空间
- 不同类状态能被明显区分
- 向量随 schema 演进而可重算
很多团队一开始就追求"高维""深模型",结果却发现:
- 向量不可解释
- 版本一升级,全量失效
- 历史数据无法对比
工程上,稳定性永远优先于炫技。
8.4 元数据不是附属品,而是向量的一部分
一个可用的向量对象,永远不只是一个 float 数组,它必须绑定完整元数据:
- 状态片段 ID
- 时间范围
- 设备 / 拓扑标签
- 配置版本指针
- Featureizer 规则版本
否则,向量一旦被命中,你将无法回答最基本的问题:"这到底是哪一次网络状态?"
向量不仅是数值,必须携带元数据(Metadata)。
9、向量索引在真实网络中的使用方式
9.1 相似态检索,而不是"智能诊断"
在真实工程中,向量索引最常见、也最可靠的用途是:
给工程师一个"历史参考集合"。
例如:
- 当前状态最像过去哪 5 次
- 那些状态最终是如何演化的
- 当时采取了什么操作
这不是让系统"替你下结论",而是让工程师不必从零开始思考。
9.2 向量索引如何嵌入排障流程
一个典型的使用方式是:
- 当前状态被 Featureizer 切片
- 向量化后进入索引
- 返回 Top-K 相似历史状态
- 关联当时的变更、告警、处理记录
- 人工或半自动决策
注意,向量索引永远不应该是最终裁决者。
9.3 、为什么向量索引必须支持时间与标签过滤
如果你的向量检索只能"全库搜最近邻",那它在工程中会迅速失效。
必须支持的能力包括:
- 按时间范围过滤(只看最近一年)
- 按网络域过滤(只看某个 DC / 区域)
- 按设备类型过滤
- 按配置版本过滤
否则,检索结果会充满"历史垃圾相似态",毫无参考价值。
10、把这一整套能力接入真实工程体系
10.1 不要从"全量接入"开始
一个成熟但不讨喜的建议是:
先从一类问题、一个域、一个场景做起。
例如:
- 只做接口稳定性
- 只做 BGP 会话问题
- 只覆盖一个数据中心
否则,遥测、特征、向量会在第一周就把系统压垮。
10.2 成功与否的真正判断标准
这套体系是否成功,不取决于:
- 模型多复杂
- 指标多全面
- 数据量多大
而只取决于一个问题:
事故发生时,它有没有真的帮工程师少走弯路。
结语:
**遥测不是数据问题,而是工程认知问题,**gNMI、Streaming Telemetry、本身只是工具。
真正困难的,是:
- 如何定义"状态"
- 如何统一"语义"
- 如何保存"经验"
- 如何让系统随着网络一起成长
如果你认真走完了这一章描述的工程路径,那么你已经具备了一个关键能力:
把网络从"只能看曲线",变成"可以被回忆、被比较、被学习的系统"。
这不是一蹴而就的事,但这是所有网络智能化的起点。
(文:陈涉川)
2025年12月16日