6.4 KiB
tags, create time
| tags | create time | ||||||
|---|---|---|---|---|---|---|---|
|
2026-05-07 16:00 |
连接管理与负载均衡
概述
在生产环境中,gRPC 连接的管理质量直接影响稳定性和可用性。本文将 Cover 连接保活策略、负载均衡 Picker、服务发现 Name Resolver 三大主题——这些都是日常开发容易忽略但出问题时就是一大片故障的领域。
[!question] 先看一个问题
两个微服务之间建立了 gRPC 连接,之后 30 分钟没有任何请求。此时第一个新的 RPC 请求发起时会发生什么?
如果中间经过了 Nginx、云厂商 LB 或 AWS ALB,很可能连接已经被idle timeout 切断。但两端都还认为连接是活的,于是第一次 Send() 报 use of closed network connection。这就是没有配置 Keepalive 的典型故障。
Keepalive 策略
gRPC 连接默认不发送 keepalive ping,长时间空闲的连接会被中间代理(如 Nginx、云厂商 LB)切断:
server := grpc.NewServer(
grpc.KeepaliveParams(keepalive.ServerParameters{
Time: 10 * time.Second, // ping 间隔
Timeout: 5 * time.Second, // 超时检测
MaxConnectionAge: 5 * time.Minute, // 最大生命周期(平滑退役)
}),
grpc.KeepaliveEnvelope(keepalive.EnforcementPolicy{
MinTime: 5 * time.Second, // 客户端最小 ping 间隔
PermitWithoutStream: true, // 允许空闲连接保活
}),
)
Keepalive 参数解读
| 参数 | 方向 | 含义 |
|---|---|---|
Time |
服务端 | 多久没收到 ping 就发一个 ping |
Timeout |
服务端 | 发了 ping 后等多久没回复就算对方挂了 |
MaxConnectionAge |
服务端 | 连接最多存活多久,强制关闭让客户端重建 |
MinTime |
服务端 | 限制客户端 ping 频率,防 DoS |
PermitWithoutStream |
服务端 | 即使没有活跃 Stream 也允许发送 ping |
连接生命周期
timeline
title 连接生命周期管理
0 min : 建立连接
5 min : 达到 MaxConnectionAge<br/>服务端主动关闭<br/>(旧连接不再收新请求)
5min+ : 客户端创建新连接<br/>完成平滑迁移
[!keypoint] MaxConnectionAge 的作用
它不是为了保活,而是为了平滑退役。当后端实例缩容或升级时,通过 MaxConnectionAge 让旧连接自然到期失效,客户端自动切换到新连接,避免突然断连导致的请求失败。
负载均衡 Picker
gRPC 内建了几种负载均衡策略,客户端侧自动分发请求到不同后端实例:
graph LR
LR_WRR["Weighted Round Robin<br/>权重轮询"] --> Picking["Picker.pick()<br/>→ 选择一个 SubConn"]
LR_HRR["Hash-based<br/>粘性会话"] --> Picking
LR_RRS["Random Selection<br/>随机挑选"] --> Picking
LR_PR["Pick First<br/>单一连接"] --> Picking
Picking --> SubConn["SubConn<br/>单个后端实例"]
策略对比
| 策略 | 行为 | 适用场景 |
|---|---|---|
| pick_first(默认) | 每次只用一个 SubConn,直到 health check 失败才切换 | 只有一个后端、简单场景 |
| round_robin | 轮询分发到新连接 | 无状态服务、均匀负载 |
| weighted_round_robin | 按权重轮询(Go 1.25+) | 后端规格不一致时使用 |
| hash-based | 按 key hash 选定后端 | 需要会话粘性的场景 |
// 客户端指定负载均衡策略
conn, err := grpc.Dial(
"dns:///orderservice:9000",
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"round_robin":{}}]}`),
)
[!warning] pick_first 的隐患
默认的 pick_first 策略只使用一个连接。如果这个连接对应的后端实例挂了,gRPC 要等到 health check 失败才会切换。在高可用要求高的场景下,务必切换到 round_robin。
Name Resolver —— 服务发现桥梁
Name Resolver 是 gRPC 将逻辑服务名解析为物理 IP:Port 的桥梁:
graph LR
Target["目标地址:<br/>dns:///svc:9000"] --> NR["Name Resolver"]
NR --> SD["服务发现后端<br/>consul / kubernetes / etcd"]
SD --> AddrList["[]Resolver.Addresses"]
AddrList --> CC["ClientConn<br/>新建/复用 SubConn"]
style NR fill:#fff3e0
style SD fill:#e3f2fd
Name Resolver 方案
| 方案 | URI 前缀 | 适用场景 |
|---|---|---|
| DNS | dns:///host:port |
最简单,依赖 DNS 记录 |
| Kubernetes | k8s:// |
K8s Service Discovery |
| Eureka | eureka:///service-name |
Spring Cloud 生态 |
| File | file:///path/to/config |
静态配置文件开发调试 |
Kubernetes 环境下的零配置方案
// 结合 CoreDNS SRV 记录,无需硬编码任何地址
conn, err := grpc.Dial(
"dns:///my-service.default.svc.cluster.local:9000",
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"round_robin":{}}]}`),
)
Kubernetes 的 DNS 控制器会自动将 Service 的 Endpoints 更新到 DNS 记录,gRPC 客户端只需要监听 DNS 变化即可。
[!tip] 云原生首选
在 Kubernetes 环境中,结合 ExternalName 或 CoreDNS SRV 记录可以实现零配置的服务发现。配合
round_robin负载均衡策略,后端扩容缩容时客户端自动感知,无需手动干预。
连接管理与可观测性联动
| 维度 | 与可观测性的配合 |
|---|---|
| 连接断开 | 通过 Metrics 监控连接创建/销毁频率,异常突增说明后端频繁重启 |
| 负载均衡不均 | 通过 Histogram 看每个后端实例的 QPS 分布,失衡则调整 picker |
| Keepalive 超时 | 通过日志告警 Detect connection reset,定位中间代理 idle timeout 配置 |
[!keypoint] 监控建议
生产环境的 gRPC 客户端和服务端都应该暴露以下指标:
grpc_connection_state_changes_total:连接状态变更次数grpc_call_duration_seconds:单次 RPC 耗时grpc_server_handled_total:按 status code 分组的服务端调用计数
关联笔记
- 01-协议与架构 — Name Resolver 是架构图中连接管理层的第一环
- 07-最佳实践 — Keepalive、负载均衡在生产环境的配合配置
- 02-服务治理/04-服务发现 — 更深度的服务发现机制对比(Nacos / Consul / K8s)
- 02-服务治理/06-容错模式 — 负载均衡 + 熔断的组合效果