bigywater.tech
gRPC · 39

通道状态:IDLE、CONNECTING、READY、TRANSIENT_FAILURE

IDLE、CONNECTING、READY、TRANSIENT_FAILURE、SHUTDOWN 分别什么时候出现?服务端重启后客户端多久恢复?通道状态能反映后端的健康吗?这篇用一个监视 goroutine 记录每一次状态变化,画出时间线。

IDLE
服务端断开后通道的状态(不是 TRANSIENT_FAILURE),要等下一次调用才重连
2.71 秒 → 3.49 秒
服务端重启的时刻与通道回到 READY 的时刻,中间是退避等待
READY 不代表全部
round_robin 下停掉一个后端,通道状态仍是 READY

五个状态

实测 · 本机执行输出
  调用(服务端不在)→ code=Unavailable message="connection error: desc = \"transport: Error while dialing: dial tcp 127.0.0.1:9800: connect…"
  服务端重新启动的时刻:2.71s
  服务端回来后的调用(fail-fast)→ 成功
  状态时间线(退避设为基础 500 ms、不抖动):0.00s IDLE → 0.30s CONNECTING → 0.30s READY → 0.61s IDLE → 0.91s CONNECTING → 0.91s TRANSIENT_FAILURE → 3.49s READY

IDLE(没有连接,等调用或 Connect)、CONNECTING(建连中,包括握手)、READY(可用)、TRANSIENT_FAILURE(连接失败,退避中)、SHUTDOWN(已关闭)。

断开之后

实测 · 本机执行输出
  状态时间线:0.00s CONNECTING → 1.00s TRANSIENT_FAILURE
  这期间发一次 fail-fast 调用(deadline 5 秒):code=Unavailable message="connection error: desc = \"error reading server preface: read tcp 127.0.0.1:50301->127.0.0.…"(0.1 ms 后返回)

已建立的连接断开后进入 IDLE,不会自己重连;下一次调用才触发重连,重连失败才进入 TRANSIENT_FAILURE。

退避

实测 · 本机执行输出
  状态时间线(4 秒内):0.00s CONNECTING → 0.00s TRANSIENT_FAILURE
  fail-fast 调用:code=Unavailable message="connection error: desc = \"transport: Error while dialing: dial tcp 127.0.0.1:9802: connect…"(0.1 ms 后返回)
  WaitForReady(true),deadline 800 ms:code=DeadlineExceeded message="latest balancer error: connection error: desc = \"transport: Error while dialing: dial tcp …"(802.1 ms 后返回)

TRANSIENT_FAILURE 期间按退避重试,服务端恢复后要等当前退避结束才会连上。fail-fast 的调用立即失败,WaitForReady 的调用等到 deadline。

握手超时

实测 · 本机执行输出
  Close 之后状态:SHUTDOWN;调用:code=Canceled message="grpc: the client connection is closing";WaitForReady 调用:code=Canceled message="grpc: the client connection is closing"
  再 Close 一次:rpc error: code = Canceled desc = grpc: the client connection is closing

连接建立阶段最多等 MinConnectTimeout,超时就当失败。

多个地址

实测 · 本机执行输出
  round_robin 停掉后端 1 之后:调用 成功,通道状态 READY;再停掉后端 2:调用 code=Unavailable message="connection error: desc = \"transport: Error while dialing: dial tcp 127.0.0.1:9804: connect…",通道状态 TRANSIENT_FAILURE
      状态时间线:0.00s CONNECTING → 0.00s READY → 0.50s IDLE → 0.50s CONNECTING → 0.51s TRANSIENT_FAILURE
  pick_first  停掉后端 1 之后:调用 成功,通道状态 READY;再停掉后端 2:调用 code=Unavailable message="connection error: desc = \"transport: Error while dialing: dial tcp 127.0.0.1:9807: connect…",通道状态 TRANSIENT_FAILURE
      状态时间线:0.00s CONNECTING → 0.00s READY → 0.20s IDLE → 0.51s CONNECTING → 0.51s READY → 0.51s IDLE → 0.81s CONNECTING → 0.81s TRANSIENT_FAILURE

通道状态是子连接状态的汇总,READY 只表示至少有一个可用,不能代替对每个后端的健康监控。

分步演示

通道状态
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。时间线由 conn.WaitForStateChange 驱动的监视 goroutine 记录(只记录状态变化,时间是相对起点的秒数);本篇把退避参数调成基础 500 ms 以便观察,默认值是基础 1 秒、倍数 1.6(第 11 篇);“假服务端”是只 Accept 不回应的 TCP 监听
    • 没有验证:IDLE 超时(WithIdleTimeout)与状态的关系(第 11 篇);其它负载均衡策略(ring_hash、least_request)的状态汇总;状态变化回调的 channelz / stats 观测方式;xDS 下的状态;TRANSIENT_FAILURE 时 round_robin 的 fail-fast 与 WaitForReady 细节;时间线中同一时刻多个状态的先后顺序是 WaitForStateChange 观察到的,可能漏掉极短暂的中间状态
    京ICP备2026067502号-1京公网安备11010502063487号