服务注册发现与负载均衡
在微服务架构中,服务实例动态扩缩容,客户端无法硬编码地址。注册中心与负载均衡解决了"如何找到服务"和"如何分配流量"的核心问题。
1. 服务注册发现模式
客户端发现
┌─────────┐ ┌───────────────┐ ┌────────────┐
│ Service │ ←→ │ Registry │ │ │
│ A │ │ (Eureka,Consul)│ │ Service B │
└─────────┘ └───────────────┘ └────────────┘
↓ 查询并缓存实例列表 ↑
└─────────────────────────────→
Service A 自行决定访问哪个 B 实例
代表:Eureka、Consul Client、Nacos Client
服务端发现
┌─────────┐ ┌───────────────┐ ┌────────────┐
│ Service │ → │ Load Balancer │ → │ Service B │
│ A │ │ (Consul,Nginx)│ │ Instance │
└─────────┘ └───────────────┘ └────────────┘
A 只访问 LB 地址,由 LB 选择后端实例
代表:AWS ALB、Kubernetes Service、Nginx + Consul Template
1.5 服务注册发现的核心挑战
在单体应用时代,服务地址是静态配置在文件中的。微服务化后,实例数量、IP、端口都在动态变化,注册发现系统必须解决以下核心挑战:
1. 服务如何注册?
- 自注册(Self-registration):服务启动时主动向注册中心注册,停止时注销。
- 第三方注册(Third-party registration):由外部代理(如 K8s Pod、Consul Agent)代为注册。
2. 服务如何发现?
- 客户端发现:客户端直接向注册中心查询服务列表,自行选择实例。
- 服务端发现:客户端只访问负载均衡器/网关,由其从注册中心获取实例并分发请求。
3. 如何处理故障?
- 心跳机制:实例定期发送心跳证明存活,超时未收到则标记为不健康。
- 健康检查:注册中心主动探测实例健康状态(HTTP/TCP/自定义脚本)。
- 快速失败重试:客户端调用失败时自动切换其他实例,避免级联故障。
4. 网络分区怎么办?
- CP 优先(Consul/ZooKeeper):网络分区时拒绝写入,保证一致性但牺牲可用性。
- AP 优先(Eureka/Nacos):网络分区时保留旧数据,优先保证可用性但可能读到过期信息。
2. 注册中心对比
| 特性 | Eureka | Consul | Nacos |
|---|---|---|---|
| 开发方 | Netflix | HashiCorp | 阿里巴巴 |
| 一致性协议 | AP(自我保护) | CP/AP 可选 | AP + raft CP |
| 健康检查 | Client 心跳 | HTTP/TCP/自定义 | TCP/HTTP/MYSQL |
| 多数据中心 | 弱支持 | 原生支持 | 支持 |
| 配置中心 | 无 | 简单 KV | 内置强大配置 |
| 生态 | Spring Cloud | 云原生 | Dubbo/Spring Cloud |
| 控制台 | 基础 | 完善 | 完善 |
Eureka 自我保护
网络分区时,Eureka 进入保护模式:不注销心跳过期的实例,优先保证可用性(AP)。
eureka:
server:
enable-self-preservation: true
renewal-percent-threshold: 0.85
Consul 服务网格
# 服务定义 + Sidecar 代理
service {
name = "order-service"
port = 8080
connect {
sidecar_service {}
}
}
Nacos 注册+配置一体化
spring:
cloud:
nacos:
discovery:
server-addr: nacos:8848
namespace: prod
config:
server-addr: nacos:8848
file-extension: yaml
2.5 服务端发现与 API 网关集成
服务端发现模式将服务发现的复杂性从客户端转移到基础设施层。客户端只需知道 API 网关的地址,由网关负责从注册中心获取实例、负载均衡、重试、熔断等。
Spring Cloud Gateway + Consul 集成示例:
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder,
ConsulDiscoveryClient discoveryClient) {
return builder.routes()
.route("order-service", r -> r
.path("/api/orders/**")
.filters(f -> f
.stripPrefix(2)
.circuitBreaker(config -> config
.setName("orderService")
.setFallbackUri("forward:/fallback/order"))
.retry(retryConfig -> retryConfig.setRetries(3)))
.uri("lb://order-service")) // lb:// 表示从 Consul 获取实例
.build();
}
}
服务端发现的优势:
| 维度 | 客户端发现 | 服务端发现 |
|---|---|---|
| 语言无关 | ❌ 需多语言 SDK | ✅ 客户端无感知 |
| 升级成本 | 高(需更新所有客户端) | 低(只需更新网关) |
| 流量控制 | 分散在各客户端 | 集中统一管理(限流、熔断) |
| 可观测性 | 需每个客户端接入追踪 | 单点接入,全链路追踪 |
| 适用场景 | 同构技术栈内部调用 | 异构系统、对外开放 API |
实践建议:内部微服务调用使用客户端发现(性能更优,减少一跳网络延迟);对外暴露 API 使用服务端发现(统一管理安全策略和流量控制)。
3. 负载均衡算法
| 算法 | 机制 | 适用场景 |
|---|---|---|
| Round Robin | 轮询 | 实例性能相近 |
| Weighted Round Robin | 按权重轮询 | 异构实例 |
| Least Connections | 最少连接 | 长连接服务 |
| Least Response Time | 响应时间最低 | 性能敏感 |
| Random | 随机 | 简单、无状态 |
| Consistent Hash | 一致性哈希 | 缓存场景 |
| IP Hash | 源 IP 哈希 | 会话保持 |
一致性哈希
Hash Ring (0 ~ 2^32-1)
Node A ●----------------● Node B
/ \
Key 1 ● ● Key 2
Key 3 ● ● Node C
Key 4
Key 顺时针找最近的 Node,新增/删除只影响相邻区域
Ribbon 负载均衡规则
// Spring Cloud OpenFeign + Ribbon
@FeignClient(name = "order-service", configuration = FeignConfig.class)
public interface OrderClient { ... }
// IRule 实现
new RoundRobinRule(); // 轮询
new WeightedResponseTimeRule(); // 按响应时间加权
new RandomRule(); // 随机
4. Kubernetes 服务发现
Service + Endpoint
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order
ports:
- port: 80
targetPort: 8080
# 集群内 DNS 解析
order-service.default.svc.cluster.local → Pod IPs
Headless Service
spec:
clusterIP: None # 直接返回 Pod IP 列表,用于 StatefulSet
高级流量管理(Istio)
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: order-route
spec:
http:
- route:
- destination:
host: order-service
subset: v1
weight: 90
- destination:
host: order-service
subset: v2
weight: 10
4.5 服务网格中的发现:Istio + Envoy xDS
服务网格(Service Mesh)将服务发现、负载均衡、安全通信、可观测性从应用代码中剥离到 Sidecar 代理(Envoy)中,通过控制面(Istiod)统一管理。
Istio 发现机制:
pilot-discovery (Istiod)
│
│ xDS API (ADS/CDS/EDS/LDS/RDS/SDS)
│
▼
Envoy Sidecar ──► 其他服务
│
mTLS + 自动负载均衡
| xDS API | 全称 | 用途 |
|---|---|---|
| CDS | Cluster Discovery Service | 定义后端集群和 LB 策略 |
| EDS | Endpoint Discovery Service | 动态发现和更新后端实例 |
| LDS | Listener Discovery Service | 配置入站监听器 |
| RDS | Route Discovery Service | 配置 HTTP 路由规则 |
| SDS | Secret Discovery Service | 动态证书管理 |
| ADS | Aggregated Discovery Service | 聚合所有 xDS 的单一 gRPC 流 |
Istio 服务发现的优势:
- 智能路由:无需修改应用即可实现金丝雀发布、A/B 测试、故障注入
- mTLS 自动加密:Sidecar 之间自动协商证书,应用无感知
- 多集群发现:通过 Istio 多集群网格,跨 K8s 集群的服务自动发现
- 细化观察:Envoy 原生输出 RED 指标(Rate/Errors/Duration)
# Istio DestinationRule:配置负载均衡和连接池
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: order-service
spec:
host: order-service
trafficPolicy:
loadBalancer:
simple: LEAST_REQUEST # 最少请求负载均衡
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 10
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s # 故障实例摘除 30 秒
选型建议:小规模(< 50 服务)用 K8s Service + Ingress 足够;中等规模(50-200 服务)用 Consul Connect 或 Linkerd(轻量);大规模(> 200 服务)或需要多集群治理能力时选择 Istio。
5. CAP 权衡:CP vs AP 选型
分布式系统的 CAP 定理指出:一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance)三者不可兼得。注册中心的选型本质上是 CP 与 AP 的权衡。
CP 注册中心(ZooKeeper/Consul with Raft/etcd):
网络分区时:
┌─────────────┐ × ┌─────────────┐
│ Leader │◄───────►│ Follower │
│ (可写) │ 分区 │ (拒绝写入) │
└─────────────┘ └─────────────┘
优势:强一致性,不会读到过期节点
劣势:分区时部分节点不可用
AP 注册中心(Eureka/Nacos 默认模式):
网络分区时:
┌─────────────┐ × ┌─────────────┐
│ Node A │◄───────►│ Node B │
│ (接受注册) │ 分区 │ (接受注册) │
└─────────────┘ └─────────────┘
优势:分区时所有节点仍可读写
劣势:可能读到已下线的实例(需客户端重试)
选型决策树:
需要强一致性保证(如金融交易服务发现)?
├─ 是 → CP(Consul Raft / etcd / ZooKeeper)
└─ 否 → 需要多语言 SDK 支持?
├─ 是 → Nacos(Java/Go/Python/Node.js)
└─ 否 → Spring Cloud 生态?
├─ 是 → Eureka(简单)/ Consul(功能全)
└─ 否 → Kubernetes 原生 DNS
6. 高可用设计
| 策略 | 说明 |
|---|---|
| 多实例集群 | 注册中心本身多节点部署 |
| 客户端缓存 | 本地缓存服务列表,注册中心故障仍可调用 |
| 心跳保活 | 定期续约,及时剔除不健康实例 |
| 重试+退避 | 调用失败时自动切换实例 |
总结
| 维度 | 客户端发现 | 服务端发现 |
|---|---|---|
| 耦合 | 客户端依赖 SDK | 客户端无感知 |
| 灵活性 | 可自定义 LB 策略 | 集中管理 |
| 复杂度 | 客户端较重 | 需额外 LB 组件 |
| 典型 | Eureka + Ribbon | K8s Service + Istio |
选择注册中心时考虑:技术栈匹配、一致性需求、生态集成、运维复杂度。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。