第 07 期 / 共 10 期

服务发现与 McpBridge

Higress 区别于纯 K8s Ingress 最关键的能力——把 Nacos / ZK / Consul / Eureka 等注册中心的服务注入网关。本期 20 讲拆解 registry/ 各实现。

L01

为什么要做服务来源

大型企业中,服务往往同时存在于多种注册中心:K8s Service + Nacos(Spring Cloud / Dubbo) + ZK(老 Dubbo) + 自建。Higress 一统这些来源,让 Ingress 可路由到任何 host,无需自己写 ServiceEntry。

思考:如果只用 K8s Service 行不行?答:行,但企业混合架构时不够。
L02

McpBridge CRD

apiVersion: networking.higress.io/v1
kind: McpBridge
metadata: { name: default, namespace: higress-system }
spec:
  registries:
    - name: nacos1
      type: nacos2
      domain: nacos.example.com
      port: 8848
      nacosNamespaceId: public
      nacosGroups: [DEFAULT_GROUP]
    - name: zk1
      type: zookeeper
      domain: zk.example.com
      port: 2181
      zkServicesPath: [/dubbo]

一个 namespace 通常只有一份 default McpBridge,名称固定。

思考:为什么是单例?答:避免冲突,简化语义。
L03

McpBridgeController 薄壳

49 行的控制器:监听 McpBridge 资源 → 把整个 spec 交给 IngressConfig 的 AddOrUpdateMcpBridge → IngressConfig 把它交给 registry/reconcile。Controller 自己不做协议解析。

思考:为什么 controller 这么薄?答:协议实现独立,便于测试 + 扩展。
L04

registry/ 总览

registry/
  watcher.go          # 统一接口
  auth_option.go      # 鉴权选项
  mcp_model.go        # 服务模型
  memory/             # 内存 ServiceRegistry
  reconcile/          # 协调器
  nacos/{,v2,address,mcpserver}
  zookeeper/、consul/、eureka/{,client}
  direct/             # 静态配置
  proxy/              # 代理模式
思考:为什么 nacos/v2 单独包?答:Nacos 2.x SDK 不兼容 1.x,API 大改。
L05

Watcher 接口

type Watcher interface {
    Run()
    Stop()
    IsHealthy() bool
    GetRegistryType() string
    ReadyHandler(func(bool))
    AppendServiceUpdateHandler(func(*v1.ServiceEntry, model.Event))
}

每种注册中心都要实现这套接口。统一把"服务变更 → Istio ServiceEntry"作为输出,下游无需关心具体协议。

思考:为什么用 ServiceEntry 而不是自定义模型?答:复用 Istio Push 链路。
L06

reconcile 协调器

registry/reconcile/ 是真正的"大脑":

  1. 对比新旧 McpBridge.spec.registries;
  2. 需要新增的 → 工厂创建 Watcher 并 Run;
  3. 需要删除的 → Watcher.Stop;
  4. 变更(域名 / 端口)→ Stop 后重建。

所有 Watcher 共享同一份 memory.ServiceRegistry。

思考:reconcile 频率高时会不会抖?提示:变更比对 + key 唯一性。
L07

memory.ServiceRegistry

实现 Istio serviceregistry.Instance 接口的内存版本:所有注册中心来的 ServiceEntry 都注入这里。Pilot 通过 aggregate.Controller 从中读出 endpoints 推 EDS。

思考:memory 版崩溃后数据丢吗?答:丢,重启时 watcher 会重新拉一份。
L08

nacos/ v1

registry/nacos/watcher.go 适配 Nacos 1.x SDK。流程:登录 → SubscribeService → 收到 Instance 列表 → 转成 ServiceEntry → emit。Higress 维护 namespaceId × group 维度的订阅。

思考:Nacos 1.x 推送方式?答:UDP push + 长轮询 fallback。
L09

nacos/v2

registry/nacos/v2/ 适配 Nacos 2.x gRPC 协议。性能优于 1.x,但 SDK API 不同。Higress 通过 type=nacos2 区分。

思考:能不能直接升 2.x 客户端兼容 1.x server?答:不能,需要服务端也升级。
L10

nacos/address

registry/nacos/address/ 处理 Nacos 中存的"地址类型"配置(非服务实例,而是单纯的 ip:port 列表)。这是阿里内部常见用法,开源后保留。

思考:地址 vs 实例 主要差异?答:实例有健康检查 + 元数据。
L11

nacos/mcpserver

Higress 把 Nacos 配置中心当作 MCP Server 注册表:通过监听 Nacos 配置发现 MCP Server 实例。代码在 registry/nacos/mcpserver/

思考:为什么 MCP 实例不放普通 service 而要单独抽?答:携带 MCP 元数据(工具/资源列表)。
L12

zookeeper Dubbo

registry/zookeeper/watcher.go

  • 连接 ZK,监听 /dubbo/{interfaceName}/providers/ 路径。
  • 子节点是 URL(dubbo://ip:port/...?param=...),解析后转 ServiceEntry。
  • 支持 Triple/Dubbo 协议。
思考:Dubbo URL 里的方法签名是否要落到 ServiceEntry?答:通过 Http2Rpc CRD 单独承载。
L13

consul

registry/consul/watcher.go 调 Consul HTTP API,watch /v1/health/service/{name} 拿健康实例。一个 watcher 一个 service。dc / token / namespace 通过 spec.consulDataCenter 等字段传入。

思考:Consul long-poll vs Watch event 怎么选?
L14

eureka

registry/eureka/{client,watcher}。Eureka 没有 watch 接口,只能定时 fetch /eureka/apps/。Higress 用 30s 定时 + delta API 优化。

思考:Eureka pull 模型对延迟敏感的场景合适吗?答:不太合适,建议迁 Nacos。
L15

direct 静态

registry/direct/ 提供"静态服务"能力:直接在 McpBridge 里写一份 ip:port 列表,不依赖外部注册中心。适合简单场景或调试。

思考:直接写 ServiceEntry 跟 direct registry 区别?答:direct 走统一 reconcile 管控,运维一致。
L16

proxy 代理模式

registry/proxy/ 把请求转发到外部 HTTP 注册中心代理(如自研服务发现)。这是兜底机制:协议不在标准列表里也能接。

思考:proxy 模式对延迟有什么影响?
L17

健康检查与 IsHealthy

每个 Watcher 都要实现 IsHealthy()。控制面 /registry/status 端点会列出所有 watcher 状态,运维直接查。健康判定通常是"最后一次成功拉取距今 < 阈值"。

思考:某个注册中心挂了对其它流量有影响吗?答:互不影响。
L18

服务元数据合并

不同注册中心同名服务时,memory.ServiceRegistry 按 name + namespace + clusterId 去重。Endpoint 列表合并(按 ip:port 唯一)。这避免重复实例导致权重失衡。

思考:同 host 跨多个 watcher 的版本元数据如何保留?提示:labels merge。
L19

ServiceEntry 注入

最终所有 watcher 把变更 emit 给 reconcile,reconcile 调用 memory.ServiceRegistry 的 UpdateService API,触发 PushContext 重建,进而 EDS / CDS 重推。

思考:实例增删频繁时怎么避免 push 风暴?答:debounce + delta EDS。
L20

写一个新注册中心适配

步骤:

  1. registry/ 下新建包 myreg/
  2. 实现 Watcher 接口。
  3. 在 reconcile 工厂注册 type → 构造函数。
  4. 在 McpBridge CRD types 加 type 枚举。
  5. 写单测,参考 registry/nacos/watcher_test.go
本期收尾:你已经能给 Higress 接入任意新注册中心。下一期我们切到 Wasm 插件,看真正运行在 Envoy 内的代码。