背景
gRPCにはクライアントとサーバとの通信を抽象化したChannelという仕組みがあります。
GRPC_GO_LOG_SEVERITY_LEVEL=info
を有効にした際に出てくる
[core] Channel Created [core] parsed scheme: "" [core] scheme "" not registered, fallback to default scheme [core] ccResolverWrapper: sending update to cc: {[{localhost:8080 <nil> 0 <nil>}] <nil> <nil>} [core] Resolver state updated: {Addresses:[{Addr:localhost:8080 ServerName: Attributes:<nil> Type:0 Metadata:<nil>}] ServiceConfig:<nil> Attributes:<nil>} (resolver returned new addresses) [core] ClientConn switching balancer to "pick_first" [core] Channel switches to new LB policy "pick_first" [core] Subchannel Created [core] Subchannel(id:2) created [core] Subchannel Connectivity change to CONNECTING [core] Subchannel picks a new address "localhost:8080" to connect [core] Channel Connectivity change to CONNECTING [core] CPU time info is unavailable on non-linux or appengine environment. [core] Subchannel Connectivity change to READY [core] Channel Connectivity change to READY ... [core] Channel Connectivity change to SHUTDOWN [core] Subchannel Connectivity change to SHUTDOWN [core] Subchannel Deleted [core] Subchanel(id:2) deleted [core] Channel Deleted
Channel、Subchannelのような概念もあるので、理解のため一度整理します。
環境
- grpc-go v1.38.1
Channelの状態
Channelの状態は以下の5つがあります。
状態 | 説明 |
---|---|
CONNECTING | 接続を試みている状態。 名前解決、TCP接続の確立、TLSハンドシェイクなどが含まれる |
READY | 接続を確立し、その後のすべての通信試行が成功している |
TRANSIENT_FAILURE | 接続に失敗している状態。 接続の再試行が行われ成功するまでCONNECTING↔TRANSIENT_FAILUREの状態を繰り返す。 再試行は指数関数的なバックオフで行われるためこの状態にいる時間がどんどん長くなっていく。 |
IDLE | 新しいRPCや保留中のRPCがないため、Channelが接続を作成しようともしない状態。 デフォルトのIDLE_TIMEOUTは300秒(5分) |
SHUTDOWN | Channelがシャットダウンを開始。 保留中のRPCはキャンセルされるまで続行されるが 新規のRPCは即座に失敗する |
grpc/connectivity-semantics-and-api.md at master · grpc/grpc · GitHub
Channelの状態遷移図
Channelの状態は以下のように遷移します。
gRPC Channelz
gRPCの接続に関して、gRPC Channelzというデバッグの仕様があります。
理解しておくべき概念は以下です。
Channel
ChannelはRPCを開始したり完了したりすることができる機能を抽象化した概念です。
有向非巡回グラフ(DAG)で、内部のチャネルはChannelとSubhannelのみを持ち、リーフチャネルのみがSocketを持ちます。
ref: grpc/connectivity-semantics-and-api.md at master · grpc/grpc · GitHub
grpc-goでは一番上のChannelがClientConn
に当たります。
ClientConn作成時にChannelが登録されます。
Subchannel
Subchannelはロードバランスのための接続の抽象化です。
DNSで名前解決した際に複数のバックエンドが返った場合、各バックエンドへの(論理的な)接続はSubchannelとして表現されます。
grpc-goでは、SubConn
はSubchannelとして見ることができます。
ResolverState.Addresses毎にSubchannelを登録します。
- https://github.com/grpc/grpc-go/blob/v1.38.1/balancer/base/balancer.go#L109-L148
- https://github.com/grpc/grpc-go/blob/v1.38.1/balancer_conn_wrappers.go#L131-L150
- https://github.com/grpc/grpc-go/blob/v1.38.1/clientconn.go#L763
Socket
Socketはファイルディスクリプタとほぼ同等で、一般的には2つのエンドポイント間のTCPコネクションと考えることができます。
gRPCではTCPコネクション上でHTTP/2のstreamが多重化されますが、Socketは各streamに関する情報を保持しています。
grpc-goではhttp2Client
やhttp2Server
に当たります。
例えばクライアントはhttp2Clientを作成したタイミングで登録されます。
Channelzの可視化
Channelzの情報を可視化するには
- grpc-experiments/gdebug at master · grpc/grpc-experiments · GitHub
- GitHub - grpc-ecosystem/grpcdebug
といったツールがあります。
今回は後者のgrpcdebug
を使って可視化してみます。
インストールは以下。
$ go install -v github.com/grpc-ecosystem/grpcdebug@latest
実装
Channelzはサーバ側もクライアント側もメトリクスを出せます。
サーバ側はChannelよりも概念的にシンプルなので、先程のような階層モデルはありません。ロードバランシングや名前解決の必要もありません。各Subchannelは1つのSocketに結びついているので、SubchannelとSocketはフラット化されてSocketだけになります。
なので今回はクライアント側のみ表示できるようにします。
以下のようにChannelzのRPCを叩けるgRPCサーバを内部で起動します。クライアント側の処理をブロックしないよう非同期で起動しておきます。
import ( "log" "net" "google.golang.org/grpc" "google.golang.org/grpc/channelz/service" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/reflection" ) ... go func() { s := grpc.NewServer() reflection.Register(s) service.RegisterChannelzServiceToServer(s) lis, err := net.Listen("tcp", ":8000") if err != nil { log.Fatalf("failed to listen: %v", err) } if err := s.Serve(lis); err != nil { log.Fatalf("err %v\n", err) } }()
Channel
一覧取得と
$ grpcdebug localhost:8000 channelz channels Channel ID Target State Calls(Started/Succeeded/Failed) Created Time 1 localhost:8080 READY 1/0/0 13 seconds ago
ID指定による詳細取得があります。
$ grpcdebug localhost:8000 channelz channel 1 Channel ID: 1 Target: localhost:8080 State: READY Calls Started: 2 Calls Succeeded: 1 Calls Failed: 0 Created Time: 46 seconds ago --- Subchannel ID Target State Calls(Started/Succeeded/Failed) CreatedTime 2 localhost:8080 READY 2/1/0 46 seconds ago --- Severity Time Child Ref Description CT_INFO 46 seconds ago Channel Created CT_INFO 46 seconds ago parsed scheme: "" CT_INFO 46 seconds ago scheme "" not registered, fallback to default scheme CT_INFO 46 seconds ago ccResolverWrapper: sending update to cc: {[{localhost:8080 <nil> 0 <nil>}] <nil> <nil>} CT_INFO 46 seconds ago Resolver state updated: {Addresses:[{Addr:localhost:8080 ServerName: Attributes:<nil> Type:0 Metadata:<nil>}] ServiceConfig:<nil> Attributes:<nil>} (resolver returned new addresses) CT_INFO 46 seconds ago ClientConn switching balancer to "pick_first" CT_INFO 46 seconds ago Channel switches to new LB policy "pick_first" CT_INFO 46 seconds ago subchannel(subchannel_id:2 ) Subchannel(id:2) created CT_INFO 46 seconds ago Channel Connectivity change to CONNECTING CT_INFO 46 seconds ago Channel Connectivity change to READY
Subchannel
Channelで表示されていたSubchannel IDを指定することで取得できます。
$ grpcdebug localhost:8000 channelz subchannel 2 Subchannel ID: 2 Target: localhost:8080 State: READY Calls Started: 3 Calls Succeeded: 2 Calls Failed: 0 Created Time: 1 minute ago --- Socket ID Local->Remote Streams(Started/Succeeded/Failed) Messages(Sent/Received) 5 [::1]:52197->[::1]:8080 3/2/0 3/2
Socket
Subchannelで表示されていたSocket IDを指定することで取得できます。
$ grpcdebug localhost:8000 channelz socket 5 Socket ID: 5 Address: [::1]:52197->[::1]:8080 Streams Started: 4 Streams Succeeded: 4 Streams Failed: 0 Messages Sent: 4 Messages Received: 4 Keep Alives Sent: 11 Last Local Stream Created: 33 seconds ago Last Remote Stream Created: a long while ago Last Message Sent Created: 33 seconds ago Last Message Received Created: 11 seconds ago Local Flow Control Window: 65535 Remote Flow Control Window: 65535
先に述べたようにstreamの情報があります。またKeepaliveの送信数なども見れます。
TRANSIENT_FAILUREの場合
接続に問題が発生してChannelがTRANSIENT_FAILUREになった場合、
Channel
$ grpcdebug localhost:8000 channelz channel 1 Channel ID: 1 Target: localhost:8080 State: TRANSIENT_FAILURE Calls Started: 55 Calls Succeeded: 0 Calls Failed: 103 Created Time: 54 seconds ago --- Subchannel ID Target State Calls(Started/Succeeded/Failed) CreatedTime 2 localhost:8080 TRANSIENT_FAILURE 7/0/7 54 seconds ago
Subchannel
$ grpcdebug localhost:8000 channelz subchannel 2 Subchannel ID: 2 Target: localhost:8080 State: TRANSIENT_FAILURE Calls Started: 7 Calls Succeeded: 0 Calls Failed: 7 Created Time: 28 seconds ago
となりSocketは存在しません。
まとめ
Channelの概念を知ることでgRPCのコネクション周りの理解が少し深くなりました。