コンテンツにスキップ

gRPC@RPC-API

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. gRPCの仕組み

アーキテクチャ

RPCフレームワークの一つで、Protocol Bufferを使用してRPC (リモートプロシージャーコール) を実行する。

従来のHTTP/1.1ではなく、HTTP/2 (例:gRPCなど) を使用する。

RESTful-APIに対するリクエストではリクエストのヘッダーやボディを作成する必要がある。

一方で、リモートプロシージャーコールであれば通信先の関数を指定して引数を渡せばよく、まるで自身の関数のようにコールできる。

grpc_architecture


02. 通信方式

gRPCの通信方式とは

gRPCでは、gRPCクライアントとgRPCサーバーの間の通信方式に種類がある。

通信方式は、protoファイルで定義する。

grpc_connection-type


Unary RPC (単項RPC)

▼ 単項RPCとは

grpc_unary-rpc

まず、1個のTCPコネクションを確立し、その中に1個のストリームを作成する。

次に、gRPCクライアントが1個のリクエストを送信し、これが終えると受信後に1個のレスポンスを返信する。

一番よく使用する。

service Request {

  rpc Request (Request) returns (Response) {

    ...

  }
}


Server Streaming RPC (サーバーストリーミングRPC)

▼ サーバーストリーミングRPCとは

grpc_server-streaming

まず、1個のTCPコネクションを確立し、その中に複数のストリームを作成する。

次に、gRPCクライアントが1個のリクエストを送信し、これが終えるとサーバーは複数個のレスポンスを並行的に返信する。

任意のタイミングで、サーバーからまとめてレスポンスさせたい場合に使用する。

service Notification {

  rpc Notification (NotificationRequest) returns (stream NotificationResponse) {

    ...

  }
}


Client Streaming RPC (クライアントストリーミングRPC)

▼ クライアントストリーミングRPC とは

grpc_client-streaming-rpc

まず、1個のTCPコネクションを確立し、その中に複数のストリームを作成する。

次に、gRPCクライアントが複数個のリクエストを並行的に送信し、これが終えるとサーバーは1個のレスポンスを返信する。

gRPCクライアントからのリクエストのデータサイズが大きくなる場合 (例:アップロードサービス) に使用する。

service Upload {

  rpc Upload (stream UploadRequest) returns (UploadResponse) {

    ...

  }
}


Bidirectional Streaming RPC (双方向ストリーミングRPC)

▼ 双方向ストリーミングRPCとは

grpc_bidrectional-streaming-rpc

まず、1個のTCPコネクションを確立し、その中に複数のストリームを作成する。

次に、gRPCクライアントが複数個のリクエストを並行的に送信し、これが終えるとサーバーも複数個のレスポンスを並行的に返信する (逆にサーバーからもリクエストを送信できる)。

gRPCクライアントとgRPCサーバーが互いにリクエストを送信する場合 (例:チャット、オンラインゲーム) に使用する。

service Chat {

  rpc Chat (stream ChatRequest) returns (stream ChatResponse) {

    // gRPCクライアントからのリクエストを受信する。
    in, err := stream.Recv()

    ...

    // gRPCクライアントにリクエストを送信する。
    stream.Send(message);

    ...

    // リクエストを終了する。
    err = stream.CloseSend()
  }
}


03. HTTP/1.1とgRPCの違い

パケットの構造

項目 HTTP1.1の場合 HTTP2の場合
アプリケーションデータの形式 テキスト (例:JSON、XMLなど) バイナリ (例:Protocolbuf)
TLSによるアプリケーションデータの暗号化 任意 必須
トランスポートヘッダー あり あり
IPヘッダー あり あり


リクエストの構造

▼ リクエストメタデータ

gRPCのリクエストでは、メタデータをヘッダーに格納する。

メタデータのキー名 説明
accept-encoding
content-type
grpc-accept-encoding
grpc-timeout gRPCのタイムアウト時間を表す。
method リクエストのメソッドを表す。
path リクエストのパスを表す。
scheme
user-agent
...

▼ レスポンスメタデータ

gRPCのレスポンスでは、エラーに関するメタデータをトレーラーに、それ以外のメタデータをヘッダーに格納する。


レスポンスタイム

▼ HTTP/1.1の場合

HTTP/1.1の場合、1個のリクエストとレスポンスを送受信する。

▼ gRPCの場合

単項RPCの場合、1個のリクエストとレスポンスを送受信する。

そのため、従来のHTTP/1.1と同じレスポンスタイムである。

一方でストリーミングRPCの場合、複数個のリクエストとレスポンスを並行的に送受信する (多重化)。

そのため、重複しない通信時間が合計のレスポンスタイムになる。

この時、リクエストとレスポンスの多重化により、帯域幅を無駄なく使用できるため、レスポンスタイムが短くなる。

grpc_streaming-rpc_response-time


ステータスコード

HTTP/1.1の場合 HTTP/2の場合 意味 説明
200 0 OK リクエストに成功した。
499 1 Canceled
500 2 Unknown
400 3 InvalidArgument
504 4 DeadlineExceeded タイムアウト時間内にgRPCサーバーに接続できなかった。
404 5 NotFound リクエストしたデータが存在しない。
409 6 AlreadyExists
403 7 PermissionDenied
429 8 ResourceExhausted
400 9 FailedPrecondition
499 10 Aborted
400 11 OutOfRange リクエストのパラメーターが正しくない。
501 12 Unimplemented
500 13 Internal 宛先がエラーを返却した。
503 14 Unavailable gRPCサーバー側で関数を実行する準備ができておらず、gRPCクライアント側で関数のコールに失敗している。gRPCクライアントからgRPCサーバーへのリクエスト送信は完了したが、レスポンスが返信されていない可能性がある。
500 15 DataLoss
401 16 Unauthenticated


タイムアウト

▼ HTTP/1.1の場合

TCPコネクションをリクエスト/レスポンスにタイムアウト時間を適用する。

▼ 単項RPCの場合

TCPコネクション上に単一のストリーミングしかない。

そのため、ストリーミングを通過するリクエスト/レスポンスにタイムアウト時間を適用する。

gRPCは、TCPコネクションの確立前にタイムアウト時間を開始し、ストリーミング時に残りのタイムアウト時間をgrpc-timeoutヘッダーに設定する。

▼ ストリーミングRPCの場合

TCPコネクション上に複数のストリーミングがある。

そのため、ストリーミングを通過するリクエスト/レスポンスごとに同じタイムアウト時間を別々に適用する。

gRPCは、TCPコネクションの確立前にタイムアウト時間を開始し、ストリーミング時に残りのタイムアウト時間をgrpc-timeoutヘッダーに設定する。


04. ディレクトリ構成規約

前提

ここでは、マイクロサービスが以下のような順で実行されるとする。

foo # Node.js製
⬇⬆︎︎
⬇⬆︎︎
bar # Go製
⬇⬆︎︎
⬇⬆︎︎
baz # Python製


protoファイルをgRPCサーバー側に置く場合

▼ gRPCクライアント/サーバーのリポジトリ

各マイクロサービスのリポジトリでは、アプリケーションのインフラ層にprotoファイルを置く。

# fooサービス (Node.js製)
repository/
├── src/
│   ├── interface/
│   ├── usecase/
│   ├── domain/
│   ├── infrastructure
│   │   ├── doc/ # .protoファイルから自動作成したRPC-API仕様書
│   │   │   └── bar/
│   │   │       └── bar-client.html
│   │   │
│   │   ├── pb_go/ # .protoファイルから自動作成したpb.*ファイル
│   │   │   └── bar/
│   │   │       └── bar-client.pb.js
│   │   │
│   │   └── grpc # gRPCクライアントの定義
│   │       └── bar/
│   │           └── bar-client.js
│   ...

├── proto/ # サービス定義ファイル (.protoファイル)
│   └── bar/
│       └── bar-client.proto

...
# barサービス (Go製)
repository/
├── src/
│   ├── interface/
│   ├── usecase/
│   ├── domain/
│   ├── infrastructure
│   │   ├── doc/ # .protoファイルから自動作成したRPC-API仕様書
│   │   │   ├── bar/
│   │   │   │   └── bar-server.html
│   │   │   │
│   │   │   └── baz/
│   │   │       └── baz-client.html
│   │   │
│   │   ├── pb_go/ # .protoファイルから自動作成したpb.*ファイル
│   │   │   ├── bar/
│   │   │   │   └── bar-server.pb.go
│   │   │   │
│   │   │   └── baz/
│   │   │       └── baz-client.pb.go
│   │   │
│   │   └── grpc # gRPCクライアントとgRPCサーバーの定義
│   │       ├── bar/
│   │       │   └── bar-server.go
│   │       │
│   │       └── baz/
│   │           └── baz-client.go
│   │
│   ...

├── proto/ # サービス定義ファイル (.protoファイル)
│   ├── bar/
│   │   └── bar-server.proto
│   │
│   └── baz/
│       └── baz-client.proto

...
# bazサービス (Python製)
repository/
├── src/
│   ├── interface/
│   ├── usecase/
│   ├── domain/
│   ├── infrastructure
│   ├── infrastructure
│   │   ├── doc/ # .protoファイルから自動作成したRPC-API仕様書
│   │   │   └── baz/
│   │   │       └── baz-server.html
│   │   │
│   │   ├── pb_go/ # .protoファイルから自動作成したpb.*ファイル
│   │   │   └── baz/
│   │   │       └── baz-server.pb.py
│   │   │
│   │   └── grpc # gRPCサーバーの定義
│   │       └── baz/
│   │           └── baz-server.py
│   │
│   ...

├── proto/ # サービス定義ファイル (.protoファイル)
│   └── baz/
│       └── baz-server.proto

...


protoファイルとpb_goファイルを専用リポジトリに置く場合

▼ gRPCクライアント/サーバーのリポジトリ

各マイクロサービスのリポジトリでは、アプリケーションのインフラ層にgRPCクライアントとgRPCサーバーの定義を置く。

なお、pbファイルはProtocol Bufferの共有リポジトリで管理する。

# fooサービス (Node.js製)
repository/
├── src/
│   ├── interface/
│   ├── usecase/
│   ├── domain/
│   ├── infrastructure
│   │   └── grpc # gRPCクライアントの定義
│   │       └── bar/
│   │           └── bar-client.js
│   │
# barサービス (Go製)
repository/
├── src/
│   ├── interface/
│   ├── usecase/
│   ├── domain/
│   ├── infrastructure
│   │   └── grpc # gRPCサーバーとクライアントの定義
│   │       ├── bar/
│   │       │   └── bar-server.go
│   │       │
│   │       └── baz/
│   │           └── baz-client.go
│   │
# bazサービス (Python製)
repository/
├── src/
│   ├── interface/
│   ├── usecase/
│   ├── domain/
│   ├── infrastructure
│   │   └── grpc # gRPCサーバーの定義
│   │       └── baz/
│   │           └── baz-server.py
│   │

▼ Protocol Bufferの共有リポジトリ

pbファイルに関しては、gRPCサーバーは、さらにアップストリームのマイクロサービスをコールするgRPCクライアントにもなる。

そのため、Protocol Bufferの共有リポジトリでは、各マイクロサービスのprotoファイル、RPC-API仕様書、pbファイル、を管理する。

pbファイルには以下があり、これは共有リポジトリではなく、gRPCクライアント/サーバーのリポジトリで管理してもよい。

  • gRPCサーバーとしてのprotoファイルから作ったpbファイル
  • gRPCクライアントとしてのprotoファイル (これはアップストリームのgRPCサーバーのリポジトリにある) から作ったpbファイル
# Protocol Buffer
repository/
├── proto/ # サービス定義ファイル (.protoファイル)
│   ├── bar/
│   │   ├── bar-server.proto
│   │   └── bar-client.proto
│   │
│   └── baz/
│       └── baz-server.proto

├── doc/ # .protoファイルから自動作成したRPC-API仕様書
│   ├── bar/
│   │   ├── bar-server.html
│   │   └── bar-client.html
│   │
│   └── baz/
│       └── baz-server.html


└── pb_go/ # .protoファイルから自動作成した.pb.*ファイル
    ├── bar/
    │   ├── bar-server.pb.go
    │   └── bar-client.pb.js
    
    └── baz/
        ├── baz-client.pb.go
        └── baz-server.pb.py