---
tags: [grpc, connection-management, keepalive, load-balancing, dns, service-discovery]
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)切断:
```go
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 |
### 连接生命周期
```mermaid
timeline
title 连接生命周期管理
0 min : 建立连接
5 min : 达到 MaxConnectionAge
服务端主动关闭
(旧连接不再收新请求)
5min+ : 客户端创建新连接
完成平滑迁移
```
> [!keypoint] MaxConnectionAge 的作用
>
> 它不是为了保活,而是为了**平滑退役**。当后端实例缩容或升级时,通过 MaxConnectionAge 让旧连接自然到期失效,客户端自动切换到新连接,避免突然断连导致的请求失败。
## 负载均衡 Picker
gRPC 内建了几种负载均衡策略,客户端侧自动分发请求到不同后端实例:
```mermaid
graph LR
LR_WRR["Weighted Round Robin
权重轮询"] --> Picking["Picker.pick()
→ 选择一个 SubConn"]
LR_HRR["Hash-based
粘性会话"] --> Picking
LR_RRS["Random Selection
随机挑选"] --> Picking
LR_PR["Pick First
单一连接"] --> Picking
Picking --> SubConn["SubConn
单个后端实例"]
```
### 策略对比
| 策略 | 行为 | 适用场景 |
|------|------|---------|
| **pick_first**(默认) | 每次只用一个 SubConn,直到 health check 失败才切换 | 只有一个后端、简单场景 |
| **round_robin** | 轮询分发到新连接 | 无状态服务、均匀负载 |
| **weighted_round_robin** | 按权重轮询(Go 1.25+) | 后端规格不一致时使用 |
| **hash-based** | 按 key hash 选定后端 | 需要会话粘性的场景 |
```go
// 客户端指定负载均衡策略
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 的桥梁:
```mermaid
graph LR
Target["目标地址:
dns:///svc:9000"] --> NR["Name Resolver"]
NR --> SD["服务发现后端
consul / kubernetes / etcd"]
SD --> AddrList["[]Resolver.Addresses"]
AddrList --> CC["ClientConn
新建/复用 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 环境下的零配置方案
```go
// 结合 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-容错模式]] — 负载均衡 + 熔断的组合效果