Skip to content
Charles Shao
Go back

服务注册与发现深挖

views

上一篇讲 RPC 框架 时,客户端负载均衡有一个隐含前提:手里的实例列表是对的。列表从哪来、多久更新、实例死了多久被摘掉——这就是服务注册与发现。做不好,再炫的负载均衡算法也只是在一堆僵尸 IP 里挑来挑去。

广告微服务里,发现层决定了「一次发布抖动会不会变成全站超时」:竞价、预算、定向几十个服务,每次滚动发布都是对注册表与客户端缓存的压力测试。本篇把发现模式、主流注册中心、健康检查与优雅发布一次讲透,并和 ZooKeeper / etcd 内部机制负载均衡 串起来。

本文是微服务与 RPC 系列的第 2 篇。 系列顺序:① RPC 框架(开篇);② 服务注册与发现深挖(本篇);③ 服务网格 Service Mesh 深挖;④ API 网关与分布式追踪

一句话定位:服务发现解决「此刻谁在提供这个服务、是否还活着」——模式选客户端或服务端,一致性选 AP 或 CP,真正决定线上体验的往往是健康检查延迟与优雅下线顺序。

TL;DR

Table of contents

Open Table of contents

1. 两种发现形态

客户端发现与服务端发现对照图。左栏客户端发现:Consumer 持有实例列表,经 watch/pull 与 Registry(ZK/Nacos/etcd)同步,再对 Provider A/B/C 做进程内 LB 直连;右栏服务端发现:Consumer 只认 VIP,固定入口打到 LB/Gateway(nginx·Envoy·ALB),由负载均衡在健康探测后转发到后端 A/B/C。底部说明客户端发现更灵活但多语言都要接 SDK,服务端发现对调用方极简;Service Mesh 把客户端发现下沉到 Sidecar。

两端形态解决同一问题,把复杂放在客户端还是中间层,决定了运维与 SDK 成本。

1.1 客户端发现

流程:Provider 启动向注册中心 Register → Consumer Subscribe/Watch 拿到地址列表 → 本地 LoadBalance 选一个直连。

增量推送(Watch)比定时全量拉取更实时,但要处理「推送丢失 / 乱序 / 空列表」;定时 pull 可作为兜底对账。实战上很多事故不是「完全没有发现」,而是半新半旧:一部分客户端已更新、另一部分还握着旧列表,发布窗口里故障率呈波浪——这时要看「缓存版本分位数」,而不是只看注册中心上的实例数。

1.2 服务端发现

流程:Provider 挂到 LB 后端池(或由控制面同步 Endpoints)→ Consumer 只访问稳定名字(DNS / VIP)。

K8s 里 Service + Endpoints + kube-proxy/IPVS/eBPF 本质是平台托管的服务端发现;Ingress/Gateway 再往北伸一层。

1.3 混合与演进

现实里常见混合:南北向走网关/LB(服务端发现),东西向走客户端发现或 MeshService Mesh(下一篇)可以看成:把「客户端发现 + 精细路由」从业务 SDK 搬进 Sidecar,兼得多语言一致与细粒度治理。

2. 注册中心四剑客

注册中心四剑客对照卡片。从左到右:ZooKeeper(CP/ZAB、Watch/临时节点、强一致写延迟高、老牌 Dubbo/协调);etcd(CP/Raft、Lease+Watch、K8s 元数据大脑);Consul(偏 CP+健康、原生 DNS/HTTP、多 DC 联邦);Nacos(可切 AP/CP、配置+发现一体、推空保护与权重、国内微服务常用)。底部提示选 CP 还是 AP 的准则及健康检查决定摘除延迟。

没有万能注册中心,只有「一致性 / 可用性 / 运维成本」的三角。

2.1 ZooKeeper

ZAB 保序与多数派;临时节点 + Session 实现「进程死了节点自动删」——早期 Dubbo 的默认搭档。适合偏协调/配置的 CP 场景;超大规模实例频繁变更时,写与 Watch 风暴要小心(详见 ZK/etcd 深挖)。

坑点:Session 超时与 JVM GC 过长叠加时,可能被误判过期 → 闪断摘除 → 抖动。Session 超时要大于可接受 GC pause,并监控 session 重建。

2.2 etcd

Raft + Lease + Watch,Kubernetes 的元数据底座。服务发现可以自建在 etcd 上,也可以间接享受「K8s Endpoints ≈ 平台级发现」。Lease KeepAlive 续不上 → key 过期 → 自然摘除,语义清晰。

注意:etcd 不是为「百万级实例每秒狂抖」设计的海量发现库;海量变化要分片、或把发现数据面交给专用组件,etcd 保关键元数据。

2.3 Consul

服务注册、健康检查、DNS/HTTP 查询、多数据中心产品化。适合「我想用 DNS 做发现、又要认真健康检查」的团队;与 Nomad/Vault 同生态。健康检查可以是 TTL、TCP、HTTP、gRPC,失败阈值可配——把「摘除 SLA」显式化。

2.4 Nacos

国内微服务常见选择:配置中心 + 服务发现一体;支持按场景选 AP(高可用、最终一致)或 CP。推空保护、权重、元数据路由对广告这种「多机房/多单元」很有用——但推空保护也会掩盖真实空列表故障,必须配监控。

维度ZKetcdConsulNacos
一致性CPCP偏 CPAP/CP 可选
摘除手段临时节点LeaseHealth Check心跳 + 探活
查询方式Watch/APIWatch/APIDNS/HTTPHTTP/gRPC
典型搭档老 DubboK8sHashi 栈Spring Cloud Alibaba

2.5 元数据:发现的第二公民

注册不只是 IP:port。务必要带上:

没有元数据,Router 就无法做「只把压测流量打到压测组」「同单元优先」。发现系统是带 schema 的小数据库,不是 DNS 的简陋替代。

2.6 反模式速查

3. 健康检查与实例生命周期

实例生命周期流水线:启动注册(写 IP:port/meta)→ 心跳/TTL(Lease KeepAlive)→ 主动探测(TCP·HTTP·gRPC)→ 不健康摘除(从候选集移除)→ 优雅下线(先摘再毁进程)。注释强调正确发布顺序与推空保护的双刃剑。

列表的正确性 = 注册及时 + 续约可靠 + 探活够快 + 下线顺序对。

3.1 三种探活

  1. 客户端心跳 / TTL:实例定期续约,过期即删。实现简单;进程假死(卡死但仍能发心跳)可能漏检。
  2. 主动探测:注册中心或独立 checker 打 TCP/HTTP/gRPC Health。能发现「进程在、服务挂」。
  3. 会话/临时节点:ZK ephemeral、etcd lease——连接或租约断即删,和进程生命周期绑定紧。

摘除 SLA 要算清楚:探测间隔 × 失败次数 + 传播延迟。若合计 30–60s,滚动发布就必须用「先摘后杀」,不能指望探活兜底。

gRPC 的标准 Health Checking 协议值得统一:探活命中真实服务状态,而不是只探操作系统端口开没开。

组合策略往往最好:会话/Lease 负责「进程没了」的快速剔除,主动探测负责「进程在但业务挂了」。单一手段要么漏检,要么误杀。误杀在广告场景的代价是瞬时容量塌方——探活参数要配合容量冗余一起调,不能只追「摘得越快越好」。

探活失败的告警也要分级:单个实例不健康是常态(发布会触发),同服务不健康比例突增才是火灾。把「实例不健康数」和「服务可用实例比例」拆开告,能少很多无效夜班。

3.2 优雅发布标准顺序

  1. 实例标为 不可用(或从注册中心显式 deregister / 权重打 0);
  2. 等调用方缓存/Watch 生效,并排空 in-flight(可设宽限期);
  3. 确认 QPS 归零后再 停进程
  4. K8s 场景配合 preStop + terminationGracePeriodSeconds + readiness 探针。

反模式:直接 kill Pod,注册还在 → 调用方连 RST → 超时重试风暴(接上篇 FailOver 雪崩)。

上线也有对称问题:新实例刚启动就接 100% 权重 → 缓存未热、连接未建,瞬间被打挂。用权重预热(从 0 爬升)或 readiness 挡住未就绪流量。

3.3 缓存与推空保护

客户端必有本地缓存(抗注册中心抖动)。推空保护:短时间收到空列表则继续用旧列表——防「注册中心故障 → 全集群以为没后端」。副作用:真上线失败/真被摘光时,会多撑一会儿僵尸流量,要用「空列表持续 N 秒告警」对冲。

缓存还有时效:过长 → 脏;过短 → 打爆注册中心。配合 Watch 的增量更新 + 周期性全量对账最稳。

3.4 订阅风暴与容量

大规模 Consumer 对同一服务 Watch:注册中心故障恢复瞬间,可能出现风暴式重连与全量拉取。手段包括:分片、连接限流、退避重试、服务端推送批次化。容量规划要把「恢复场景」算进去,不能只算稳态 QPS。

4. AP vs CP:发现场景怎么选

实务建议:

  1. 发现数据可用最终一致,但「变更传播延迟」必须可观测、可告警;
  2. 真正强一致的开关与路由规则另放 CP 存储,别和海量心跳挤在一起;
  3. 多活时按 机房与区域部署 规划:跨 DC 的 Watch/心跳参数要整体变松或分区自治,避免跨洋强一致幻想。

CAP 提醒:分区来临时,你要么暂时写不进注册(CP),要么暂时读到旧列表(AP)。两边都想要完美,最后两边都不完美——选你更能承受的那一种失败模式。

4.1 多机房发现拓扑

常见三种:

  1. 全局一套注册中心:简单,但跨城延迟与分区风险高;
  2. 每机房自治 + 少量全局服务:同城调用吃本地列表,跨城走明确的全局入口;
  3. 联邦(Consul WAN / 自研同步):机房间同步服务目录,查询仍本地优先。

广告投放多单元时,错误拓扑的典型症状是:明明同城有实例,流量却被哈希到异地——元数据里缺 zone,或 Router 没读元数据。发现系统要和跨区域调度同一张图,不能各画各的。

4.2 安全:注册表也是攻击面

谁能注册 budget-service?若注册中心无鉴权,恶意或误配置实例可以冒名接流量。最低要求:

Mesh 的 SPIFFE 身份(下一篇)把「谁在说话」再往前推一步;但注册中心本身的写权限仍要守住。

5. 小结:发现层的验收问题

交付验收时,用这五个问题自检就够:

  1. 谁有权注册这个服务名? 无鉴权就等于任何人都能劫持流量。
  2. 实例从健康变不健康,最长多久被所有调用方摘掉? 写不出秒数,说明没有 SLA。
  3. 发布会不会先 deregister? 如果靠探活兜底,窗口期必有僵尸命中。
  4. 客户端收到空列表怎么办? 推空保护有没有告警对冲?
  5. 元数据够不够路由? 机房、单元、版本、权重缺一样,灰度与就近就会退化成随机。

能清楚回答这五问,注册中心换哪家反而其次;答不上来,换产品也治不好发布抖动。

参考


views
Share this post on:

Previous Post
服务网格 Service Mesh 深挖
Next Post
RPC 框架(开篇)· 原理与 gRPC/Thrift/Dubbo