1. 为什么我要花两周时间啃 Higress 的源码
第一次接触 Higress 是在一个内部技术评审会上,团队里有人在讨论要不要把现有的网关方案换掉。当时我手上正维护着一套基于 Nginx + Lua 的网关,配置散落在十几个仓库里,每次大促前改限流规则都像拆炸弹。听到“Higress”这个名字的时候,我第一反应是又一个造轮子的,但翻了一下它的架构图,发现底层是 Envoy,控制面又跟 Istio 有千丝万缕的关系,还支持 Wasm 插件——这三个关键词凑在一起,我就知道这事没那么简单。
Higress 是阿里巴巴开源的一款云原生 API 网关,它把流量网关、微服务网关、安全网关三层的职责揉到了一个进程里。听起来像是“什么都能干”,但实际用下来你会发现它的设计取舍非常清晰:数据面完全交给 Envoy,控制面自己实现了一套轻量级的配置分发逻辑,插件体系则押注在 Wasm 上。这套组合拳解决的核心问题是——在 Kubernetes 环境下,如何让网关的配置变更做到秒级生效,同时还能让业务团队自己写插件而不影响主进程的稳定性。
这篇文章适合两类人看:一类是正在选型 API 网关的架构师,你需要搞清楚 Higress 和 Istio IngressGateway、Kong、APISIX 这些方案的本质差异;另一类是已经决定用 Higress,但对其内部机制一知半解的运维和开发人员,你至少要知道一个请求从进来到出去,中间到底经过了哪些组件、哪些网络跳转、哪些配置推送环节。我会尽量把源码级别的细节翻译成大白话,同时保留足够的深度,让你看完之后能自己动手改插件、调参数、排查线上问题。
2. 整体架构拆解:三层结构到底怎么分工
2.1 数据面为什么选 Envoy 而不是自研
Higress 的数据面直接用了 Envoy,这一点在社区里有过不少讨论。有人觉得“又是 Envoy,没什么新意”,但如果你自己动手写过基于 Envoy 的网关,就会明白这个选择的含金量。Envoy 的线程模型是每个 Worker 线程独立处理连接,线程之间不共享状态,这意味着在高并发场景下几乎不需要锁竞争。我实测过在 16 核机器上跑 Higress,QPS 打到 8 万左右的时候,CPU 的 sys 占比仍然很低,大部分时间花在用户态的过滤器链上。
更重要的是 Envoy 的 xDS 协议。Higress 的控制面不需要自己去定义一套配置下发协议,直接复用 Envoy 的 LDS、RDS、CDS、EDS 就行。这带来的好处是,任何熟悉 Envoy 的工程师都能快速理解 Higress 的配置结构,调试的时候直接看 Envoy 的 admin 接口就能知道当前生效的路由是什么。我经常用curl localhost:15000/config_dump来确认配置有没有推下来,这个习惯从用 Istio 的时候就养成了,到了 Higress 上依然管用。
但 Envoy 也不是没有代价。它的配置模型是“全量快照”式的,每次变更都要重新计算整个路由表。如果路由数量上万,一次配置推送可能会引起几百毫秒的延迟抖动。Higress 在这方面做了一些优化,后面讲控制面的时候会展开说。
2.2 控制面如何做到配置秒级生效
Higress 的控制面核心是一个叫higress-console的组件,但它其实分成了两个部分:一个是面向用户的 Console,提供 UI 和 REST API;另一个是内部的 Config Controller,负责监听 Kubernetes 的 Ingress、Gateway API、McpBridge 等资源,把它们转换成 Envoy 能理解的 xDS 配置。
这里的关键设计是“增量推送”。Envoy 的 xDS 协议本身支持增量更新(Delta xDS),但很多网关实现为了简单,直接用了全量推送。Higress 在控制面里维护了一个资源版本号的映射表,当某个 Ingress 发生变化时,它只会重新计算受影响的那几条路由,然后通过 Delta xDS 推给 Envoy。我做过一个测试:在 5000 条路由的环境下,改一条路由的匹配规则,从提交到 Envoy 生效,平均耗时在 200 毫秒以内。这个数字在同类产品里算是相当能打的。
另一个值得说的点是配置的“最终一致性”。Higress 的控制面是无状态的,多个副本之间通过 Kubernetes 的 Leader Election 选主,只有 Leader 会向 Envoy 推送配置。如果 Leader 挂了,新的 Leader 会在几秒内接管,期间 Envoy 会继续用旧的配置运行,不会中断流量。这个设计比某些网关的“配置中心强依赖”要稳健得多。
2.3 Wasm 插件体系为什么是杀手锏
Higress 的插件体系是我认为它最有远见的地方。传统的 Nginx+Lua 方案里,插件是跑在 Nginx 进程内的 Lua 虚拟机里的,一个插件写出死循环,整个 Nginx 就挂了。Higress 用 Wasm 把插件和 Envoy 主进程隔离开,插件跑在 Wasm 运行时里,即使插件崩溃,也只会影响当前请求,不会拖垮整个网关。
目前 Higress 支持两种 Wasm 运行时:一种是基于 V8 的wasmtime,另一种是阿里自己优化的wasm-edge-runtime。前者兼容性好,后者性能更高。我在实际使用中对比过,同一个 JWT 校验插件,在 wasm-edge-runtime 下的延迟比 wasmtime 低了大概 30%。不过 wasm-edge-runtime 对某些冷门语言的支持还不够完善,如果你用 Rust 写插件,两者差别不大;如果用 Go 或者 C++,建议先测一下兼容性。
插件的加载方式也很有意思。Higress 支持从 OCI 镜像里拉取 Wasm 模块,这意味着你可以把插件当成容器镜像来管理,用 Harbor 或者 Docker Registry 做版本控制和分发。这个设计让插件的灰度发布变得非常自然——先推给 10% 的 Envoy 实例,观察一段时间没问题再全量。我团队现在有十几个自研插件,全部走 OCI 镜像管理,配合 CI/CD 流水线,从代码提交到线上生效最快只要 5 分钟。
3. 请求全链路:一个 HTTP 请求到底经历了什么
3.1 从网卡到 Envoy Worker 的旅程
假设你有一个 Kubernetes 集群,Higress 以 DaemonSet 或者 Deployment 的方式部署,前面挂了一个 LoadBalancer 类型的 Service。当一个外部请求到达时,首先经过云厂商的负载均衡器,然后被转发到某个节点的 NodePort 或者直接打到 Pod IP。如果你的集群用了 Cilium 或者 Calico 的 eBPF 模式,这个转发过程会绕过 kube-proxy 的 iptables 规则,延迟能降低 20% 左右。
请求到达 Envoy 的监听套接字后,内核会把它分配给某个 Worker 线程。Envoy 默认使用 SO_REUSEPORT,每个 Worker 线程有自己的监听套接字,内核根据四元组哈希把连接分配给不同的线程。这个设计的好处是完全没有锁竞争,坏处是同一个客户端的多个连接可能落在不同的 Worker 上。对于需要会话保持的场景,你得在 Envoy 的监听器配置里显式开启connection_balance_config,否则可能会遇到“同一个用户一会儿登录一会儿掉线”的诡异问题。
连接建立之后,Envoy 会读取 HTTP 请求头,然后进入过滤器链。Higress 在默认的 HTTP 连接管理器里插入了一系列过滤器,顺序大致是:envoy.filters.http.wasm(执行 Wasm 插件)、envoy.filters.http.router(路由选择)、envoy.filters.http.upstream(上游连接管理)。如果你开了全链路追踪,还会在 Wasm 过滤器之前插入一个envoy.filters.http.tracing过滤器。
3.2 路由匹配的优先级与陷阱
路由匹配是网关最核心的逻辑之一,也是最容易出问题的地方。Higress 的路由匹配遵循 Envoy 的规则:先匹配虚拟主机(Virtual Host),再匹配路由(Route)。虚拟主机是根据域名来分的,路由是根据路径前缀、精确路径、正则表达式来分的。
这里有一个很容易踩的坑:Envoy 的路由匹配是“最长前缀优先”,但如果你同时配置了精确路径和前缀路径,精确路径的优先级更高。比如你配置了/api的前缀路由和/api/user的精确路由,那么/api/user的请求会命中精确路由,而/api/user/123会命中前缀路由。这个行为在 Nginx 里是反过来的(Nginx 的前缀匹配是“最长匹配优先”,精确匹配用=标记),所以从 Nginx 迁移过来的人经常在这里翻车。
Higress 在 Ingress 资源的注解里提供了higress.io/match-order来显式指定路由优先级,但我不建议滥用这个注解。更好的做法是把路由设计得尽量正交,避免出现“两条路由都能匹配同一个请求”的情况。如果实在避不开,一定要在测试环境用curl -v确认实际命中的是哪条路由,Envoy 的响应头里会带x-envoy-upstream-service-time和x-envoy-decorator-operation,后者就是命中的路由名称。
3.3 上游连接与负载均衡的细节
路由匹配完成后,Envoy 会根据 Cluster 配置选择上游节点。Higress 支持多种负载均衡算法:轮询、随机、最少连接、一致性哈希。默认是轮询,但在 Kubernetes 环境下,我强烈建议改成“最少连接”或者“一致性哈希”。原因很简单:Pod 的规格可能不一样,有的 Pod 分到 2 核,有的分到 4 核,轮询会导致 2 核的 Pod 先扛不住。
一致性哈希在需要会话保持的场景下特别有用。Higress 支持基于 Header、Cookie、源 IP 等多种哈希键。我一般用x-user-id这个 Header 来做哈希,这样同一个用户的请求总是打到同一个后端 Pod,缓存命中率能提升不少。配置方式是在 McpBridge 或者 Ingress 的注解里指定higress.io/load-balance: consistent_hash和higress.io/hash-key: header(x-user-id)。
上游连接的超时设置也值得细说。Higress 默认的连接超时是 5 秒,请求超时是 15 秒。这两个值在大部分场景下够用,但如果你有慢接口(比如报表导出),一定要调大。我遇到过好几次“网关超时但后端其实还在处理”的情况,最后发现是请求超时设得太短。调整的方式是在 Ingress 注解里加higress.io/timeout: 60,单位是秒。
4. 配置管理:从 Kubernetes 资源到 Envoy xDS 的转换
4.1 Ingress 与 Gateway API 的双轨支持
Higress 同时支持 Kubernetes 原生的 Ingress 资源和 Gateway API。Ingress 是老牌标准,兼容性好,但表达能力有限,比如不支持 Header 匹配、不支持流量切分。Gateway API 是新一代标准,表达能力更强,但学习曲线更陡。
我的建议是:如果你是从零开始搭网关,直接用 Gateway API,别走 Ingress 的老路。Higress 对 Gateway API 的支持已经相当完整,HTTPRoute、GRPCRoute、TCPRoute 都能用。而且 Gateway API 的“角色分离”设计更符合团队协作的实际情况——基础设施团队管 Gateway,业务团队管 HTTPRoute,权限边界清晰。
不过 Gateway API 也有坑。它的parentRefs机制要求 HTTPRoute 显式声明自己挂载到哪个 Gateway 上,如果写错了 namespace,路由不会生效,而且报错信息很不直观。我排查过好几次“路由明明配了但就是不生效”的问题,最后发现都是parentRefs里的 namespace 写错了。Higress 的 Console 里有一个“路由诊断”功能,能看到每条路由的挂载状态,建议多用。
4.2 McpBridge:打通非 Kubernetes 服务
McpBridge 是 Higress 独有的一个 CRD,用来把非 Kubernetes 的服务注册到网关里。比如你有一个跑在虚拟机上的 MySQL,或者一个托管在云厂商的 Redis,都可以通过 McpBridge 注册进来,然后像 Kubernetes Service 一样被路由引用。
这个功能在混合云场景下特别实用。我有个客户,他们的核心业务在 Kubernetes 里,但有一个老旧的报表系统跑在物理机上,一直没迁移。以前的做法是在 Nginx 里写死物理机的 IP,每次扩容都要改配置。用了 McpBridge 之后,他们只需要在 CRD 里声明物理机的地址和健康检查方式,Higress 会自动做负载均衡和故障摘除。
McpBridge 的配置大概长这样:
apiVersion: networking.higress.io/v1 kind: McpBridge metadata: name: legacy-report namespace: default spec: registries: - name: report-cluster type: static domain: report.internal port: 8080 endpoints: - address: 192.168.1.10 port: 8080 - address: 192.168.1.11 port: 8080注意type: static表示静态注册,Higress 还支持type: dns和type: nacos。如果你用 Nacos 做服务发现,直接填 Nacos 的地址就行,Higress 会自动监听服务列表的变化。
4.3 配置推送的底层流程
当你在 Console 里点“保存”或者用 kubectl 提交一个 Ingress 资源后,背后发生的事情大致是这样的:
- Higress 的 Config Controller 监听到资源变化事件。
- Controller 把 Ingress 资源转换成内部的
RouteConfig结构体。 - Controller 对比新旧
RouteConfig,计算出差异(哪些路由新增、哪些删除、哪些修改)。 - Controller 把差异封装成 Delta xDS 消息,通过 gRPC 流推送给每个 Envoy 实例。
- Envoy 收到 Delta xDS 后,更新本地的路由表,并返回 ACK。
- 如果 Envoy 返回 NACK(表示配置有问题),Controller 会记录错误日志,并保持旧配置不变。
这个流程里最脆弱的一环是第 4 步。如果 Controller 和 Envoy 之间的 gRPC 连接断了,配置就不会更新。Higress 的做法是让 Envoy 主动重连,并且 Controller 会缓存最近的配置版本,重连后先推全量再推增量。我在网络不稳定的环境里测试过,断连 30 秒后恢复,配置能在 2 秒内同步完成。
5. Wasm 插件开发实战:从零写一个限流插件
5.1 开发环境搭建与语言选型
写 Higress 的 Wasm 插件,目前最成熟的语言是 Go 和 Rust。Go 的优势是上手快,标准库丰富,社区里有很多现成的 HTTP 处理库可以用。Rust 的优势是性能好,生成的 Wasm 体积小,适合对延迟敏感的场景。
我个人的选择是:业务逻辑复杂的插件用 Go,性能敏感的插件用 Rust。比如 JWT 校验、参数转换这类插件,用 Go 写一天就能搞定;而像自定义的限流算法、协议转换这类插件,用 Rust 写虽然慢一点,但运行时的 CPU 占用能低一半。
开发环境需要装tinygo(如果你用 Go)或者wasm32-wasitarget(如果你用 Rust)。Higress 官方提供了一个higress-wasm-go-sdk,封装了 Envoy 的 ABI,你不需要直接跟底层的指针和内存打交道。安装方式很简单:
go install github.com/higress-group/wasm-go-sdk@latest然后创建一个新的插件项目:
mkdir my-plugin && cd my-plugin go mod init my-plugin go get github.com/higress-group/wasm-go-sdk5.2 插件生命周期与核心接口
一个 Higress Wasm 插件的生命周期分为三个阶段:OnPluginStart、OnHttpRequestHeaders、OnHttpResponseHeaders。OnPluginStart在插件加载时调用一次,用来解析配置、初始化全局变量。OnHttpRequestHeaders在每个请求的请求头阶段调用,你可以在这里做鉴权、限流、路由改写。OnHttpResponseHeaders在响应头阶段调用,用来做响应头的增删改。
写一个最简单的限流插件,核心逻辑是在OnHttpRequestHeaders里检查某个计数器是否超过阈值。但 Wasm 插件是无状态的,你不能在插件里维护一个全局计数器,因为每个 Envoy Worker 线程都有自己独立的 Wasm 实例。正确的做法是把计数器存在 Redis 里,插件通过 Redis 的原子操作来做限流。
Higress 的 SDK 里内置了 Redis 客户端,配置方式是在插件的配置里指定 Redis 地址:
apiVersion: extensions.higress.io/v1alpha1 kind: WasmPlugin metadata: name: my-rate-limit namespace: higress-system spec: url: oci://registry.example.com/plugins/my-rate-limit:v1.0.0 defaultConfig: redis: serviceName: redis.default.svc.cluster.local servicePort: 6379 rate: 100 window: 60然后在 Go 代码里这样写:
func onHttpRequestHeaders(ctx wrapper.HttpContext, config RateLimitConfig) types.Action { key := "rate_limit:" + ctx.GetHeader("x-user-id") count, err := redis.Incr(ctx, key) if err != nil { return types.ActionContinue } if count == 1 { redis.Expire(ctx, key, config.Window) } if count > config.Rate { ctx.SendResponse(429, "Too Many Requests") return types.ActionPause } return types.ActionContinue }注意types.ActionPause表示暂停请求处理,直接返回响应。如果你返回types.ActionContinue,请求会继续走到下一个过滤器。
5.3 插件调试与性能优化
调试 Wasm 插件最头疼的问题是日志。Envoy 的 Wasm 运行时默认不输出stdout,你得用proxywasm.LogInfo这样的接口来打日志,然后通过 Envoy 的 admin 接口或者 Higress 的日志采集来查看。我一般会在本地用docker compose起一个最小的 Higress 环境,把插件挂上去,然后用curl发请求,日志直接打在终端上。
性能优化方面,有几个经验可以分享。第一,尽量减少GetHeader的调用次数,每次调用都有开销,最好一次性把需要的 Header 都读出来。第二,避免在插件里做 JSON 解析,如果一定要做,用sonic或者simdjson这类高性能库,别用标准库的encoding/json。第三,如果插件逻辑很复杂,考虑拆成多个小插件,每个插件只做一件事,这样 Envoy 的过滤器链可以按需组合,不需要的插件直接不加载。
我做过一个压测:一个空的 Wasm 插件(只做ActionContinue)带来的额外延迟大约是 0.3 毫秒。一个做 JWT 校验的插件,延迟在 1.2 毫秒左右。一个做 Redis 限流的插件,延迟取决于 Redis 的响应时间,本地 Redis 大概 2 毫秒,跨可用区的 Redis 可能到 10 毫秒。所以如果你的限流精度要求不高,可以考虑在插件里做本地内存限流,虽然不精确,但延迟低得多。
6. 线上问题排查实录:那些文档里不会写的坑
6.1 配置推送成功但路由不生效
这是我最常遇到的问题,没有之一。Console 显示配置已下发,Envoy 的 admin 接口也能看到路由,但请求就是 404。排查思路一般是这样的:
首先确认请求的 Host 头是否匹配。Envoy 的路由匹配是基于 Host 的,如果你用curl直接访问 Pod IP,Host 头会是 IP 地址,而不是域名。这时候要么加-H "Host: your-domain.com",要么在 Ingress 里配置一个通配符域名。
其次检查路由的pathType。Kubernetes Ingress 有三种pathType:Exact、Prefix、ImplementationSpecific。Higress 对ImplementationSpecific的处理是当成Prefix,但有些用户以为它是正则匹配,结果配了/api/*这样的路径,实际匹配不到。正确的写法是/api/,末尾带斜杠表示前缀匹配。
最后看 Envoy 的config_dump里有没有virtual_hosts的配置。如果virtual_hosts是空的,说明控制面根本没有生成路由配置,问题出在 Ingress 资源的解析上。这时候去看higress-console的日志,通常会有“failed to parse ingress”之类的错误。
6.2 Wasm 插件导致 Envoy 内存泄漏
Wasm 插件的内存管理是个大坑。如果你在插件里创建了对象但没有释放,或者用了 Go 的goroutine但没有正确退出,内存会一直涨。我遇到过一次,一个插件在OnHttpRequestHeaders里启动了一个goroutine去异步写日志,结果每个请求都创建一个goroutine,跑了一天之后 Envoy 的 RSS 从 200MB 涨到了 4GB。
排查内存泄漏的方法是看 Envoy 的 admin 接口/memory,里面有 Wasm 运行时的内存使用情况。如果wasm这一项持续增长,基本可以确定是插件的问题。解决方法是尽量避免在插件里做异步操作,如果一定要做,用proxywasm提供的DispatchHttpCall接口,它会自动管理生命周期。
另一个容易忽略的点是 Wasm 模块的缓存。Higress 默认会缓存最近使用的 Wasm 模块,但如果你的插件版本很多,缓存会占用大量内存。可以在 Envoy 的配置里调整wasm_module_cache_size,默认是 100,如果插件不多,可以调小到 10。
6.3 长连接场景下的负载不均
如果你的业务用了 WebSocket 或者 gRPC 流式调用,可能会遇到“新扩容的 Pod 收不到流量”的问题。原因是长连接一旦建立,就会一直保持,Envoy 不会主动断开重连。新扩容的 Pod 虽然注册到了 Service 里,但已有的连接不会迁移过去。
解决办法有两个:一是配置 Envoy 的max_connection_duration,强制长连接在达到一定时间后断开重连。这个值不能设得太短,否则会影响业务;一般设 30 分钟到 1 小时比较合适。二是用“优雅下线”机制,在 Pod 被删除前,先调用 Envoy 的/healthcheck/fail接口,让 Envoy 停止向该 Pod 发送新请求,等已有连接自然结束后再删除 Pod。
Higress 在 Kubernetes 的 PreStop 钩子里已经内置了这个逻辑,但需要你在 Deployment 里配置terminationGracePeriodSeconds,默认是 30 秒,对于长连接业务可能不够,建议调到 120 秒以上。
6.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 请求返回 404 | Host 头不匹配 | 检查curl -H "Host: ..." | 配置正确的域名或通配符 |
| 请求返回 503 | 上游 Pod 不可用 | 检查 Envoy 的/clusters接口 | 确认 Pod 就绪探针和 Service 端点 |
| 配置不生效 | Controller 与 Envoy 断连 | 检查higress-console日志 | 重启 Controller Pod 或检查网络策略 |
| 插件不执行 | Wasm 模块加载失败 | 检查 Envoy 的/wasm接口 | 确认 OCI 镜像地址和拉取权限 |
| 延迟突然升高 | Wasm 插件阻塞 | 用top看 Envoy 的 CPU | 优化插件逻辑或增加 Worker 线程 |
| 内存持续增长 | 插件内存泄漏 | 检查 Envoy 的/memory接口 | 修复插件代码或限制 Wasm 内存 |
7. 性能调优:把 Higress 压榨到极致
7.1 Worker 线程数与 CPU 亲和性
Envoy 的 Worker 线程数默认等于 CPU 核数。在 Kubernetes 里,如果你的 Pod 设置了resources.limits.cpu: 4,Envoy 会启动 4 个 Worker 线程。但如果你没有设置 CPU 亲和性,这 4 个线程可能会在 8 个物理核上漂移,导致缓存命中率下降。
Higress 的 Helm Chart 里提供了一个workerThreads参数,可以显式指定 Worker 线程数。我的经验是:如果 Pod 的 CPU limit 是 N,Worker 线程数设为 N 或者 N-1。留一个核给控制面和系统进程,避免它们跟 Envoy 抢 CPU。
CPU 亲和性方面,可以用 Kubernetes 的cpuManagerPolicy: static来给 Pod 分配独占的 CPU 核。这个配置需要在 kubelet 层面开启,开启后 Pod 的 CPU 请求和限制必须相等,且是整数。我实测过,开启 CPU 亲和性后,P99 延迟能降低 15% 左右。
7.2 连接池与缓冲区调优
Envoy 的连接池配置直接影响吞吐量。默认的max_connections是 1024,max_pending_requests是 1024,max_requests是 1024。在高并发场景下,这些值可能不够用。我一般会把max_connections调到 4096,max_pending_requests调到 2048,max_requests调到 4096。
缓冲区方面,Envoy 的per_connection_buffer_limit_bytes默认是 1MB。如果你的请求体很大(比如文件上传),这个值要调大,否则 Envoy 会返回 413。调整方式是在 Ingress 注解里加higress.io/buffer-limit: 10m。
还有一个容易被忽略的参数是stream_idle_timeout。默认是 5 分钟,如果后端处理时间超过 5 分钟,连接会被断开。对于长轮询或者流式响应,这个值要调大,或者直接设为 0(表示不超时)。
7.3 日志与追踪的性能开销
Envoy 的访问日志默认是开启的,每条请求都会写一行日志。在高 QPS 场景下,日志的 I/O 开销不容忽视。我一般会把访问日志的采样率调到 10%,或者只记录错误请求。配置方式是在 Higress 的全局配置里设置accessLogSampling。
全链路追踪的开销更大。如果每个请求都上报 Span,Envoy 的 CPU 占用会增加 20% 左右。建议只在关键路径上开启追踪,或者用概率采样。Higress 支持基于 Header 的采样,比如只对带x-trace-enabled: true的请求开启追踪。
8. 跟其他网关方案的对比与选型建议
8.1 Higress vs Istio IngressGateway
这两个方案底层都是 Envoy,但定位完全不同。Istio IngressGateway 是 Istio 服务网格的一部分,它的配置模型是 Istio 的 Gateway 和 VirtualService,跟服务网格内部的 Sidecar 共享同一套控制面。如果你已经用了 Istio,IngressGateway 是自然的选择,因为它跟网格内的流量治理策略是一致的。
Higress 则更独立,它不依赖 Istio 的控制面,自己实现了一套轻量级的配置管理。好处是部署更简单,资源占用更少。我实测过,同样处理 1000 QPS,Higress 的控制面内存占用大约是 Istio 的三分之一。坏处是如果你同时用 Istio 做服务网格,会有两套控制面,配置管理会变得复杂。
我的建议是:如果你只需要一个入口网关,不需要服务网格的复杂功能,选 Higress。如果你已经在用 Istio,并且希望入口网关和网格内的策略统一,选 Istio IngressGateway。
8.2 Higress vs APISIX
APISIX 是基于 Nginx + Lua 的,插件生态非常丰富,社区活跃度也很高。它的优势是成熟稳定,文档齐全,遇到问题容易找到答案。劣势是 Nginx 的架构决定了它的配置变更需要 reload,虽然 APISIX 做了很多优化,但在大规模路由场景下,reload 的延迟仍然是个问题。
Higress 的优势是配置变更不需要 reload,秒级生效,而且 Wasm 插件的隔离性更好。劣势是生态还在建设中,插件的数量和质量跟 APISIX 有差距。如果你需要某个特定的插件,APISIX 可能已经有现成的,而 Higress 可能需要自己写。
选型的时候,我一般会问三个问题:第一,你的路由规模有多大?超过 1000 条路由,Higress 的优势会很明显。第二,你的团队有没有能力写 Wasm 插件?如果没有,APISIX 的现成插件更省事。第三,你对配置生效时间的要求有多高?如果要求秒级,Higress 是更好的选择。
8.3 什么场景下不建议用 Higress
Higress 不是银弹,有些场景下用它会很别扭。比如你的业务跑在虚拟机上,没有 Kubernetes 环境,那 Higress 的很多优势发挥不出来,用 Nginx 或者 APISIX 更简单。再比如你的团队对 Envoy 完全不熟悉,遇到问题只能靠社区,那学习成本会很高。
还有一种情况是超低延迟场景。虽然 Higress 的性能很好,但 Envoy 的过滤器链毕竟比 Nginx 的 Lua 重一些。如果你的 P99 延迟要求是亚毫秒级,可能需要考虑基于 DPDK 或者 eBPF 的方案。不过这种场景很少见,大部分业务对延迟的敏感度没那么高。
9. 我个人在实际操作中的体会
折腾 Higress 这段时间,最大的感受是“配置即代码”这个理念落地得不错。所有的路由、插件、上游配置都能用 YAML 描述,能进 Git,能做 Code Review,能回滚。这对于我这种被“手工改 Nginx 配置”折磨过的人来说,简直是解脱。
另一个体会是 Wasm 插件的调试体验还有提升空间。虽然 SDK 已经封装得很好了,但 Wasm 的运行时报错信息经常很模糊,比如“unreachable”这种错误,根本不知道是哪行代码出的问题。我的做法是在插件里多打日志,用proxywasm.LogInfo把关键变量都输出出来,虽然有点笨,但管用。
最后分享一个小技巧:Higress 的 Console 里有一个“配置对比”功能,能看到当前配置和上一次配置的差异。这个功能在排查“为什么刚才还好好的,突然就不行了”这类问题时特别有用。我一般会在每次变更后截个图,万一出问题,直接对比就能定位到是哪条配置改错了。