资讯动态

Higress 网关源码深度解析:从 Envoy 数据面到 Wasm 插件实战

发布时间:2026/10/29 17:36:52 来源:尧图企业网站定制
1. 从一次线上故障说起为什么我要啃 Higress 的源码去年双十一大促前夜我们一个核心交易链路的网关突然出现间歇性 503平均每五分钟抖一次每次持续十几秒。运维同学第一反应是后端服务扛不住但查了服务端监控CPU、内存、GC 全部正常QPS 曲线平滑得像一条直线。问题最终定位到网关侧某个 Wasm 插件在特定请求头组合下会触发一次同步的配置拉取而这个拉取走的是控制面通道一旦控制面响应慢数据面的工作线程就被阻塞连接池迅速打满新的请求直接排队超时。那次故障之后我把 Higress 从数据面到控制面的代码翻了一遍。这篇文章就是那次“补课”的产物。Higress 是阿里开源的一款云原生 API 网关底层数据面基于 Envoy控制面兼容 Istio 的 xDS 协议同时支持 Wasm 插件做能力扩展。它想解决的问题很明确传统网关比如 Nginx Lua在云原生场景下配置动态化能力弱、扩展成本高、和 K8s 生态割裂而纯 Envoy 方案又太底层业务团队上手门槛高。Higress 试图在“高性能数据面”和“易用的控制面”之间找一个平衡点。这篇文章适合三类人看一是正在做网关选型、想搞清楚 Higress 到底强在哪的架构师二是已经在用 Higress、但遇到性能或插件问题不知道怎么排查的一线工程师三是对 Envoy、Istio、Wasm 这套技术栈感兴趣、想通过一个真实项目把它们串起来理解的技术爱好者。我会尽量把原理讲透同时把踩过的坑和实操细节都摊开来说不玩虚的。2. 整体架构拆解Higress 到底由哪几块拼起来2.1 数据面为什么选 Envoy 而不是自研Higress 的数据面直接复用了 Envoy这一点很多人第一反应是“那不就是套壳吗”。但真正做过网关的人知道自研一个高性能数据面的成本极高。Envoy 经过多年打磨在连接管理、负载均衡、熔断限流、可观测性这些方面已经非常成熟尤其是它的线程模型和连接池设计是经过大规模生产验证的。Envoy 的核心线程模型是“多线程 每线程独立事件循环”。每个 worker 线程绑定一个 CPU 核独立处理自己负责的连接线程之间几乎不共享状态。这种设计的好处是避免了锁竞争坏处是配置更新时需要每个线程各自处理。Higress 选择 Envoy本质上是选择了一套已经被验证过的数据面基础设施把精力集中在控制面和插件生态上。但直接裸用 Envoy 有个问题Envoy 的配置是静态的 xDS 协议业务团队要改一个路由规则得理解 Cluster、Listener、RouteConfiguration 这一整套概念学习曲线陡峭。Higress 的价值就在于把这层复杂度封装掉了。2.2 控制面与 Istio 的关系兼容但不依赖Higress 的控制面兼容 Istio 的 xDS 协议这意味着它可以把 Istio 作为控制面来用也可以用自己的控制面。这里有个常见的误解很多人以为 Higress 必须依赖 Istio 才能跑。实际上 Higress 的控制面是独立的它自己实现了一套配置管理逻辑只是对外暴露的接口遵循 xDS 标准。为什么这么做因为 xDS 已经是云原生网关的事实标准Envoy、Istio、gRPC 都在用。遵循这个标准意味着 Higress 可以无缝接入现有的服务网格体系同时又不被 Istio 的复杂度绑架。你可以只用 Higress 做南北向流量网关也可以把它作为东西向网格的一部分灵活性很高。控制面的核心职责是把用户配置K8s Ingress、CRD、控制台操作转换成 Envoy 能理解的 xDS 配置然后通过 gRPC 流推送给数据面。这个转换过程是 Higress 最核心的逻辑之一后面我会专门拆解。2.3 Wasm 插件机制扩展性的关键落子传统 Nginx 网关扩展靠 Lua 脚本性能损耗大而且 Lua 生态和云原生脱节。Higress 选择 Wasm 作为插件运行时这是一个很有前瞻性的决定。Wasm 的优势在于语言无关可以用 C、Rust、Go 写插件、沙箱隔离插件崩溃不影响主进程、接近原生的性能。Higress 的 Wasm 插件运行在 Envoy 的 Wasm 运行时里通过 ABI 和 Envoy 交互。插件可以挂载在请求处理的各个阶段请求头解析、请求体处理、响应头处理、响应体处理。这种细粒度的挂载能力让插件可以做鉴权、限流、日志、协议转换等各种事情。但 Wasm 也不是银弹。我实测下来一个简单的鉴权插件Wasm 版本的延迟比原生 C 插件高 10% 到 20%比 Lua 版本低 30% 左右。这个损耗在大多数场景下可以接受但在极致性能场景下需要权衡。3. 配置流转全链路一条路由规则是怎么生效的3.1 从 K8s Ingress 到 xDS 配置的转换过程假设你在 K8s 里创建了一个 Ingress 资源声明了域名api.example.com转发到后端服务order-service。这条规则要生效需要经过以下步骤第一步Higress 的控制面监听 K8s API Server感知到 Ingress 资源的创建事件。这里用的是 Informer 机制不是轮询所以感知延迟通常在毫秒级。第二步控制面把 Ingress 资源转换成内部的数据模型。这个模型比 K8s 原生 Ingress 更丰富支持灰度、熔断、重试等高级配置。转换过程中会做校验比如域名格式、后端服务是否存在、端口是否合法。第三步内部模型被翻译成 Envoy 的 xDS 配置。具体来说域名和路由规则会生成 RouteConfiguration后端服务会生成 Cluster监听端口会生成 Listener。这一步是 Higress 最核心的逻辑代码量也最大。第四步xDS 配置通过 gRPC 流推送给数据面。Envoy 收到配置后会在所有 worker 线程中生效。这个过程是增量的只推送变化的部分不是全量刷新。整个链路走下来从 Ingress 创建到规则生效实测延迟在 100 毫秒到 500 毫秒之间取决于配置复杂度和数据面实例数量。3.2 xDS 增量推送的底层逻辑Envoy 的 xDS 协议支持全量推送和增量推送两种模式。全量推送是把所有配置重新发一遍简单但浪费带宽而且配置量大时会导致数据面短暂抖动。增量推送只发变化的部分效率高但实现复杂。Higress 默认用的是增量推送。它的实现逻辑是控制面维护一份当前配置的快照每次有变更时计算新旧快照的差异只把差异部分推送给数据面。Envoy 收到增量配置后会做合并操作。这里有个坑我踩过如果增量推送的版本号管理出问题数据面可能会处于一个“半新半旧”的状态导致路由规则部分生效部分不生效。排查这种问题时可以看 Envoy 的/config_dump接口对比控制面的配置快照看差异在哪里。3.3 配置生效的时序问题与排查方法配置生效不是瞬间完成的它有一个时序控制面生成配置 - 推送 - 数据面接收 - 校验 - 应用。任何一个环节出问题都会导致配置不生效。我整理了一个排查清单按顺序检查排查步骤检查方法常见问题控制面是否感知变更看控制面日志Informer 未启动或权限不足配置转换是否成功看控制面错误日志配置格式错误或校验失败xDS 推送是否成功看控制面推送日志gRPC 连接断开或超时数据面是否接收看 Envoy 日志版本号冲突或校验失败配置是否应用调/config_dump应用失败但无日志提示排查配置问题时先确认控制面和数据面的版本号是否一致。版本号不一致是很多诡异问题的根源。4. Wasm 插件深度剖析从加载到执行的完整生命周期4.1 插件加载机制与沙箱隔离原理Higress 的 Wasm 插件加载有两种方式一种是编译进镜像随数据面一起启动另一种是动态加载通过控制面下发插件二进制。动态加载更灵活但需要处理版本管理和依赖问题。插件加载时Envoy 会为每个插件创建一个独立的 Wasm 运行时实例。这个实例运行在沙箱里和主进程的内存空间隔离。插件崩溃时Envoy 会捕获异常并记录日志不会导致整个数据面挂掉。这是 Wasm 相比原生插件最大的优势。但沙箱隔离也有代价。插件和 Envoy 之间的数据交换需要通过 ABI 序列化和反序列化这会产生额外的 CPU 开销。我实测过一个日志插件Wasm 版本比原生版本多消耗约 15% 的 CPU。4.2 请求处理各阶段的挂载点与执行顺序Envoy 的请求处理分为多个阶段Wasm 插件可以挂载在以下阶段onRequestHeaders请求头解析完成后此时可以修改请求头、做鉴权onRequestBody请求体读取时可以修改请求体、做内容校验onResponseHeaders响应头生成后可以修改响应头、做日志onResponseBody响应体读取时可以修改响应体、做内容替换多个插件挂载在同一阶段时执行顺序由插件优先级决定。优先级高的先执行。这里有个容易忽略的点请求阶段的插件执行顺序和响应阶段是相反的。请求阶段是 A - B - C响应阶段就是 C - B - A类似栈的结构。4.3 一个鉴权插件的完整实现与性能实测我写过一个基于 JWT 的鉴权插件逻辑很简单从请求头里取 Token校验签名和过期时间校验通过就放行不通过就返回 401。核心代码逻辑如下用 Rust 写编译成 Wasm// 伪代码展示核心逻辑 fn on_request_headers(mut self) - Action { let token get_header(Authorization); if token.is_empty() { return Action::Pause; // 返回 401 } match verify_jwt(token) { Ok(claims) { set_header(X-User-Id, claims.user_id); Action::Continue } Err(_) Action::Pause, } }性能实测数据在 4 核 8G 的测试机上单插件 QPS 约 12000P99 延迟增加 0.8 毫秒。对比不加插件的基线延迟增加约 12%。这个损耗在鉴权场景下完全可以接受。注意Wasm 插件里不要做阻塞操作比如同步的 HTTP 请求或文件读写。一旦阻塞会卡住整个 worker 线程导致所有请求排队。需要外部调用时用异步方式或者把逻辑放到控制面。5. 性能调优实战从参数到架构的优化路径5.1 关键参数调优连接池、线程数、缓冲区Envoy 的性能调优参数很多但真正影响大的就那么几个。我按重要性排序连接池配置。Envoy 和后端服务之间的连接池大小直接影响吞吐。默认配置偏保守高并发场景下需要调大。关键参数是max_connections和max_pending_requests。我的经验值是max_connections设为后端服务实例数乘以 20max_pending_requests设为max_connections的 2 倍。Worker 线程数。Envoy 默认使用所有 CPU 核作为 worker 线程数。但在容器环境里如果 CPU limit 设得比实际核数小会导致线程争抢。建议把 worker 线程数显式设置为 CPU limit 的值。缓冲区大小。Envoy 的读写缓冲区默认是 32KB大请求体场景下需要调大。但调太大又浪费内存需要根据实际请求体大小分布来定。我一般设为 P99 请求体大小的 2 倍。5.2 长连接场景下的内存与 GC 优化长连接场景比如 WebSocket、gRPC 流对网关的内存管理要求很高。每个长连接都会占用一定的内存连接数一多内存增长很快。Higress 在这方面的优化主要是连接复用和内存池化。Envoy 内部用内存池管理连接相关的对象避免频繁分配释放。但 Wasm 插件如果自己管理内存容易出问题。我踩过一个坑插件里缓存了请求级别的数据但没有在连接关闭时清理导致内存泄漏。后来改成用 Envoy 提供的生命周期钩子来清理问题才解决。5.3 压测数据对比Higress vs Nginx vs 原生 Envoy我在测试环境做了一组对比压测场景是简单的反向代理后端是一个返回固定 JSON 的服务。测试机 8 核 16G客户端用 wrk持续压测 5 分钟。网关方案QPSP99 延迟CPU 使用率内存占用Nginx2800012ms65%120MB原生 Envoy2500015ms72%180MBHigress无插件2400016ms75%220MBHigress带鉴权插件2100019ms82%260MB数据说明几个问题Higress 相比原生 Envoy 有约 4% 的性能损耗主要来自控制面交互和插件框架的开销。带插件后性能下降约 12%这个损耗在可接受范围内。相比 NginxHigress 的 QPS 略低但功能丰富度和动态化能力完全不是一个量级。提示压测时一定要用和生产一致的配置包括 TLS、插件、日志级别。我见过有人用默认配置压测结果上线后性能差一半就是因为生产开了 access log 和 TLS。6. 踩坑实录那些文档里不会写的经验6.1 配置不生效的五个常见原因第一个原因Ingress 的ingressClassName没配对。Higress 有自己的 IngressClass如果 Ingress 资源没指定或者指定错了控制面根本不会处理。第二个原因命名空间隔离。Higress 默认只监听特定命名空间的 Ingress跨命名空间需要额外配置。第三个原因配置冲突。多条 Ingress 规则匹配同一个域名和路径时优先级规则可能导致你预期的规则没生效。第四个原因xDS 推送失败但没报错。这种情况最坑控制面日志显示推送成功但数据面没应用。通常是版本号管理出了问题。第五个原因数据面缓存。Envoy 有配置缓存极端情况下会用到旧配置。重启数据面可以强制刷新。6.2 Wasm 插件内存泄漏的排查过程前面提到过插件内存泄漏的坑这里详细说一下排查过程。现象是数据面内存持续增长每天涨 200MB 左右重启后恢复。排查步骤先用 Envoy 的/memory接口看内存分布发现 Wasm 运行时占用的内存一直在涨。然后逐个禁用插件定位到是哪个插件的问题。最后看插件代码发现是缓存没清理。解决方法是在插件的on_connection_close钩子里清理缓存。这个钩子 Envoy 会保证调用即使连接异常关闭也会触发。6.3 灰度发布场景下的路由优先级陷阱灰度发布是网关的高频场景但路由优先级很容易踩坑。假设你有两条规则一条是header: x-version v2转发到 v2 服务另一条是默认转发到 v1 服务。如果默认规则的优先级更高灰度规则就永远不会生效。Higress 的路由匹配是按优先级排序的优先级高的先匹配。配置时一定要确保灰度规则的优先级高于默认规则。我建议用显式的优先级字段不要依赖默认排序。7. 插件开发进阶从写一个到写好一个7.1 插件性能优化的三个关键点第一个关键点是减少 ABI 调用。每次插件和 Envoy 之间的数据交换都要走 ABI这是性能瓶颈。优化方法是批量读取需要的头信息而不是一个一个读。第二个关键点是避免内存分配。Wasm 里的内存分配比原生慢频繁分配会拖慢性能。尽量复用缓冲区或者用栈上分配。第三个关键点是异步化外部调用。插件里如果需要调用外部服务比如鉴权中心一定要用异步方式不能阻塞。7.2 插件配置热更新的实现方式插件配置热更新是个常见需求。比如限流插件的阈值需要动态调整不能重启数据面。Higress 支持通过控制面下发插件配置。实现方式是插件在初始化时注册一个配置回调控制面配置变更时回调被触发插件更新内部状态。这个过程不需要重启插件是真正的热更新。但要注意配置更新时如果有正在处理的请求可能会出现新旧配置混用的情况。对一致性要求高的场景需要加版本号或者锁机制。7.3 多插件协同工作的注意事项多个插件同时工作时要注意执行顺序和状态共享。执行顺序前面说过请求阶段和响应阶段相反。状态共享方面插件之间不能直接通信需要通过 Envoy 的元数据机制间接传递。我遇到过一个场景鉴权插件解析出的用户信息需要在日志插件里使用。解决方案是鉴权插件把用户信息写到请求头里日志插件从请求头读。这种方式简单但不够优雅请求头会被传到后端服务。更好的方式是用 Envoy 的动态元数据但配置起来更复杂。8. 选型建议什么场景该用 Higress什么场景不该用8.1 适合 Higress 的三类典型场景第一类是 K8s 环境下的南北向网关。Higress 和 K8s 生态集成好Ingress 资源直接转换运维成本低。第二类是需要动态扩展能力的网关。Wasm 插件机制让业务团队可以自己写插件不用改网关核心代码。第三类是需要和 Istio 服务网格协同的场景。Higress 兼容 xDS 协议可以作为网格的入口网关。8.2 不建议使用 Higress 的情况如果你的场景是超高性能、超低延迟比如 P99 要求 1 毫秒以内Higress 的抽象层会带来额外开销不如直接用 Nginx 或者原生 Envoy。如果你的团队没有 K8s 环境Higress 的优势发挥不出来传统网关可能更合适。如果你只需要最简单的反向代理功能不需要动态配置和插件扩展用 Higress 是杀鸡用牛刀。8.3 从 Nginx 迁移到 Higress 的实操路线迁移不是一蹴而就的我建议分三步走第一步并行运行。Nginx 和 Higress 同时在线用 DNS 或者负载均衡把部分流量切到 Higress验证功能。第二步功能对齐。把 Nginx 的配置逐条翻译成 Higress 的 Ingress 或 CRD确保行为一致。重点检查重写规则、超时配置、限流策略。第三步全量切换。确认无误后把流量全部切到 HigressNginx 保留作为回滚方案观察一周后下线。迁移过程中最容易出问题的是重写规则和正则匹配两边的语法不完全一样需要仔细测试。9. 最后分享几个我常用的排查命令排查 Higress 问题时这几个命令我几乎每次都会用到查看 Envoy 当前配置kubectl exec -it higress-pod -- curl localhost:15000/config_dump查看 Envoy 统计信息kubectl exec -it higress-pod -- curl localhost:15000/stats查看 Wasm 插件状态kubectl exec -it higress-pod -- curl localhost:15000/wasm查看控制面推送日志kubectl logs higress-controller-pod -f | grep xds这几个接口在排查配置不生效、性能异常、插件问题时非常有用。尤其是/config_dump能看到数据面实际生效的配置是排查配置问题的第一手资料。我个人在实际操作中的体会是Higress 这套东西上手不难但要用好需要理解它背后的 Envoy 和 xDS 机制。遇到问题时不要只看 Higress 的日志要往下钻到 Envoy 层面很多答案都在那里。另外Wasm 插件虽然灵活但不要滥用能用原生配置解决的问题就不要写插件每多一层抽象就多一份性能损耗和排查成本。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑