Day 09 / 共 20 天 · 第 2 周 服务发现与配置
服务 → ServiceEntry
watcher 发现的服务怎么进入 Istio config store、最终下发给 Envoy?今天看共享内存缓存(生产者-消费者的中介)、延迟删除、convertServiceEntry,串起完整数据流。昨天(Day 08)看的是"Nacos watcher 怎么把实例转成 ServiceEntry",今天看这些 ServiceEntry 如何汇入一块共享缓存、再被消费下发,把第 2 周的服务发现主线彻底走通。
📍 你在 Higress 源码课的位置(第1周 全景与控制器 · 第2周 服务发现与配置)
D01 全景→
D02 启动→
D03 Ingress控制器→
D04 配置翻译→
D05 xDS-over-MCP→
D06 多注册中心→
D07 McpBridge→
D08 Nacos→
D09 ServiceEntry→
D10 配置+证书
💡 本质:共享缓存 = 快递驿站;生产者放件,消费者取件,互不打扰
八种 watcher 是八个快递员,各自把发现的服务(包裹)放进同一个驿站货架(共享缓存);istiod 是收件人,需要时来驿站一次取走全部。快递员不用认识收件人、收件人不用管包裹来自哪家——驿站把两边解耦了。好处:再加一种新快递(新注册中心),只要它会往货架放件,收件侧一行都不用改。而"延迟删除"就像驿站的规矩:包裹显示"已取消"时先别急着退回,放两天确认真取消了再清走,防止快递系统一抖动就误退件。
L01
共享内存缓存
// registry/memory/cache.go:39 Cache 接口
// 生产者(watcher):UpdateServiceWrapper / DeleteServiceWrapper
// 消费者(ingress config):GetAllServiceWrapper / GetAllServiceEntry / GetAllProxyWrapper
// store(:71):sew(host→ServiceWrapper)+ toBeUpdated/toBeDeleted + ip2services
读法:各注册中心的 watcher(Day 08)把发现的服务写进这个共享内存缓存;ingress config(Day 04)从缓存读出来转成 config.Config。缓存是"生产者和消费者之间的中介"。
L02
生产者-消费者
缓存解耦"发现"和"下发"
生产者(8 种注册中心的 watcher)各自异步地发现服务、写进缓存;消费者(istiod 来 List 时触发的 convertServiceEntry)从缓存读。中间用缓存解耦——watcher 不用知道谁消费、消费者不用知道服务从哪个注册中心来。好比一个公告板:各注册中心把"我这有哪些服务"贴上去,istiod 需要时来看板子取全部。这个解耦让"多源发现"和"统一下发"各自独立演化——加一个新注册中心类型,只要它往缓存写,消费侧零改动。
图注:缓存是生产者与消费者之间的驿站,两边靠它彻底解耦。
L03
延迟删除
// cache.go DeleteServiceWrapper(:219)只做标记
// PurgeStaleItems(:260,reconcile 完成时调)才真正物理删除
// deferredDeleteServices:延迟删除列表
为什么删除要"延迟"?
注册中心抖动(网络闪断)时,watcher 可能瞬间收到"服务全没了"的错误信号——如果立即删除,Envoy 会瞬间把该服务的路由全撤,导致流量中断。延迟删除:
Delete 只打个"待删"标记,等一轮 reconcile 完整跑完、确认"确实没了",PurgeStaleItems 才真删。如果期间服务又回来了,标记撤销。这避免了"抖动导致误删、流量中断"——是服务发现系统的稳定性保障(Istio 的 EDS 也有类似的健康状态过渡 Draining/Terminating)。📝 错误驱动:如果没有延迟删除会出什么事故
12:00:00 Nacos 与网关网络闪断 1 秒 → watcher 误收到"order-svc 实例全空"
没有延迟删除:立即删缓存 → 推送 → Envoy 撤掉 order-svc 全部 endpoint → 这 1 秒内所有 /order 请求 503 → 网络恢复后又重新发现、再推一次,白白抖动两回。
有延迟删除:先打"待删"标记,不动 Envoy → 1 秒后网络恢复、实例又出现 → 撤销标记 → 流量毫发无损。
没有延迟删除:立即删缓存 → 推送 → Envoy 撤掉 order-svc 全部 endpoint → 这 1 秒内所有 /order 请求 503 → 网络恢复后又重新发现、再推一次,白白抖动两回。
有延迟删除:先打"待删"标记,不动 Envoy → 1 秒后网络恢复、实例又出现 → 撤销标记 → 流量毫发无损。
👶 小白:那延迟删除会不会导致"服务真下线了还一直转过去"?
👨🏫 老师:不会长期如此。延迟只是"等一轮 reconcile 确认",不是永久保留。真下线的服务在下一轮对账里依然是空的,PurgeStaleItems 就会物理清除。延迟换来的是"抗抖动",代价只是极短时间内可能往一个刚下线的实例试转一两次——而 Envoy 自身的健康检查/重试会兜住这一两次。两害相权取其轻。
L04
convertServiceEntry
// pkg/ingress/config/ingress_config.go:862 convertServiceEntry —— 消费侧核心
RegistryReconciler.GetAllServiceWrapper() // 从缓存拿所有 ServiceWrapper
// 逐个包成 config.Config{
// GroupVersionKind: gvk.ServiceEntry,
// Namespace: "mcp",
// Labels: {registry-type, registry-name},
// Spec: se.ServiceEntry }
// 再补充来自 nacos3 MCP 的 SE
读法:istiod 来
List(gvk.ServiceEntry) 时触发(Day 04/05):从缓存取所有服务,包成标准 Istio config.Config(ServiceEntry)返回。这些 ServiceEntry 就是 istiod 认识的标准配置对象——和 Ingress 翻译出的 VirtualService 一样,通过 MCP 喂给 istiod。L05
mcp 命名空间
注册中心来的 ServiceEntry 固定放在 Namespace: "mcp",带 registry-type/registry-name 标签(常量 RegistryTypeLabelKey/RegistryNameLabelKey)。
读法:用固定命名空间
mcp + 标签标记"这个 ServiceEntry 来自哪个注册中心"——方便区分、管理、排查。不同来源的服务在配置里可追溯。这是"给动态发现的资源打来源标记"的实践。L06
direct watcher 样例
// registry/direct/watcher.go(static/dns,最简单的样例)
// Run(:103)直接同步生成 SE(无需轮询)
// generateServiceEntry(:142):
// static 解析 IP:port(支持 IPv6 [::1]:8080),Resolution=STATIC
// dns 校验域名,Resolution=DNS
// HTTPS 时生成带 SNI 的 DestinationRule(ClientTLSSettings_SIMPLE)
读法:
direct(static/dns)是最好懂的 watcher——static 就是配置里写死的 IP、dns 就是域名。没有轮询/订阅的复杂性,直接生成 ServiceEntry。看它能快速理解"watcher → ServiceEntry"的最小闭环。consul/zookeeper/eureka watcher 结构类似(用各自 SDK)。L07
全链路数据流
McpBridge CRD 变更 → mcpbridgeController(informer)→ AddOrUpdateMcpBridge(Day 07)
Reconciler.Reconcile → generateWatcherFromRegistryConfig → watcher.Run(Day 07-08)
watcher 订阅注册中心 → generateServiceEntry → memory.Cache.UpdateServiceWrapper(Day 08)
serviceUpdate() 回调 → serviceEntryHandlers EventUpdate(触发推送)
istiod 来 List → IngressConfig.convertServiceEntry(读 Cache)(今天)
生成 ServiceEntry → 经 MCP(15051) 下发 istiod → istiod 生成 xDS → Envoy
读法:这条链把 Day 06-09 全串起来:注册中心 → watcher → 缓存 → convertServiceEntry → MCP → istiod → Envoy。建议对照这张图在源码里各跳一遍。Envoy 最终拿到的 Cluster/Endpoint,源头就是 Nacos 里的服务实例。
L08
今日小结 + 动手
🧠 今天你应该能回答
- 共享内存缓存的生产者/消费者各是谁?
- 缓存为什么解耦"发现"和"下发"?
- 为什么删除要延迟?防什么问题?
- convertServiceEntry 怎么把缓存里的服务变成 Istio config?
- 全链路数据流的六步?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/higress
grep -n 'UpdateServiceWrapper\|PurgeStaleItems\|deferredDeleteServices' registry/memory/cache.go
grep -n 'func.*convertServiceEntry' pkg/ingress/config/ingress_config.go
sed -n '103,160p' registry/direct/watcher.go
明天预告 · Day 10(第2周收官):配置模型 + 证书管理——config 常量/环境变量,以及 Higress 的自动 HTTPS(certmagic + ACME Let's Encrypt + 用自身网关完成 HTTP-01 挑战的巧妙设计)。