想象你住在一个很大的社区里。过去邻里互送东西,你要自己记门牌、排队、担心送错,甚至不知道对方是不是已经搬走。后来物业推出新服务:每栋楼前配一位专属管家。你只需把东西放进门口信箱,管家负责认路、分拣、确认收件人、处理异常——你完全不关心他走哪个门、排哪条队,东西总能送到。这位管家就是 Envoy;管家们组成的投递网络,就是 Service Mesh(服务网格);而下发住户名册与投递规则的物业中心,就是控制面(Control Plane)。 Envoy 是 Service Mesh 中最被广泛使用的数据面。它不写业务代码,只管理流量:拦下进出请求、按规则找到正确后端、在服务故障时快速切换、升级时不停机。本文用“楼栋管家”这个比喻贯穿始终,带你理解 Envoy 的 Sidecar 模式、流量拦截、集群管理、xDS 协议、热重启,以及与 Linkerd 的对比。

岗位关联

学懂 Envoy 数据面,你就真正讲得清 Service Mesh 中流量从哪来、到哪去、如何切换与治理——这是云原生架构师绕不开的核心能力。

目录
  1. 可视化演示
  2. Sidecar 模式——楼门口的专属管家
  3. 流量拦截——管家如何接管所有来信
  4. 集群管理——管家手里的住户名册
  5. xDS 协议——物业中心的实时广播
  6. 热重启——管家换班不停服务
  7. 与 Linkerd 对比——隔壁小区的轻量管家
  8. 服务治理应用——让社区井井有条
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Sidecar透明代理流量拦截机制、xDS动态配置下发流程(LDS/RDS/CDS/EDS)、Envoy热重启与数据面对比,交互切换理解Service Mesh核心原理。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Sidecar透明代理流量拦截 iptables重定向出入站流量到Envoy 应用无感知透明代理机制 Traffic → iptables → Envoy → App Sidecar模式实现业务与治理解耦

Sidecar 模式——楼门口的专属管家

在传统小区里,各户之间互送东西,全凭自己跑腿:记住每一户的门牌、排队、担心对方不在家。物业配了管家之后,每栋楼门口多了一个小岗亭。管家不占住户的客厅,却与这栋楼的住户“同进同出”;住户出门、回家,管家都在一旁。所有对外往来,都从岗亭前经过。这种模式,就是 Sidecar(边车)模式。

在 Kubernetes 世界里,Sidecar 指的是:在同一个 Pod 里,除了业务容器,再放一个 Envoy 代理容器。两个容器共享网络命名空间,业务进程只和 localhost 上的代理通信,出向流量和入向流量都由代理统一处理。这样业务代码完全不需要知道自己是不是在服务网格里,这就是“透明接入”的价值。

从成本角度看,引入 Sidecar 会带来额外的资源开销,但也换来了统一治理能力:

有了这个“岗亭”,后面讲到的流量拦截、负载均衡、平滑升级,才都有落脚点。楼栋管家,正是 Service Mesh 数据面的起点。

Podtotal=appcontainer+sidecarcontainer\text{Pod}_{\text{total}} = \text{app}_{\text{container}} + \text{sidecar}_{\text{container}}
这个公式表达的是一个 Pod 的资源构成:既有处理业务的容器,也有伴随在旁边的代理容器。
  1. Pod_{\text{total}}一个 Pod 占用的总资源,是业务和代理两部分成本之和。
    Podtotal\text{Pod}_{\text{total}}
  2. app_{\text{container}}业务容器,承载真实业务逻辑的进程,例如 Java 应用或 Go 服务。
    appcontainer\text{app}_{\text{container}}
  3. sidecar_{\text{container}}Envoy 代理容器,不执行业务逻辑,只负责数据面的流量转发。
    sidecarcontainer\text{sidecar}_{\text{container}}

流量拦截——管家如何接管所有来信

管家要管好一栋楼的往来信件,就必须先“接管信箱”。物业在每栋楼门口装了一个分拣闸,凡是进出的信件,一律先经过管家的小桌绕行一圈再放行。住户不知道这个过程,但信件已经被“透明地”多走了一小段路。

对应到技术上,接管流量靠的是 iptables 规则。Pod 启动时,初始化容器会写入一系列 iptables 规则,把应用发出的出向流量和外部发来的入向流量,全部重定向到 Envoy 监听的端口(常见的是 15001 端口)。应用仍然认为自己直接访问目标地址,实际上数据包已经先在 Envoy 这里“绕了一圈”。

于是,一条请求的真实链路可以写成:

住户不需要改写信封格式,甚至不知道管家在岗亭里做了什么。这正是 Envoy“透明代理”的威力:业务无感,流量却被完整接管。

client→iptablesEnvoy→clusterserver\text{client} \xrightarrow{\text{iptables}} \text{Envoy} \xrightarrow{\text{cluster}} \text{server}
请求从客户端出发,被 iptables 规则截获后先进入 Envoy,再由 Envoy 根据集群配置转交给真正的后端服务。
  1. client发出请求的一方,例如另一个微服务 Pod 中的进程。
    client\text{client}
  2. iptablesLinux 内核的流量规则引擎,在这里充当“分拣闸”,把流量引入 Envoy。
    iptables\text{iptables}
  3. Envoy透明代理本体,负责接收被劫持的流量,并根据配置做出转发决策。
    Envoy\text{Envoy}
  4. server真正处理业务请求的后端实例,Envoy 通过集群管理找到它。
    server\text{server}

集群管理——管家手里的住户名册

管家为什么能精准投递?因为他手里有一本物业中心发的《住户名册》,上面记录了各楼栋当前有哪些住户、谁在家、谁休假、谁频繁拒收。每次派信,管家先翻名册,选一个目前状态正常的目标;如果这户最近老是拒收,他就换一户投递。

在 Envoy 中,这个“名册”由两个抽象组成:Cluster(集群)和 Endpoint(端点)。Cluster 是一组提供相同服务的后端集合,Endpoint 是集合里的具体实例,比如 10.0.0.8:8080。控制面通过 EDS(端点发现服务)把名册推给 Envoy,Envoy 再根据健康检查和负载均衡策略,选出一个端点转发请求。

最常用的负载均衡策略之一是加权轮询。假设每个后端有一个权重,被选中的概率与该权重成正比:

名册不是印好就完事,物业中心会持续更新。管家手里的名单越准,这栋楼的通信成功率就越高。对于微服务来说,健康的端点列表,就是整个服务网格的“活地图”。

Pi=wi∑j=1NwjP_i = \frac{w_i}{\sum_{j=1}^{N} w_j}
第 i 个后端被选中的概率,等于它自己的权重除以所有健康后端权重之和。
  1. P_i第 i 个后端实例被选中的概率,决定这次请求落到谁头上。
    PiP_i
  2. w_i第 i 个后端的权重,权重越大,承受的流量比例越高。
    wiw_i
  3. \sum_{j=1}^{N} w_j所有健康后端权重之和,是归一化的分母。
    ∑j=1Nwj\sum_{j=1}^{N} w_j

xDS 协议——物业中心的实时广播

名册和投递规则经常要变:有人搬家、有人改门牌、最近某类包裹需要特殊路线。物业中心如果每次派人挨家挨户发纸质通知,效率太低。于是物业用内部广播电台实时播报,每位管家随身带一台小收音机收听。规则一变,下一单立刻生效。

这套“广播系统”在 Service Mesh 里就是 xDS 协议。xDS 全称 X Discovery Service,是一族从控制面到数据面下发配置的协议。常见子协议包括:

管家不需要知道物业中心内部怎么决策,只需要认真听广播。广播里说换门牌,他就按新门牌送信。这就是 xDS 的价值:控制面与数据面解耦,配置动态生效、持续收敛。

ADS={LDS,RDS,CDS,EDS}\text{ADS} = \{\text{LDS}, \text{RDS}, \text{CDS}, \text{EDS}\}
ADS 表示聚合发现服务,它把几类 xDS 子协议放在同一条 gRPC 流上传输,相当于把多个广播频道合成了一个“总台”。
  1. ADS聚合发现服务,一条 gRPC 双向流上承载多种 xDS 资源更新。
    ADS\text{ADS}
  2. LDS监听器发现服务,告诉 Envoy 要监听哪些端口,相当于“岗亭该开哪扇门”。
    LDS\text{LDS}
  3. RDS路由发现服务,告诉 Envoy 如何按请求特征匹配集群,相当于“信送到哪栋楼”。
    RDS\text{RDS}
  4. CDS集群发现服务,告诉 Envoy 有哪些后端集群可用,相当于“名册上有哪些楼栋”。
    CDS\text{CDS}
  5. EDS端点发现服务,告诉 Envoy 每个集群下具体的实例列表,相当于“每栋楼具体住户名单”。
    EDS\text{EDS}

热重启——管家换班不停服务

老管家要退休了,物业不会让楼栋先停摆再换新人。新管家提前到岗,两人在小岗亭里完成交接:钥匙、在途信件、门口登记表。全部确认清楚后,老管家才放下工牌离开。整个过程,住户没有任何感知,信件照常递送。

Envoy 的热重启(Hot Restart)做得正是这件事。新进程启动后,通过 Unix Domain Socket 从旧进程继承监听套接字;随后新进程开始接收新连接,旧进程进入排空(drain)状态,耐心处理完已在途的请求,再优雅退出。整个过程不需要重启 Pod,也不会中断流量。

数学上,热重启的“平滑”体现在新旧进程短暂共存:

对住户来说,管家还是那个岗亭,门牌没变,服务一秒都没有停止。Envoy 热重启解决了数据面升级时“代理不能断”的难题。

Sold∩Snew≠∅S_{\text{old}} \cap S_{\text{new}} \neq \emptyset
旧进程持有的连接集合与新进程持有的连接集合存在交集,说明两者并不是“先关一个再开一个”的接力关系。
  1. S_{\text{old}}旧进程在交接瞬间仍然持有的连接集合,包括正在处理的在途请求。
    SoldS_{\text{old}}
  2. S_{\text{new}}新进程启动并接管监听后的连接集合。
    SnewS_{\text{new}}
  3. \cap \neq \emptyset两个集合有交集,表示新旧进程之间存在一段并存的过渡窗口,流量不会有一刻真空。
    Sold∩Snew≠∅S_{\text{old}} \cap S_{\text{new}} \neq \emptyset

与 Linkerd 对比——隔壁小区的轻量管家

隔壁小区物业预算有限,也引入了管家制度,但只给每位管家配一件轻便马甲和一部平板电脑。不负责重物搬运,不做复杂分拣,只保证最基本的收件和送件。这个小而轻的管家,就是 Linkerd。它比 Envoy 更省电、更安静,但复杂规则的处理能力有限。

技术上,Linkerd 数据面由 Rust 实现(linkerd2-proxy),优先保证低内存、低延迟;Envoy 则由 C++ 实现,功能极为丰富,支持大量过滤器、协议和扩展机制。两者不是谁取代谁,而是不同场景下的取舍。如果团队追求极简运维和资源敏感型环境,Linkerd 更有吸引力;如果平台需要深度定制和复杂流量治理,Envoy 更合适。

内存开销的对比可以简单表示为:

选管家不是越贵越好,而是看社区规模、预算和管理复杂度。Envoy 与 Linkerd 的选择,本质上是在“功能丰富度”和“资源轻量度”之间做权衡。

r=mlinkerdmenvoy<1r = \frac{m_{\text{linkerd}}}{m_{\text{envoy}}} < 1
在同等代理场景下,Linkerd 的内存占用通常小于 Envoy,因此资源开销的比值小于 1。
  1. rLinkerd 与 Envoy 的内存开销比值,用来直观比较两个数据面的资源效率。
    rr
  2. m_{\text{linkerd}}Linkerd 数据面代理的平均内存占用。
    mlinkerdm_{\text{linkerd}}
  3. m_{\text{envoy}}Envoy 数据面代理的平均内存占用。
    menvoym_{\text{envoy}}
  4. r < 1说明在相同体量下 Linkerd 更省内存,这是它最大的差异化优势。
    r<1r < 1

服务治理应用——让社区井井有条

管家不止送信,还会“拦信”。某户连续多天拒收,管家会在名册上记一笔,暂时停止给这户投递;过一段时间,再试投一两封看是否恢复。这样一来,整个社区的投递成功率大幅提升,不会再有人做无用功。

Envoy 把类似能力内置在数据面:熔断(Circuit Breaking)、限流(Rate Limiting)、超时重试、异常点检测、mTLS 加密、访问日志和指标。原本这些都需要业务代码用 SDK 实现,如今全部下沉到代理层。开发团队只需关注业务逻辑,流量治理交给数据面统一完成。

熔断器的核心是一个三态切换逻辑:

当整个社区都配上这样的管家,楼与楼之间的通信就自动变得有序、安全、可观测。Envoy 数据面的最终价值,不是单个代理,而是把治理能力变成微服务社区默认的基础设施。

S(t+1)={closede≤θopene>θhalf-opentimeoutS(t+1) = \begin{cases} \text{closed} & e \leq \theta \\ \text{open} & e > \theta \\ \text{half-open} & \text{timeout} \end{cases}
熔断器的下一个状态取决于当前错误率:低于阈值时保持关闭放行,超过阈值时打开熔断,超时后再进入半开状态做小流量试探。
  1. S(t+1)熔断器在下一个时刻的状态,是服务治理决策的输出。
    S(t+1)S(t+1)
  2. closed错误率没有超过阈值,请求正常放行,类似管家正常投递。
    closed\text{closed}
  3. open错误率超过阈值,请求快速失败,避免继续向故障后端发送流量。
    open\text{open}
  4. half-open冷却超时后放行少量探测请求,验证后端是否恢复。
    half-open\text{half-open}

AI 应用场景

场景一:LLM 服务网格中的动态路由

阶段:模型服务部署后的流量编排与治理阶段

解决的问题:企业接入多个大模型供应商时,传统网关需要硬编码路由规则,切换模型或做灰度发布都要改配置、重启网关。Envoy 数据面通过 xDS 协议接收控制面动态下发的路由规则,可以在不重启代理的情况下,把请求按比例路由到不同模型服务。

具体案例:某 AI 中台团队为内部 LLM 网关接入了 Envoy Sidecar。当供应商 A 的模型响应变慢时,控制面根据健康检查数据自动提升供应商 B 的权重,Envoy 随即把更多请求切换到 B。整个过程业务方无感,就像管家按名册自动换了一户投递。

场景二:多集群容灾与金丝雀发布

阶段:模型版本升级与多集群调度阶段

解决的问题:机器学习模型的迭代频率很高,直接全量上线风险大。传统做法是在应用层写一堆发布脚本。借助 Envoy 的流量拦截与集群管理,可以将新版本模型服务作为新集群加入名册,并设置极小权重做金丝雀验证。

具体案例:一家推荐系统团队把两个版本的特征服务部署在不同可用区,Envoy 按 9:1 权重分发流量,灰度版本表现稳定后再把权重调成 10:0。控制面通过 xDS 实时下发配置,完全是“广播一响,规则生效”。

Python 演示

python56 行
# -*- coding: utf-8 -*-import random class Endpoint:    def __init__(self, name, weight=1):        self.name = name        self.weight = weight        self.healthy = True        self.requests = 0        self.failures = 0     def __repr__(self):        return self.name class MockEnvoy:    def __init__(self):        self.endpoints = {}     def update_cluster(self, eps):        self.endpoints = {ep.name: ep for ep in eps}        names = ", ".join(ep.name for ep in eps)        print("[xDS] 收到集群更新:", names)     def mark_health(self, name, healthy):        if name not in self.endpoints:            return        self.endpoints[name].healthy = healthy        state = "健康" if healthy else "不健康"        print("[xDS] 健康检查更新: {} -> {}".format(name, state))     def pick(self):        healthy = [ep for ep in self.endpoints.values() if ep.healthy]        if not healthy:            return None        weights = [ep.weight for ep in healthy]        return random.choices(healthy, weights=weights, k=1)[0]     def forward(self, req_id):        ep = self.pick()        if ep is None:            print("请求 {}: 所有端点不健康, 进入熔断".format(req_id))            return "503"        ep.requests += 1        print("请求 {} -> {} (权重 {})".format(req_id, ep.name, ep.weight))        return "200" if __name__ == "__main__":    envoy = MockEnvoy()    envoy.update_cluster([        Endpoint("server-a", weight=5),        Endpoint("server-b", weight=3),        Endpoint("server-c", weight=2),    ])    envoy.mark_health("server-a", False)    for i in range(6):        envoy.forward(i + 1)
目的:用一个极简的 Python 类模拟 Envoy 数据面的核心链路:xDS 下发端点列表、健康检查更新、加权随机负载均衡,以及全后端不可用时的熔断行为。
  1. class MockEnvoy:用类来模拟 Envoy 代理,属性保存当前端点列表,方法模拟路由和治理逻辑。
  2. self.endpoints = {ep.name: ep for ep in eps}将控制面下发的端点列表转化为字典,按名字快速查找。
  3. def mark_health(self, name, healthy):模拟 EDS 健康检查推送,动态更新端点的健康状态。
  4. healthy = [ep for ep in self.endpoints.values() if ep.healthy]只从健康端点中选目标,相当于管家只看名册里“可投递”的住户。
  5. random.choices(healthy, weights=weights, k=1)[0]按权重做随机选择,模拟 Envoy 的加权负载均衡策略。
  6. return "503"没有健康端点时返回 503,对应熔断状态下的快速失败。

常见误区

误区以为“Envoy 就是 Service Mesh”,装了 Envoy 就等于有了服务网格,甚至直接在 Envoy 上管理服务注册和路由配置,把它当成控制面来用。

正解Envoy 是数据面,负责流量的转发与执行;真正做配置决策、维护服务名册的是控制面,例如 Istio 的 Istiod。类比来说,管家执行物业中心的指令,但管家不是物业中心。控制面通过 xDS 下发配置,Envoy 只是忠实的执行者。

为何常见:因为 Istio 常与 Envoy 成对出现,很多人把“执行者”和“决策者”混为一谈,忽略了二者在 Service Mesh 架构中的分工。

误区认为接入 Envoy 后,Java、Go 等应用必须修改代码,改用代理指定的请求库或 API,才能把流量发给 Envoy。

正解Envoy 的透明代理依赖 iptables 重定向。在 Pod 网络命名空间内,应用发出的数据包被内核规则自动导向 Envoy 监听端口,业务进程完全无感。也就是说,代码一行都不用改,只要 Envoy 在,流量就会被接管。

为何常见:过去的微服务 SDK 接入方式往往要求在代码里初始化客户端、配置注册中心地址,这种惯性思维让人误以为 Service Mesh 也必须改代码。

误区以为“换新进程”就等于“重启服务”,所有连接都会断掉,线上请求必然出现大量失败。

正解热重启的流程是先让新进程启动,通过 Unix Domain Socket 继承旧进程的监听套接字;新进程开始接收新连接,旧进程则进入排空阶段,把在途请求处理完再退出。新旧进程短暂并存,连接不会出现空窗。

为何常见:因为普通进程重启确实是“先关再开”,而热重启的关键是“先开再关”。只看字面意思,容易忽略背后的交接机制。

误区以为 xDS 就是某一个配置文件或单一接口,比如在系统词典里查“xDS=true”,认为它是控制面里的一个开关。

正解xDS 是“X Discovery Service”的统称,是一族动态配置协议,包括 LDS、RDS、CDS、EDS,还可以通过 ADS 在同一条 gRPC 流上聚合传输。类比为广播电台的不同“频段”,每个频段播放不同类别的规则。

为何常见:因为 xDS 这个缩写太像一个具体名词,初学者容易忽略它背后是多种资源发现服务的集合。

面试题

Q1. Envoy 在 Service Mesh 中承担的角色是什么?

单选题 · easy

  • A.数据面代理← 正确答案
  • B.控制面调度
  • C.业务应用容器
  • D.配置存储仓库

答案:A。Envoy 是数据面,负责流量的拦截、转发和治理;控制面才负责配置决策。好比管家执行物业指令,但管家不是物业中心本身。

Q2. Envoy 透明代理实现流量劫持的核心机制是?

单选题 · mid

  • A.iptables 流量重定向← 正确答案
  • B.业务代码显式接入
  • C.代理网关显式接入
  • D.数据库连接池调整

答案:A。Envoy 依赖 iptables 规则把进出 Pod 的流量重定向到自己的监听端口。业务代码无感,这是“透明”二字的来源。其余选项都属于显式接入,违反透明代理的原则。

Q3. 以下哪些属于 xDS 的子协议?

多选题 · mid

  • A.LDS 监听发现← 正确答案
  • B.RDS 路由发现← 正确答案
  • C.CDS 集群发现← 正确答案
  • D.HTTP 配置协议

答案:ABC。LDS、RDS、CDS 都是 xDS 的资源发现子协议,再加上 EDS 等,共同构成控制面到数据面的配置下发体系。HTTP 配置协议不是 xDS 体系中的标准成员。

Q4. 与 Envoy 相比,Linkerd 数据面最大的特点是什么?

单选题 · easy

  • A.Rust 实现更轻量← 正确答案
  • B.Java 实现更重
  • C.不需要任何代理
  • D.仅支持 HTTP 流量

答案:A。Linkerd 数据面用 Rust 编写,以低内存、低延迟著称。它不是去掉代理,也不是只支持 HTTP,而是主打资源更省、运维更简单。

Q5. Envoy 热重启时新旧进程交换状态的方式是什么?

单选题 · hard

  • A.Unix 域套接字传递← 正确答案
  • B.环境变量隐式传递
  • C.共享缓存文件传递
  • D.控制面数据库传递

答案:A。Envoy 通过 Unix Domain Socket 让新进程从旧进程继承监听套接字和关键运行时状态,从而在不中断流量的情况下完成进程替换。环境变量和数据库都不能承载监听套接字这样的内核对象。

Q6. 关于 Envoy 热重启,下列说法正确的是?

多选题 · hard

  • A.新进程先接管监听并转交连接← 正确答案
  • B.旧进程排空在途请求后退出← 正确答案
  • C.整个 Pod 会重建并中断服务
  • D.业务容器需要同步重启

答案:AB。热重启的关键是新旧进程短暂并存:新进程继承监听能力,旧进程处理完在途请求再退出。Pod 和业务容器都不需要重建或重启,因此 C、D 是错误的。

Q7. EDS 端点发现服务主要负责下发什么?

单选题 · mid

  • A.可用后端实例列表← 正确答案
  • B.路由匹配规则集
  • C.监听端口策略集
  • D.日志输出级别配置

答案:A。EDS 全称 Endpoint Discovery Service,负责把集群下的具体后端实例列表下发给 Envoy。路由规则归 RDS,监听端口归 LDS,日志级别不在 xDS 核心职责内。

Q8. 在 Sidecar 模式中,Envoy 的部署位置是哪里?

单选题 · easy

  • A.与应用同 Pod 伴生← 正确答案
  • B.独立的控制面集群
  • C.独立的网关集群
  • D.浏览器端的模块

答案:A。Sidecar 的核心是“伴生”,Envoy 和应用容器在同一个 Pod 内,共享网络命名空间,像管家一样守在楼门口。独立部署的网关属于另一种形态,不是 Sidecar。

Q9. 当所有后端都不健康时,Envoy 通常会如何处理?

单选题 · mid

  • A.直接返回失败响应← 正确答案
  • B.无限期等待恢复
  • C.重启后端进程
  • D.修改后端路由表

答案:A。在熔断或无可用端点的场景下,Envoy 会快速返回失败响应,避免请求长时间挂起,从而保护调用方。它不会去重启后端进程,也不会修改路由表。

Q10. 热重启时新进程从旧进程继承的核心资源是什么?

单选题 · extreme

  • A.监听套接字句柄← 正确答案
  • B.业务数据库连接
  • C.应用层编程语言
  • D.用户身份认证令牌

答案:A。热重启的关键是把内核级的监听套接字句柄从旧进程转交给新进程,使新进程可以无缝接收新连接。业务数据库连接、编程语言或认证令牌都不是需要跨进程转移的“接管对象”。