bigywater.tech
gRPC · 11

连接管理:一个 ClientConn 里的多个子连接与重连退避

gRPC 的 ClientConn 到底管着几条连接?什么时候建立?连接断了怎么重连、隔多久?服务端优雅关闭时客户端看到什么?空闲连接会被关吗?这篇逐项验证。

0 条
grpc.NewClient 之后、第一次调用之前的 TCP 连接数(惰性建连)
1.00、1.55、2.24、4.79 秒
默认退避下相邻两次重连尝试的间隔
2147483647 → 3
GracefulStop 时两个 GOAWAY 里的最后流 ID

惰性建连

实测 · 本机执行输出
  NewClient 之后 200 ms:状态 IDLE;代理看到的 TCP 连接 0 条
  第一次调用之后:状态 READY;TCP 连接 1 条
  再调用 20 次之后:状态 READY;TCP 连接仍是 1 条
  另一个 ClientConn 只调用 Connect(),不发请求:状态 READY;TCP 连接共 2 条

grpc.NewClient 不会连接,创建后状态是 IDLE。第一次调用或 Connect() 才建 TCP 连接;之后所有调用复用这一条。

一个 ClientConn 几条连接

实测 · 本机执行输出
  pick_first   30 次调用之后,三个后端各自收到的 TCP 连接数 [1 0 0]
  round_robin  30 次调用之后,三个后端各自收到的 TCP 连接数 [1 1 1]

每个后端地址一个子连接。pick_first 只连一个地址;round_robin 对所有地址都建连接并轮流使用(第 14 篇)。

重连退避

实测 · 本机执行输出
  默认退避:11 秒内尝试 5 次,时刻: 0.00s  (+1.00s) 1.00s  (+1.55s) 2.56s  (+2.24s) 4.80s  (+4.79s) 9.59s
     最后状态 TRANSIENT_FAILURE
  自定义退避(基础 100 ms、倍数 2、抖动 0、最大 1 s):11 秒内尝试 14 次,时刻: 0.00s  (+0.10s) 0.10s  (+0.20s) 0.30s  (+0.40s) 0.70s  (+0.80s) 1.50s  (+1.00s) 2.51s  (+1.00s) 3.51s  (+1.00s) 4.51s  (+1.00s) 5.51s  (+1.00s) 6.51s  (+1.00s) 7.51s  (+1.00s) 8.52s  (+1.00s) 9.52s  (+1.00s) 10.52s
     最后状态 TRANSIENT_FAILURE

连不上时按指数退避重试:默认基础 1 秒、倍数 1.6、抖动 20%、最长 120 秒(读源码 backoff/backoff.go)。可以用 WithConnectParams 调整。

断开之后

实测 · 本机执行输出
  服务端 Stop 之后正在进行的调用:code=Unavailable message="error reading from server: EOF"(0.2 ms 后返回)
  连接状态 IDLE
  退避期间的新调用(fail-fast):code=Unavailable,0.3 ms 返回
  服务端重启后用 WaitForReady(true) 调用:err=<nil>,等了 301.2 ms(退避结束后重连成功才发出)
  连接状态 READY

连接断开后客户端进入 IDLE,等下一次调用才重连;fail-fast 调用在退避期间立即失败,WaitForReady 会等到重连成功。

GracefulStop

实测 · 本机执行输出
  GracefulStop 开始后发起的新调用:code=Unavailable message="connection error: desc = \"error reading server preface: read tcp 127.0.0.1:55557->127.0.0.1:9806: read: connection reset by peer\""
  在途的慢调用:err=<nil>,399.4 ms 后完成
  线上 S→C GOAWAY:最后流 2147483647,错误码 NO_ERROR,附加数据 "graceful_stop"
  线上 S→C GOAWAY:最后流 3,错误码 NO_ERROR,附加数据 "graceful_stop"
  连接状态 TRANSIENT_FAILURE

服务端先发最后流 ID 为 2147483647 的 GOAWAY,再发带真实最后流 ID 的 GOAWAY;在途调用完成,新调用失败。

空闲超时

实测 · 本机执行输出
  第一次调用之后:状态 READY,TCP 连接 1 条
  空闲 700 ms 后:状态 IDLE
  再调用一次:2.1 ms,状态 READY,TCP 连接累计 2 条(重新建立了连接)

WithIdleTimeout 让空闲的连接关闭并回到 IDLE,下一次调用重新建立,可避开被中间设备悄悄断掉的连接(第 40 篇)。

分步演示

连接管理
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1 / grpc-go v1.84.0 / protobuf-go v1.36.12 / golang.org/x/net v0.57.0 / protoc 36.2(Homebrew);服务端和客户端都是本机起的 Go 进程(127.0.0.1,端口 9800 以上,不访问任何外部服务);需要看线上字节时用我自己写的 TCP 抓包代理加 golang.org/x/net/http2 的 Framer 解析 HTTP/2 帧;计时用 time.Now 或 perf_counter,数字每次运行会有小幅抖动。第三节的“假后端”是只 Accept 就 Close 的 TCP 监听,时刻是它记录的相对时间;退避的默认值是读 backoff/backoff.go 得到的;第二节用 resolver/manual 提供三个地址;第四节自定义了退避(基础 300 ms),所以 WaitForReady 等了约 301 ms;其余是测量
    • 没有验证:默认空闲超时 30 分钟(只读了源码注释,没有等够 30 分钟去测);MinConnectTimeout 的作用;连接建立过程中的 TLS 握手耗时;DNS 解析器何时重新解析;服务端 max_connection_age 触发的 GOAWAY(第 40 篇);多个 ClientConn 共享一个后端时服务端的连接数
    京ICP备2026067502号-1京公网安备11010502063487号