2. Envoy核心架构

Envoy 的核心架构围绕多层代理、无锁多线程和事件驱动三大设计原则构建。理解这些底层机制是掌握流量劫持与透明代理配置的前提。本章将拆解 Envoy 的 L4/L7 分层架构、工作线程模型以及事件驱动循环,帮助你在后续章节中精准定位配置项与性能瓶颈。

2.1 L4/L7 分层代理架构

Envoy 在单一进程中同时实现了传输层(L4)和应用层(L7)的代理能力,这种分层设计使其既能处理原始 TCP/UDP 流量,又能深度解析 HTTP/2、gRPC、MongoDB 等七层协议。

层级 核心组件 主要职责 典型场景
L4 传输层 Listener、Filter Chain、TCP Proxy Filter 连接接受、TLS 终止、原始字节转发、连接统计 透明代理、TCP 流量劫持、TLS 桥接
L7 应用层 HTTP Connection Manager、Router Filter、Cluster Manager 协议解析、路由匹配、Header 修改、重试/限流 API 网关、服务网格、七层流量治理
关键设计点: L4 和 L7 过滤器在同一个 Filter Chain 中串联执行。L4 过滤器先处理连接元数据(如源 IP、SNI),决定是否将流量交给 L7 过滤器;L7 过滤器再对应用协议进行精细化处理。这种“先 L4 后 L7”的流水线模式避免了不必要的协议解析开销。

下图展示了 Envoy 内部从监听器到集群的完整数据流,清晰呈现 L4 与 L7 过滤器的协作关系:

Envoy L4/L7 分层代理数据流 Envoy L4/L7 分层代理数据流 外部客户端 Listener (端口/IP 绑定) TLS 终止 L4 Filter Chain • TCP Proxy Filter • Original Dst Filter • TLS Inspector • Rate Limit (L4) L4 直通 (TCP/UDP 转发) L7 处理 (HTTP/gRPC) Cluster (Upstream) 说明: L4 Filter Chain 决定流量是“直通转发”还是“升入 L7 处理”。透明代理场景下,Original Dst Filter 在 L4 层完成目标地址还原, 随后流量可继续进入 HTTP Connection Manager 进行七层路由,或直接由 TCP Proxy 转发至原始目标。
透明代理中的分层意义: 在透明劫持场景中,L4 层的 original_dst 过滤器负责从被劫持的连接中提取原始目标地址;L7 层的 HTTP Connection Manager 则负责解析 HTTP 请求中的 Host 头,两者配合可实现“无感知代理”。后续章节将详细展开这一机制。

2.2 工作线程模型:无锁多线程架构

Envoy 使用 单进程 + 多工作线程 模型,每个工作线程(Worker Thread)拥有独立的 事件循环 和 连接池,线程之间通过 共享内存(Shared Memory) 进行少量状态同步,避免了传统多线程模型中频繁的锁竞争。

线程角色 数量 主要职责 关键特性
Main Thread 1 配置加载/更新、统计聚合、管理接口(Admin API)、热重启协调 不处理数据面流量;配置变更后通过 线程本地存储(TLS) 分发
Worker Threads N(默认等于 CPU 核数) 监听器 accept、连接读写、过滤器执行、转发决策 每个 Worker 独立运行 libevent 事件循环;无锁处理连接
File Flusher Threads 少量 异步日志写入、文件 I/O 避免 Worker 线程因磁盘 I/O 阻塞
无锁设计核心: Envoy 使用 SO_REUSEPORT(Linux 3.9+)或 Accepting via Event Loop 将新连接分发到不同 Worker 线程。一旦连接被分配给某个 Worker,该连接的所有后续处理(读、写、过滤、转发)都在同一个 Worker 线程内完成,无需跨线程加锁。这种 per-connection 线程绑定 策略是 Envoy 高性能的基石。

下图展示了 Main Thread 与 Worker Threads 之间的协作关系,以及连接如何被分发到不同 Worker:

Envoy 工作线程模型 Envoy 工作线程模型:无锁连接分发 Main Thread 配置加载/更新 统计聚合 Admin API TLS 分发 Worker Thread 1 事件循环 (libevent) 连接池 1 过滤器链 1 ~ 处理连接 A, C, E Worker Thread 2 事件循环 (libevent) 连接池 2 过滤器链 2 ~ 处理连接 B, D, F Worker Thread N 事件循环 (libevent) 连接池 N 过滤器链 N ~ 处理连接 G, H, ... 共享内存 • 集群健康状态 • 运行时标志 • 统计计数器 • 配置快照 (只读访问, 无锁读取) 关键设计: 每个 Worker 拥有独立的连接池和过滤器链,连接从 accept 到 close 始终绑定在同一个 Worker 上。Main Thread 通过 TLS 将配置变更推送给所有 Worker,Worker 在事件循环的下一轮 tick 中安全切换配置。
配置注意事项: 在透明代理环境中,如果使用 iptables 配合 SO_REUSEPORT 进行流量劫持,需要确保 Envoy 的 --concurrency 参数与 Worker 线程数匹配。过少的 Worker 可能导致连接堆积,过多的 Worker 则可能因上下文切换降低吞吐。建议从 --concurrency 0(自动检测 CPU 核数)开始调优。

2.3 事件驱动机制:libevent 与异步 I/O

Envoy 底层使用 libevent 库实现事件驱动。每个 Worker 线程运行一个独立的 event_base 循环,通过 非阻塞 I/O 和 回调函数 处理网络事件、定时器事件和信号事件。

2.3.1 事件循环核心流程

  1. 事件注册: 监听器(Listener)将 socket fd 注册到 event_base,设置可读/可写/错误事件的回调函数。
  2. 事件等待: event_base 调用 epoll(Linux)或 kqueue(macOS)等待事件就绪。
  3. 事件分发: 就绪事件触发后,libevent 调用对应的回调函数(如 onRead、onWrite)。
  4. 回调执行: 回调函数内完成数据读取、过滤器链处理、转发决策等操作。如果操作需要异步等待(如 DNS 解析、上游连接建立),则注册新的回调并返回事件循环。
  5. 循环继续: 处理完当前批次事件后,event_base 再次进入等待状态。
事件驱动 vs 线程驱动: 传统线程驱动模型为每个连接分配一个线程,当连接数达到数万时,线程切换开销巨大。Envoy 的事件驱动模型使用少量 Worker 线程处理海量连接,每个线程通过 非阻塞 I/O + 回调 在多个连接间快速切换,单线程可轻松处理数千个并发连接。

2.3.2 关键事件类型与回调

事件类型 触发条件 典型回调处理 性能影响
读事件 socket 接收缓冲区有数据可读 调用 Network::ConnectionImpl::onRead,触发 Filter Chain 的 onData 方法 高频率事件,需避免在回调中执行阻塞操作
写事件 socket 发送缓冲区可写入数据 调用 Network::ConnectionImpl::onWrite,将待发送数据写入内核缓冲区 通常由上层过滤器触发,需注意背压(Backpressure)
连接事件 新连接建立(accept)或连接关闭 accept 后创建 Connection 对象并注册读写事件;关闭时清理资源 连接建立/销毁频率影响 Worker 负载均衡
定时器事件 超时时间到达 连接空闲超时断开、健康检查超时、统计刷新 大量短定时器可能增加事件循环压力
信号事件 进程收到 SIGTERM/SIGHUP 等信号 触发优雅关闭、热重启、日志重新打开 低频率,但需保证原子性

2.3.3 事件驱动在透明代理中的实践意义

在透明代理场景下,事件驱动机制直接影响劫持流量的处理效率:

  • 非阻塞 accept: 当 iptables 将流量重定向到 Envoy 监听端口时,Worker 线程通过非阻塞 accept 快速接收连接,不会因为某个连接的 TLS 握手慢而阻塞其他连接。
  • 异步 DNS 解析: 透明代理中,Envoy 需要将原始目标 IP 解析为集群名称。事件驱动允许 DNS 查询在后台进行,Worker 线程继续处理其他连接,查询结果通过回调返回。
  • 背压控制: 当上游服务响应缓慢时,写事件回调会暂停读取下游数据,防止内存无限增长。这是透明代理中防止“慢上游拖垮整个代理”的关键机制。
调优建议: 在透明代理的高并发场景下,可以通过调整 --event-loop-timeout 参数(默认 2s)来检测事件循环是否被阻塞。如果日志中出现 event loop stuck 警告,说明某个回调执行时间过长,需要检查是否存在阻塞 I/O 或 CPU 密集型操作。