第 9 篇 把 LDS→RDS→CDS→EDS 依赖树钉住了。联调时第二个坑通常不是「缺哪种资源」,而是:同一套依赖,走分类型 gRPC 流还是 ADS;走 State-of-the-World 还是 Incremental;ACK 了为什么路由还是旧的。
本文只回答协议层三件事:四种传输变体差在哪;version_info
/ nonce / ACK/NACK 各自承诺什么;NACK
之后控制面与数据面分别会怎样。Warming 与「ACK
之后流量何时切」留给 第 11
篇。
本文是「Envoy / 数据面代理内核」系列第 10 篇(共 16 篇)。→ 系列目录
篇目 核心内容 第 9 篇 · xDS 资源树 LDS→RDS→CDS→EDS 依赖 第 10 篇 · ADS / SotW / Delta 流变体、ACK/NACK、version/nonce 第 11 篇 · Warming / SDS ACK≠流量;证书与热更新边界
版本锚定:Envoy v1.39.0 xDS REST and gRPC protocol;API v3。无本机联调输出,不伪造 ACK 时序或
config_dump。
一、四个变体:两个正交维度
官方协议把 streaming gRPC xDS 拆成两个独立维度(v1.39.0 xDS protocol):
- SotW vs Incremental(Delta):推送经济学——整表还是差量。
- 分类型流 vs ADS:一致性经济学——多流最终一致,还是单流可排序。
交叉得到四种变体:
| 变体 | SotW / Delta | 流 | 典型 RPC |
|---|---|---|---|
| Basic xDS | SotW | 每资源类型一条流 | 如 StreamListeners |
| Incremental xDS | Delta | 每资源类型一条流 | 如 DeltaListeners |
| ADS | SotW | 单流多路复用 | StreamAggregatedResources |
| Incremental ADS | Delta | 单流多路复用 | DeltaAggregatedResources |
flowchart LR
subgraph dims ["Two dimensions"]
SotW["SotW full snapshot"]
Delta["Incremental deltas"]
PerType["Per-type streams"]
ADS["Single ADS stream"]
end
SotW --> Basic["Basic xDS"]
SotW --> ADSSotW["ADS SotW"]
Delta --> Inc["Incremental xDS"]
Delta --> ADSDelta["Incremental ADS"]
PerType --> Basic
PerType --> Inc
ADS --> ADSSotW
ADS --> ADSDelta
SotW:客户端每次请求带齐感兴趣的
resource_names;对
LDS/CDS,服务端在每次响应里返回当前订阅集合的全集。缺席的名字被视为删除。订阅从
99 扩到 100 时,客户端要重发 100 个名字,服务端往往也要再推
100 份资源——大规模 EDS 下这是带宽与抖动税。
Incremental:请求用
resource_names_subscribe /
resource_names_unsubscribe;响应只含增删改。nonce
必填,用来把
DeltaDiscoveryResponse 与后续 ACK/NACK
配对。system_version_info 仅调试用,不承担 SotW
那套「资源类型实例版本」角色。
ADS:所有 type URL 复用一条 gRPC 双向流;每个 type URL 仍是独立的 DiscoveryRequest/Response 子序列,nonce 按 type URL 分别跟踪。官方动机很明确:分布式多管理端很难保证 make-before-break;ADS 让同一控制面按 CDS→EDS→LDS→RDS 顺序推送,降低短暂黑洞概率。
选型句:要可排序推送选 ADS;要大规模差量选 Incremental;两者可叠加为 Incremental ADS。 分类型 SotW 适合规模可控、可容忍最终一致窗口的部署。
二、version 与 nonce:两套不同的时钟
2.1
version_info(资源类型实例版本)
每个 xDS
资源类型有一个版本字符串;该类型下任一资源变更,版本跟着变。SotW
响应的 version_info
表示服务端声称的当前版本;客户端在后续请求里回填自己已接受的最近有效版本。
要点:
- 版本按 资源类型 分;ADS 上 LDS 与 CDS 各有各的版本。
- 版本按 ConfigSource / 管理端 分;连两家控制面时不能假设版本空间共享。
- 版本是资源上的属性,不是单条流的属性:流断重连时,初始请求应带上客户端已知的最近版本,便于服务端跳过未变资源(有条件时)。
2.2 nonce
每个 DiscoveryResponse 带
nonce;客户端后续请求必须把
response_nonce
设为该流上最近一次收到的 nonce。nonce
不跨流重启存活;ADS 上按 type URL
分开记。
nonce 解决的是 SotW 特有竞态:客户端可能在版本
X 上追加 resource_names
hint,而服务端同时推出版本 Y。若只看
version_info,hint 请求容易被误判成对
Y 的拒绝。服务端规则是:对带陈旧 nonce
的请求不要回包;nonce 在更新的响应出现后变旧。
推论:客户端可以发出多个 DiscoveryRequest,并不期望每个请求都有对应 Response。
三、ACK / NACK:协议层承诺边界
协议要求:客户端应对每条 DiscoveryResponse
回 ACK 或 NACK;用 response_nonce
指明在回哪一次推送。
| 信号 | 判定 | 含义(协议原文口径) |
|---|---|---|
| ACK | 无 error_detail;version_info
取响应中的版本 |
各资源单独看有效,客户端意图应用 |
| NACK | 有 error_detail;version_info
为客户端仍在用的版本 |
至少一个资源无效 |
官方钉死的两句话必须拆开读:
- ACK 只表示「隔离校验通过并打算应用」,不保证配置已成功落到可服务状态;ACK 之后仍可能应用失败。
- NACK 不表示「本批全部拒绝」;细节在
error_detail.message。
检测 NACK 的首选方式是看 error_detail
是否存在。仅靠「nonce 对应的版本与请求 version 不一致」推断
NACK,在 RDS/EDS 等会动态改订阅集合的 API
上会误判——除非服务端每次订阅变更都抬版本。
sequenceDiagram
participant CP as ControlPlane
participant E as Envoy
CP->>E: DiscoveryResponse V=X nonce=A
alt all resources valid in isolation
E->>CP: DiscoveryRequest V=X nonce=A ACK
else at least one invalid
E->>CP: DiscoveryRequest V=prev nonce=A NACK error_detail
end
与流量的关系:ACK 落在「协议接受」;Listener/Cluster 是否 warming 完成、Worker 快照是否已切、新 RDS 是否已对请求可见,是下一层状态机——见 第 11 篇。把控制面「看到 ACK」当成「金丝雀已吃到新路由」,是联调第一大误区。
四、NACK 之后:失败模式清单
| 场景 | 数据面行为 | 控制面若处理不当 |
|---|---|---|
| 单资源非法(字段/PGV/未知 type) | NACK;继续跑上一有效版本 | 反复推同一坏版本 → 无意义 CPU;应修资源或抬到可 ACK 的
Y |
| SotW 响应重复同名资源 | 客户端应 NACK | 修打包逻辑 |
| 流断开 | 重连;nonce 失效;带已知 version | 勿假设旧 nonce 仍有效 |
| 文件系统订阅 | 无 ACK/NACK 通道;坏更新拒收后保留上一有效配置 | 只能看 stats/日志 |
| ACK 后应用失败 | 协议已 ACK,运行时可能仍旧配置或进入异常路径 | 只盯 ACK 计数会漏掉真实故障;需对照 admin / stats |
NACK 不会自动回滚「已经原子切换成功」的监听器——它拒绝的是这一次协议更新。旧配置继续服务,直到出现可 ACK 且可应用的新版本(或进程级变更,见 第 12 篇)。
分类型流上的额外风险:最终一致窗口。RDS
已指向 cluster Y,而 CDS/EDS 尚未提供
Y,会出现短暂黑洞。协议文档的 make-before-break
顺序是:CDS → EDS → LDS → RDS →(再删陈旧 CDS/EDS)。ADS
降低「多服务端抢序」难度,但不免除控制面自己按该顺序推送的责任。
五、争论与开放问题
| 争论 | A | B | 工程落点 |
|---|---|---|---|
| SotW vs Incremental | 实现与推理简单 | 大规模 EDS 带宽与尾延迟 | Mesh 控制面普遍走向 Delta;小集群 SotW 仍可接受 |
| 分类型流 vs ADS | 可拆管理端、故障隔离 | 排序与一致性 | 要严格 make-before-break 时优先 ADS |
| ACK 作为「配置已生效」SLI | 控制面易观测 | 漏掉 warming / 应用失败 | SLI 应叠 warming 与路由命中,而非单看 ACK |
开放问题(可检验):在超大规模 endpoint churn 下,Incremental ADS 的控制面内存(每客户端订阅状态)与 SotW 重推成本,哪一侧先成为瓶颈——需要按具体控制面实现与订阅基数实测,本篇不给未跑数字。
六、参考资料
规范 / 官方文档(A)
- Envoy Proxy, xDS REST and gRPC protocol, docs v1.39.0(Four Variants;ACK/NACK;nonce;ADS;Incremental;Eventual consistency)。
- Envoy Proxy, xDS configuration API overview, v1.39.0(Delta gRPC xDS 综述)。
源码(A)
envoyproxy/envoytag v1.39.0:source/common/config/(订阅与 ACK 路径);ADS 相关在 xDS gRPC 客户端实现中。
站内对照
实验台账
- 本篇无命令输出;不写推送延迟或 ACK 成功率数字。
七、小结
- 四个变体由 SotW/Delta 与分类型流/ADS 正交组合;ADS 服务排序,Incremental 服务规模。
version_info是类型级时钟;nonce是流内配对时钟——混用会误判 NACK。- ACK = 隔离校验通过并意图应用,不等于 warming 完成或流量已切。
- NACK 后旧配置继续服务;危险在于控制面空转坏版本,以及分类型流上的最终一致黑洞。
→ 系列目录 · 上一篇:xDS 资源树 · 下一篇:Warming、SDS 与热更新
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Envoy 数据面】数据面全景:从 Listener 到 xDS 的可编程代理内核
定位 Envoy 相对 Nginx/HAProxy 静态配置代理与 Mesh 选型叙事的生态位;钉住请求路径、配置快照、FilterChainMatch、xDS warming、上游资源五条坐标系,并给出与 network/57 的分工及 16 篇阅读路线。
【Envoy 数据面】Main / Worker 与配置快照:事件循环、TLS 与几乎无锁热路径
钉住 Envoy 单进程多线程模型:Main 管 xDS/Admin,Worker 绑连接终生;Event::Dispatcher 与 Thread Local Storage 如何把配置变成每线程可读快照,以及快照解决什么、解决不了什么。
【Envoy 数据面】xDS 资源树:LDS→RDS→CDS→EDS 的依赖、命名与顺序
把 Listener、RouteConfiguration、Cluster、ClusterLoadAssignment(及 SDS)画成依赖树,说明资源命名如何串起引用、warming 与 make-before-break 顺序为何决定短暂黑洞,并衔接到 ADS/SotW/Delta。
【Envoy 数据面】Warming、SDS 与热更新:ACK 为何不等于新路由上量
拆解 Listener/Cluster warming、SDS 证书轮转与 xDS 热更新边界;说明为何协议 ACK 之后流量仍可能走旧路由或 503,并区分 xDS 热更新与 Hot restart。