--- 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-容错模式]] — 负载均衡 + 熔断的组合效果