Skip to content

服务发现-客户端与服务端模式 ​

看在前面 ​

本文译自 Nginx 官方博客 Chris Richardson 的微服务系列(四)。讲微服务架构中服务发现的可行方案(客户端发现、服务端发现)以及实践案例。

为什么要使用服务发现? ​

  • 传统应用运行在物理硬件上,服务实例的网络位置相对固定。代码可以从偶尔更新的配置文件中读取网络位置;

  • 在云端的现代化微服务应用中,服务实例的网络位置是动态分配的。由于扩展、失败和升级,实例会频繁动态改变。因此客户端需要更复杂的服务发现机制;

  • 服务发现有两大模式:客户端发现模式和服务端发现模式。

客户端发现模式 ​

客户端自行决定服务实例的网络位置,并对请求实现负载均衡:

                 ┌────────────────────┐
   服务实例注册/  │    服务注册表        │
   心跳刷新/注销  │ (可用实例数据库)     │
   ┌──────┐     └────────▲────────────┘
   │实例 A │──注册─────────┘
   └──────┘
   ┌──────┐        2.查询可用实例
   │实例 B │───────────────────┐
   └──────┘                   │
                      1.查询注册表    3.负载均衡算法选择实例,发请求
   客户端 ───────────────────────────────────────> 目标服务实例
  • 服务实例启动时,网络位置被记录到服务注册表,终止时删除;注册信息通常用心跳机制定期刷新;

  • 客户端查询服务注册表(一个可用服务实例的数据库),使用负载均衡算法选择一个实例。然后发出请求。

典型范例:Netflix OSS。Eureka 作为服务注册表,提供 REST API 管理服务实例的注册与查询;Ribbon 是 IPC 客户端,与 Eureka 配合实现负载均衡。

优点:相对直接,除服务注册外无需其它变动。客户端知晓可用实例,可以实现智能的、客户端侧的负载均衡(如一致性哈希)。

缺点:客户端与服务注册机制绑定,需要针对每种编程语言和框架分别实现客户端发现逻辑。

服务端发现模式 ​

客户端向负载均衡器发出请求,负载均衡器查询服务注册表,将每个请求转发到可用的服务实例:

   ┌──────┐  注册/心跳   ┌────────────┐
   │实例 A │ ──────────> │  服务注册表  │
   └──────┘             └─────▲──────┘
   ┌──────┐                   │ 2.查询
   │实例 B │          1.请求   │           3.转发请求
   └──────┘          ┌────────┴──────┐
   客户端 ─────────> │  负载均衡器    │ ──────────> 目标实例
                     └───────────────┘

典型例子:

  • AWS ELB:客户端通过 DNS 向 ELB 发送 HTTP/TCP 请求。ELB 在已注册的 EC2 实例或 ECS 容器之间均衡流量。这里没有独立的服务注册表,实例直接注册在 ELB 上;

  • NGINX/NGINX Plus:可用作服务端发现的负载均衡器。Consul Template 定期从注册表生成 nginx.conf,文件变化后触发 NGINX 重载;NGINX Plus 还支持通过 HTTP API 或 DNS 动态配置 upstream;

  • Kubernetes、Marathon:在每台集群主机上运行一个代理(proxy),充当服务端发现的负载均衡器。客户端通过主机 IP 和分配的端口向代理发起请求。代理将请求透明地转发到该服务一个可用的实例。

优点:客户端无需关注服务发现的细节,只需向负载均衡器发送请求。减少了每种语言/框架都要实现发现逻辑的负担。某些部署环境免费提供该功能。

缺点:除非负载均衡器由部署环境提供,否则它是一个需要配置和管理的高可用系统组件。

服务注册表 ​

服务注册表是服务发现的核心部分:一个包含服务实例网络地址的数据库。注册表必须高可用且随时最新。客户端可以缓存从注册表中获取的网络地址,但信息最终会过时。因此注册表由一组服务端组成,使用复制协议来保持一致性。

Netflix Eureka 高可用实践:

  • 提供完整的 REST API:服务实例用 POST 请求注册网络地址,每 30 秒通过 PUT 刷新注册信息。通过 HTTP DELETE 或实例注册超时来移除。客户端通过 HTTP GET 检索可用实例;

  • 每个 AWS 可用域(availability zone)运行一个或多个 Eureka 服务,各运行在有弹性 IP 的 EC2 实例上。DNS TXT 记录保存 Eureka 集群配置(可用域列表及各域服务器地址列表)。Eureka 服务启动时查询 DNS 获取集群配置、定位同伴、并尝试占用未使用的弹性 IP;

  • Eureka 客户端查询 DNS 来发现 Eureka 服务的网络地址。优先使用同一个可用域的服务,当前域服务不可用时回退到其它域。

其它常见注册表组件:

  • etcd:高可用、分布式、一致性的键值存储,用于共享配置和服务发现,Kubernetes 和 Cloud Foundry 使用它;

  • consul:另一个服务发现和配置工具,通过健康检查机制判断服务的可用性,支持 DNS 和 HTTP 接口;

  • Apache ZooKeeper:被广泛使用的高性能协调服务,最初是 Hadoop 的子项目,现在是 Apache 顶级项目。

另外,Kubernetes、Marathon、AWS 这些环境没有明确的外置服务注册表,注册功能内置于基础设施中。

服务注册的方式 ​

自注册模式 ​

服务实例自己负责在服务注册表中注册和注销,必要时发送心跳保证注册信息不过期。

  • 案例:Netflix Eureka 客户端处理实例的注册与注销;Spring Cloud 通过 @EnableEurekaClient 注解简化自动注册;

  • 优点:简单直接,不需要其它系统组件;

  • 缺点:服务实例与注册表耦合,必须在使用的每种编程语言和框架内实现注册代码。

第三方注册模式 ​

服务实例不自己注册,由称为"服务注册器(registrar)"的另一个系统模块处理。注册器通过查询部署环境或订阅事件来跟踪运行实例的变更。发现新实例即注册,实例终止则注销。

  • 案例:

    • Registrator:开源项目,自动注册/注销被 Docker 容器化的服务实例,支持 etcd、Consul 等多种注册表;

    • NetflixOSS Prana:面向非 JVM 服务,与服务实例并行部署运行,代表服务通过 Eureka 完成注册注销;

    • 部署环境内置:AWS 中由 Autoscaling Group 创建的 EC2 实例自动向 ELB 注册;Kubernetes 中服务自动注册并可被发现;

  • 优点:服务与注册表解耦,无需逐语言实现注册逻辑,由专有组件对所有服务集中管理;

  • 缺点:除非内置于部署环境,否则它是一个需要配置和管理的高可用额外组件。

总结 ​

  • 微服务中实例网络地址动态变化,客户端必须使用服务发现机制(服务注册表 + 查询机制);

  • 两种发现模式。客户端发现:客户端查注册表、选实例、发请求。服务端发现:路由器/负载均衡器查注册表并转发请求;

  • 两种注册方式:自注册模式与第三方注册模式;

  • 有些部署环境(Kubernetes、Marathon)内置服务发现,包括实例的注册注销和集群主机上的代理路由。其它环境则需自建 Eureka、etcd、Consul、ZooKeeper 等设施;

  • NGINX 类负载均衡器可以用作服务端发现的均衡器。通过 Consul Template 推送路由信息并触发配置更新。NGINX Plus 还支持 DNS 获取实例信息和远程配置 API。

内容来源:原文镜像(原 DaoCloud 链接已失效)· 2026-10 整理回填。