Files
cs-note/hzh/MS/06-gRPC/06-连接管理.md
T
2026-05-24 11:42:38 +08:00

6.4 KiB
Raw Blame History

tags, create time
tags create time
grpc
connection-management
keepalive
load-balancing
dns
service-discovery
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 分组的服务端调用计数

关联笔记