Day 16 / 共 20 天 · 第 4 周 高级子系统与运维

服务发现 discovery

第 4 周进入高级子系统。上游的 nodes 可以写死,也可以"动态发现"——从 Nacos/Consul/K8s 等注册中心实时获取。今天看 apisix/discovery/ 怎么把网关接进各种服务注册中心。

📍 你在整条链的位置(第 4 周 高级子系统与运维)
插件系统 W3 ✅ 服务发现 discovery(动态上游) Admin/control D17 stream L4 D18
L01

静态 vs 动态上游

🤔 痛点:上游地址天天变,手写死 nodes 累死人 微服务今天 3 个实例、明天扩到 5 个,Pod 重启后 IP 还全变了。每次都去改 upstream 的 nodes 列表?改慢一秒,流量就打到已经下线的机器上,报 502。
💡 本质:把"手写电话本"换成"实时通讯录" 静态 nodes 就像手抄的电话本——人一搬家就作废。服务注册中心(Nacos/Consul/K8s)是公司前台的实时登记簿:每个实例上线就来"报到"、下线就划掉。网关(快递员)不再背死地址,而是每次送货前问一句前台"user-service 现在住哪几号"。upstream 只要写 discovery_type + service_name,nodes 由 discovery 实时填。
微服务的地址是变的 Day 08 说 upstream 的 nodes 可以是静态列表(1.2.3.4:80)。但微服务实例经常动态扩缩容、上下线——今天 3 个实例、明天 5 个,地址还是随机的。手写死 nodes 根本跟不上。解法:服务注册中心(Nacos/Consul/K8s)——每个服务实例启动时向注册中心"报到",网关从注册中心实时查"服务 X 现在有哪些实例"。upstream 配 discovery_type: nacos + service_name: X,nodes 就由 discovery 动态填充。这是云原生环境的标配。
L02

discovery 目录

apisix/discovery/
  init.lua        统一入口(按配置加载各 discovery 实现)
  nacos/          阿里 Nacos
  consul/ consul_kv/   HashiCorp Consul
  dns/            DNS SRV 记录
  eureka/         Netflix Eureka
  kubernetes/     K8s Endpoints/Service
  tars/           腾讯 Tars
读法:每种注册中心一个子目录,实现统一接口。Day 03 的 http_init_workerdiscovery.init_worker() 就是初始化这些。和上一站 Higress 的多注册中心(Day 02 的 Nacos/ZK/Consul watcher)、Console 的 ServiceSource 8 种类型完全对应——网关都要对接这些主流注册中心。
L03

统一接口

每个 discovery 实现提供统一方法(discovery/init.lua 管理):init_worker()(启动同步)、nodes(service_name)(返回某服务当前实例列表)、dump_data()(调试)。

"接口统一、实现可换"(又一次) 不管底层是 Nacos 还是 Consul,upstream 层只调 discovery.nodes(service_name) 拿实例列表——上游代码不关心是哪种注册中心。这是本课反复出现的设计(路由可换、配置中心可换、负载均衡可换、限流策略可换……)。APISIX 到处用"策略/适配器模式"保持核心稳定、边缘可扩展。加一种新注册中心 = 加个目录实现统一接口。
L04

discovery.init_worker

discovery/init.luadiscovery.init_worker(Day 03 调):按 config.yaml 配的 discovery: 加载对应实现,每个实现启动自己的同步逻辑(连注册中心、订阅/轮询服务变化)。

读法:比如配了 nacos,就启动 nacos 的后台协程,定时/订阅从 Nacos 拉服务实例,缓存在内存。和 etcd 配置同步(Day 07)类似——都是"后台同步进内存,请求时读内存"。只不过这里同步的是"服务实例地址",etcd 同步的是"配置"。
L05

nodes() 拉实例

upstream 需要实例列表时调 discovery_impl.nodes(service_name),返回 {{host=..., port=..., weight=...}, ...}——就是 Day 10 负载均衡需要的 nodes 格式。

实例上/下线扩缩容 注册中心Nacos/K8s discovery 同步后台协程→内存 nodes()返回实例表 负载均衡
实例变化 → 注册中心更新 → discovery 同步进内存 → nodes() 给出新表 → 负载均衡自动用新实例,全自动跟随。
动态 nodes 无缝接入负载均衡 discovery 返回的 nodes 和静态配的 nodes 格式一样,所以负载均衡(Day 10)、健康检查完全不用改——它拿到 nodes 就用,不管是静态写的还是 discovery 动态给的。这就是统一接口的威力:动态发现"伪装成"普通 nodes,下游无感。实例上下线 → 注册中心更新 → discovery 同步 → 下次 nodes() 返回新列表 → 负载均衡自动用新实例。全自动跟随。
L06

和 upstream 结合

upstream 配置里写 discovery_type + service_name(不写 nodes):

-- upstream 配置
{
    discovery_type = "nacos",
    service_name = "user-service",
    type = "roundrobin",       -- 负载均衡算法照常
}
-- 运行时:apisix_upstream 发现是动态的 → 调 discovery.nodes("user-service") 填充 nodes
📝 举个例子 upstream 写 discovery_type=nacos, service_name=user-service(不写 nodes)。
运行时 discovery.nodes("user-service") 返回 [{host:10.0.0.3,port:8080,weight:1}, {host:10.0.0.7,port:8080,weight:1}] → 和你手写 nodes 一模一样的格式 → 负载均衡照常在这两台间轮询。扩容出第三台,下一次 nodes() 就自动多一条。

👶 小白:那服务发现和 Day 07 的 etcd 配置同步,是一回事吗?

👨‍🏫 老师:机制像、内容不同。两者都是"后台悄悄同步进内存、请求时读内存"。但 etcd 同步的是配置(路由/插件长啥样),discovery 同步的是服务实例地址(user-service 现在有哪些 IP)。一个管"规则",一个管"后端在哪"。

读法:apisix/upstream.lua 里判断 upstream 是"静态 nodes"还是"discovery 动态"——动态的就实时调 discovery 拿 nodes。Day 06 路由 filter 里的 filter_upstream 会预处理上游的 discovery 配置。对用户,只是配置里把 nodes 换成 discovery_type + service_name,其余(负载均衡/健康检查/重试)全一样。
L07

各注册中心特点

  • nacos:阿里生态最常用,支持 namespace/group。
  • consul / consul_kv:HashiCorp,服务发现 + KV。
  • kubernetes:直接读 K8s 的 Endpoints(Pod IP),云原生首选。
  • dns:用 DNS SRV 记录发现(轻量、无需额外注册中心)。
  • eureka / tars:Netflix / 腾讯生态。
对接哪个看你的技术栈 用 Spring Cloud Alibaba 就用 nacos、跑在 K8s 上就用 kubernetes、HashiCorp 栈就用 consul。APISIX 都支持,让网关能无缝接入你现有的服务注册体系。这是网关"融入现有架构"的关键能力——不强迫你换注册中心。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 为什么微服务需要动态服务发现而非静态 nodes?
  • discovery 的统一接口有哪些方法?为什么这么设计?
  • 动态 nodes 怎么无缝接入负载均衡(Day 10)?
  • upstream 怎么从静态改成 discovery?

✋ 动手

cd /Users/bitmart/work/codes/github/apisix
ls apisix/discovery/
grep -n "function\|nodes\|init_worker" apisix/discovery/init.lua | head
grep -n "nodes\|register\|new_client" apisix/discovery/nacos.lua 2>/dev/null | head
明天预告 · Day 17Admin API 与 control API——你怎么"往 etcd 写配置"?Admin API(apisix/admin/)是配置管理入口,control API 是运维接口(健康状态、reload 等)。
← Day 15 Day 17 · Admin API →