目录导航

1 问题起源

在单体架构向微服务架构演进的过程中,服务实例的动态性带来了一个根本性的问题

核心矛盾:硬编码 IP 在动态环境中失效

在单体时代,服务的地址是固定的,可以直接写在配置文件里。但微服务架构中,每个服务可能有多个实例,并且会随自动扩缩容、故障恢复、蓝绿部署等场景动态变化。IP 地址不再是静态的,"调用方怎么找到可用的服务实例"成为一个必须解决的基础设施问题。

单体架构

服务地址固定,直接硬编码。部署频率低,变更少。

// 直接写死地址 url = "http://192.168.1.10:8080/api"

微服务架构

服务实例动态变化,需要自动发现机制。

// 需要动态发现 url = discovery.getService("user-service")

服务动态性的典型场景

  • 自动扩缩容:流量高峰时自动增加实例,低谷时自动缩减
  • 故障自愈:实例挂掉后自动重启,新实例 IP 可能改变
  • 灰度发布:新版本逐步上线,新旧实例共存
  • 容器化部署:Docker/K8s 环境中,容器 IP 在每次重建后都会变化
2 核心概念

服务注册与发现是一种让服务实例能够自动"登记"和被"查找"的机制

📋
服务注册
服务启动时将自己的地址信息登记到注册中心
🔍
服务发现
调用方从注册中心查询可用服务实例列表
❤️
健康检查
定期检测实例是否存活,自动剔除异常节点
🔄
负载均衡
从多个可用实例中选择一个进行调用
注册中心 Registry Center 服务提供者 A 10.0.1.5:8080 服务提供者 B 10.0.1.6:8080 服务提供者 C 10.0.1.7:8080 服务消费者 Service Consumer 注册 发现 直接调用

三个核心角色

服务提供者启动时将自己的网络地址(IP + Port)注册到注册中心,并定期发送心跳维持注册状态
服务消费者需要调用其他服务时,先从注册中心查询可用实例列表,再选择一个发起调用
注册中心存储所有服务实例的地址信息,提供查询接口,执行健康检查剔除异常实例
3 工作原理

点击每个步骤查看详细工作过程

1. 服务启动与注册

服务实例启动后,读取自身配置(服务名、IP、端口、元数据),通过 HTTP/DNS/SDK 向注册中心发起注册请求。注册中心将实例信息写入存储(内存/数据库/一致性协议),并开始计时心跳超时。

// 注册请求示例 { "serviceName": "order-service", "instanceId": "order-10-0-1-5-8080", "address": "10.0.1.5:8080", "metadata": { "version": "2.1.0", "zone": "cn-east" }, "status": "UP" }

2. 心跳维持(续约)

注册成功后,服务实例需要定期(如每 30 秒)向注册中心发送心跳请求,表明自己仍然存活。如果注册中心在约定时间内(如 90 秒)未收到心跳,则将该实例标记为不健康并从可用列表中剔除。

# 心跳请求 PUT /eureka/apps/order-service/order-10-0-1-5-8080
# 超时剔除 if (now - lastHeartbeat > 90s): removeInstance(instance)

3. 服务查询(发现)

服务消费者在需要调用某个服务时,向注册中心查询该服务的所有可用实例列表。消费者获得实例列表后,结合负载均衡策略(轮询、随机、加权、一致性哈希等)选择一个实例发起 RPC 调用。

// 查询流程 1. Consumer 请求: GET /services/order-service/instances 2. Registry 返回: ["10.0.1.5:8080", "10.0.1.6:8080", "10.0.1.7:8080"] 3. Consumer 选择: loadBalance(instances) → "10.0.1.6:8080" 4. Consumer 调用: POST http://10.0.1.6:8080/api/orders

4. 实例下线与剔除

服务实例正常关闭时会主动向注册中心发送注销请求(优雅下线)。如果是异常宕机,注册中心通过心跳超时检测来被动剔除。部分实现还支持主动健康检查(注册中心反向探测实例的健康端点)。

优雅下线

实例主动发送 DELETE 请求注销自己,注册中心立即移除并通知消费者

异常剔除

心跳超时后注册中心标记为 DOWN,消费者缓存的旧记录也会在下次刷新时更新

4 交互演示

动手模拟服务注册与发现的完整流程

服务注册中心模拟器
服务名实例 ID地址状态最后心跳
暂无注册实例,点击"注册服务实例"开始
5 实现方式

服务发现有客户端发现和服务端发现两种基本模式

服务消费者 内嵌 Discovery Client + Load Balancer 注册中心 Registry Provider A Provider B Provider C 查询实例列表 直接调用

工作方式

消费者内嵌 Discovery Client 组件,自己从注册中心拉取实例列表,并在本地执行负载均衡后直接调用服务提供者。注册中心只负责存储信息,不参与请求转发。

优点:注册中心负载低,调用链路短,无额外网络跳转
缺点:客户端需要集成特定语言的 SDK,维护逻辑较复杂

Eureka Consul Zookeeper Nacos

服务消费者 普通 HTTP 调用 负载均衡器 Router / LB (查询注册中心) 注册中心 Provider A Provider B 查询 转发请求

工作方式

消费者将请求发送给负载均衡器/路由器(如 Nginx、K8s Service),由路由器查询注册中心获取实例列表,选择一个实例进行转发。消费者不需要知道注册中心的存在。

优点:客户端零侵入,无需集成 SDK,语言无关
缺点:多一层网络跳转,路由器可能成为瓶颈和单点

Nginx + Consul Template K8s Service Kong

6 常用方案

业界主流的服务注册与发现实现方案

Netflix Eureka

Netflix 开源的 REST-based 服务发现组件,AP 模型,强调可用性。Spring Cloud Eureka 是其最广泛的使用方式。

一致性模型AP 最终一致性,优先保证可用性
通信协议HTTP REST
健康检查客户端心跳模式(默认 30s 续约,90s 超时)
数据存储内存(每个节点全量缓存)
高可用Peer-to-Peer 节点复制,去中心化
缓存策略客户端缓存注册表,定期全量拉取(30s)
# application.yml (Spring Cloud) eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka1:8761/eureka,http://eureka2:8761/eureka instance: lease-renewal-interval-in-seconds: 30 lease-expiration-duration-in-seconds: 90

HashiCorp Consul

Go 语言编写的多功能工具,集服务发现、健康检查、KV 存储、多数据中心于一体。使用 Raft 协议保证强一致性。

一致性模型CP Raft 协议,强一致性(Leader 选举)
通信协议HTTP + DNS(支持 DNS 查询服务)
健康检查支持多种:HTTP/TCP/Script/Docker/gRPC
数据存储Raft Log + BoltDB(持久化)
高可用Raft 共识集群,Leader 模式
特色功能KV 存储、Service Mesh、多数据中心、Watches
# 服务注册定义 (consul.d/order-service.json) { "service": { "name": "order-service", "id": "order-1", "address": "10.0.1.5", "port": 8080, "check": { "http": "http://10.0.1.5:8080/health", "interval": "10s", "timeout": "3s" } } }

Alibaba Nacos

阿里巴巴开源,集服务发现与配置管理于一体。支持 AP/CP 切换,适合国内微服务生态。Spring Cloud Alibaba 的核心组件。

一致性模型AP / CP 可切换(临时实例用 AP,持久实例用 CP)
通信协议HTTP + gRPC
健康检查客户端心跳(临时实例)+ 服务端主动探测(持久实例)
数据存储内嵌 Derby(单机)或 MySQL(集群)
高可用Raft 协议选举 + Distro 分片协议
特色功能配置管理、命名空间、服务分组、权重路由
# application.yml (Spring Cloud Alibaba) spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: "production" group: "DEFAULT_GROUP" ephemeral: true # true=临时实例(AP), false=持久实例(CP)

Apache Zookeeper

Apache 基金会顶级项目,基于 ZAB 协议的分布式协调服务。通过临时节点(Ephemeral Node)机制实现服务注册与发现。Dubbo 生态的默认注册中心。

一致性模型CP ZAB 协议,强一致性
通信协议自定义 TCP 协议
健康检查基于 Session 的临时节点机制(Session 超时即删除)
数据存储内存树形结构(ZNode),持久化到磁盘
高可用ZAB 协议,Leader + Follower 架构
特色功能分布式锁、配置管理、Watch 机制、Leader 选举
# Zookeeper 中的节点结构 /services /order-service /0000000001 # 临时节点 (ephemeral) data: "10.0.1.5:8080" /0000000002 # 临时节点 data: "10.0.1.6:8080" /0000000003 # 临时节点 data: "10.0.1.7:8080" # Session 断开 → 临时节点自动删除 → 消费者 Watch 被触发

Kubernetes Service

K8s 原生的服务发现机制,通过 Label Selector 将 Pod 组抽象为一个 Service,提供稳定的虚拟 IP(ClusterIP)和 DNS 名称。

发现方式 1环境变量:Pod 启动时自动注入 Service 环境变量
发现方式 2DNS:CoreDNS 提供 service.namespace.svc.cluster.local
负载均衡kube-proxy(iptables / IPVS 模式)实现流量分发
健康检查Liveness Probe + Readiness Probe
数据平面Endpoint / EndpointSlice 控制实际转发目标
特色功能Headless Service、Ingress、Service Mesh(Istio)
# service.yaml apiVersion: v1 kind: Service metadata: name: order-service spec: selector: app: order version: v2 ports: - port: 80 targetPort: 8080 type: ClusterIP # 在其他 Pod 中通过 DNS 访问: curl http://order-service.default.svc.cluster.local/api/orders
7 方案选型对比

根据不同场景选择最适合的方案

特性 Eureka Consul Nacos Zookeeper K8s Service
一致性 AP CP AP/CP CP N/A
协议 HTTP REST HTTP + DNS HTTP + gRPC 自定义 TCP DNS + iptables
健康检查 客户端心跳 服务端探测 心跳 + 探测 Session 机制 Liveness/Readiness
数据存储 内存 Raft + BoltDB Derby / MySQL 内存 + 磁盘 etcd
语言 Java Go Java Java Go
配置管理 不支持 KV 存储 内置 ZNode ConfigMap
适用场景 Spring Cloud 生态 多语言/VM 环境 国内 Java 生态 Dubbo 生态 K8s 原生环境
社区活跃度 维护模式 活跃 活跃 稳定 非常活跃

选型建议

  • K8s 环境:直接用 K8s Service + CoreDNS,不需要额外的注册中心
  • Spring Cloud Java 项目:Nacos(推荐,功能全)或 Consul(多语言友好)
  • Dubbo 项目:Zookeeper 或 Nacos
  • 多语言混合技术栈:Consul(DNS 发现)或 Nacos(gRPC 支持)
  • 已有 Eureka 的老项目:继续维护即可,新项目建议迁移到 Nacos
8 生产实践要点

将服务注册与发现真正落地到生产环境的关键细节

高可用部署

  • 注册中心至少 3 节点集群部署
  • 跨可用区(AZ)分布节点
  • 客户端配置多个注册中心地址

缓存与容灾

  • 客户端本地缓存实例列表
  • 注册中心不可用时使用缓存兜底
  • 配合断路器(Hystrix/Sentinel)

优雅上下线

  • 启动预热:注册后再接收流量
  • 优雅下线:先注销再停机(preStop hook)
  • K8s 中使用 terminationGracePeriodSeconds

元数据与路由

  • 注册时携带版本、区域、权重等元数据
  • 支持按标签路由(灰度、流量染色)
  • 同区域优先调用(减少延迟)

服务注册伪代码实现

// ========== 服务提供者端 ========== class ServiceProvider: def start(self): # 1. 启动服务,监听端口 server = startHttpServer(port=8080) # 2. 向注册中心注册自己 registry.register( service_name="order-service", address=f"{self.ip}:{8080}", metadata={"version": "2.1.0"} ) # 3. 启动心跳线程 startHeartbeat(interval=30s) def stop(self): # 优雅下线:先注销,再停机 registry.deregister(service_name, address) server.shutdown() // ========== 服务消费者端 ========== class ServiceConsumer: def call(self, service_name, request): # 1. 从注册中心获取可用实例 instances = registry.fetch(service_name) # 2. 本地负载均衡 target = loadBalance( instances, strategy="weighted_round_robin" ) # 3. 发起调用 return httpClient.post( f"http://{target}/{request.path}", body=request.body )