Day 06 / 共 20 天 · 第 2 周 服务发现与配置

多注册中心概览

Higress 相比原生 Istio 的一大特色:不只从 K8s 发现服务,还能从 Nacos/ZK/Eureka/Consul/DNS 发现。今天看统一的 Watcher 抽象怎么屏蔽这些异构注册中心。第 1 周(Day 01-05)我们走通了"配置怎么翻译并喂给 istiod";第 2 周换一条线——网关要转发流量,得先知道"目标服务的实例都在哪台机器",这就要靠服务发现。今天是这条线的总览,为 Day 07-09 拆具体注册中心铺路。

📍 你在 Higress 源码课的位置(第1周 全景与控制器 · 第2周 服务发现与配置)
D01 全景 D02 启动 D03 Ingress控制器 D04 配置翻译 D05 xDS-over-MCP D06 多注册中心 D07 McpBridge D08 Nacos D09 ServiceEntry D10 配置+证书
🤔 痛点:网关知道"往 order-svc 转",可 order-svc 到底在哪几台机器? 路由规则只说"转给 order-svc",但真正转发时,门卫得知道这个服务现在有哪几个活着的实例、各自 IP 和端口。这份"服务→实例地址"的名单,存在注册中心里。麻烦的是——有的公司用 Nacos,有的用 Zookeeper、Consul、Eureka……每家格式、协议都不同。网关要一个个去适配吗?
💡 本质:注册中心 = 公司通讯录;Watcher = 统一格式的抄录员 把注册中心想成各部门各自维护的通讯录——Nacos 部门用 Excel、ZK 部门用纸质本、Consul 部门用另一套系统,格式五花八门。Watcher 接口就是一群"抄录员",不管原始通讯录长什么样,都按同一张标准表格抄录(订阅→转成 ServiceEntry→回调通知)。上层拿到的永远是统一格式,根本不用关心原始通讯录是哪种——这就是"接口屏蔽异构",你在 Envoy/Istio 课已见过好几次的老朋友。
L01

为什么要多注册中心

"微服务迁移"的痛点 很多公司的微服务用 Nacos/Zookeeper/Eureka 等注册中心(Spring Cloud/Dubbo 生态),服务实例注册在那里,不在 K8s Service 里。原生 Istio 只认 K8s Service——你的 Nacos 服务它看不见。Higress 的杀手锏:能从 Nacos/ZK/Consul/Eureka/DNS/静态配置等多种来源发现服务,统一转成 Istio ServiceEntry(Day 09)。于是你能用 Higress 网关代理这些"注册在 Nacos 的老服务",平滑迁移到云原生,不用先把服务全搬进 K8s。这是 Higress 在国内微服务场景大受欢迎的关键能力。
L02

八种类型

// registry/watcher.go:27 支持的注册中心类型
Zookeeper / Eureka / Consul
Nacos   (老 SDK v1)
Nacos2  (新 SDK v2)
Nacos3  (v2 + MCP Server 发现,AI 网关特性)
Static  (配置里写死)
DNS     (域名解析)
读法:八种注册中心类型,各有一个 Watcher 实现(在 registry/<类型>/)。Nacos 有三代——v1(基础)、v2(鉴权+地址服务器)、nacos3(MCP Server 发现,Day 08)。代码在 registry/ 各子目录。
L03

Watcher 接口

// registry/watcher.go:54 统一 Watcher 接口(所有注册中心都实现它)
type Watcher interface {
  Run(); Stop(); IsHealthy() bool; IsReady() bool
  GetRegistryType() string
  AppendServiceUpdateHandler(f func())   // 挂"服务变化回调"
  ReadyHandler(f func(bool))             // 挂"就绪回调"
}
统一接口屏蔽异构(老朋友了) Nacos、ZK、Consul 的 API 天差地别(有的推、有的拉、协议不同)。Watcher 接口把它们抽象成同一套:Run(开始订阅)、Stop(停)、IsReady(就绪没)、AppendServiceUpdateHandler(服务变了叫我)。每个注册中心只需实现这个接口——"订阅本注册中心 → 生成 ServiceEntry → 写共享缓存 → 回调通知"。上层(Reconciler,Day 07)只跟 Watcher 打交道,不管底层是 Nacos 还是 ZK。这是"接口/实现分离"的又一次应用——和 Envoy 的 LoadBalancer、Istio 的 ServiceDiscovery、OpenClaw 的 ChannelPlugin 同一个思想。
八种异构通讯录 → 同一个 Watcher 接口 → 统一 ServiceEntry NacosZookeeperConsulEurekaDNS/Static Watcher 接口 Run/Ready/回调 统一 ServiceEntry 写入共享缓存 (Day9) 上层 Reconciler 只跟这个接口打交道,不管底层是谁
图注:左边再乱,经过 Watcher 抄录,右边都是统一的 Istio ServiceEntry——上层从此只认一种格式。
L04

BaseWatcher 组合

BaseWatcherwatcher.go:64)提供接口默认实现,各具体 watcher 匿名嵌入 provider.BaseWatcher 复用。UpdateService(服务变更通知上层)和 Ready(就绪状态)两个回调是与 Reconciler 联动的关键。

读法:Go 的"组合复用"——具体 watcher(如 Nacos watcher)嵌入 BaseWatcher 就自动获得回调管理等通用能力,只需实现自己独特的"怎么订阅本注册中心"。比继承更灵活(Go 没有类继承,用组合)。
L05

两个回调

UpdateService 和 Ready 是"信号线" Watcher 和上层 Reconciler 靠两个回调联动:UpdateService——注册中心的服务实例变了(新增/下线),watcher 调它通知上层"配置该刷新了",触发向 istiod 推送新配置。②Ready——watcher 首次同步完注册中心的全部服务,调它报告"我准备好了"。Reconciler 等所有 watcher 都 Ready 才算整体就绪(Day 07)。这是"生产者(watcher 发现服务)→ 通知(回调)→ 消费者(Reconciler/istiod 刷新)"的事件驱动联动。
📝 举个例子:Nacos 上线一个新实例会发生什么 运维在 Nacos 给 order-svc 加了一个实例 10.0.0.9:8080
Nacos watcher 订阅到变化 → 重算 order-svc 的 ServiceEntry(多了个 endpoint)→ 写共享缓存 →
UpdateService 回调 → 触发向 istiod 推送 → 最终 Envoy 的负载均衡池里就多了 10.0.0.9,开始分流量给它。
整条链你不用碰任何 K8s Service——服务始终注册在 Nacos。

👶 小白:为什么要分 UpdateService 和 Ready 两个回调,不能合一个吗?

👨‍🏫 老师:它俩管的事不同。Ready 是"我第一次把整本通讯录抄完了"——只在启动同步完成时报一次,Reconciler 靠它判断"能不能对外服务了"(没抄完就服务,会漏掉一半实例)。UpdateService 是"通讯录后来又改了"——运行期每次增删实例都会调,触发增量刷新。一个管"就绪门禁",一个管"持续同步",合并了反而分不清是首次还是后续变更。

L06

探活与 vport

ProbeWatcherStatus(host, port)watcher.go:91)用 net.DialTimeout 三秒 TCP 探活,作为 IsHealthy() 依据。GetServiceVportwatcher.go:101)虚拟端口机制——把注册中心里各实例真实端口统一映射成一个对外服务端口。

vport(虚拟端口)解决什么? 注册中心里同一个服务的不同实例可能监听不同端口(比如实例 A 在 8080、实例 B 在 8081)。但对网关来说,"服务"应该有一个统一的对外端口。vport(virtual port,虚拟端口)就是给这个服务分配一个统一的逻辑端口,屏蔽各实例真实端口的差异。探活则是判断注册中心本身是否可达(连不上就标记不健康)。这些是把"异构注册中心的服务"规整成"Istio 认的统一服务模型"的细节处理。
L07

认证选项

registry/auth_option.go:17 AuthOption + Secret key 常量(nacosUsername/Password、consulToken、etcdUsername/Password)。认证信息不写在 CRD 明文里,而是引用一个 K8s Secret

读法:连 Nacos/Consul 可能需要账号密码/token。这些敏感信息不明文写在 McpBridge CRD 里,而是 CRD 里引用一个 Secret 名字,运行时从 Secret 读(Day 07 的 getAuthOption)。这是安全实践——敏感凭据和配置分离(和 Istio 的 SDS、OpenClaw 的密钥管控同理)。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 为什么 Higress 要支持多注册中心?解决什么迁移痛点?
  • 支持哪八种类型?Nacos 三代的区别?
  • Watcher 接口的核心方法?它怎么屏蔽异构?
  • UpdateService/Ready 两个回调的作用?
  • vport 解决什么?认证信息怎么存?

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/higress
sed -n '27,99p' registry/watcher.go
ls registry/
cat registry/auth_option.go | head -35
明天预告 · Day 07McpBridge + reconcile——用户用 McpBridge CRD 配置注册源,Reconciler 怎么 diff 出"该创建/停止哪些 watcher"(典型 K8s Operator 控制循环)。
← Day 05 xDS-over-MCP Day 07 · McpBridge + reconcile →