コンテンツにスキップ

gRPC@RPC-API

はじめに

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


01. gRPC の仕組み

アーキテクチャ

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

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

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

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

grpc_architecture


TLS の有無 (暗号化の有無)

Web ブラウザが gRPC クライアントの場合、TLS は必須である。

ただ、それ以外の場合は gRPC では TLS を無効化できる。


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 個のリクエストを送信し、これが終えるとサーバーは複数個のレスポンスを並行的に返信する。

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

サーバーストリーミング RPC であっても、クライアントがクライアントストリーミング RPC を非同期的に実行すれば、同時ストリーミングになる。

service Notification {

  rpc Notification (NotificationRequest) returns (stream NotificationResponse) {

    ...

  }
}


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

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

grpc_client-streaming-rpc

ストリーミング方式の通信を実施する。

まず、1 個の TCP 接続を確立し、そのなかに単一のストリームを作成する。

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

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

クライアントストリーミング RPC であっても、クライアントがクライアントストリーミング RPC を非同期的に実行すれば、同時ストリーミングになる。

service Upload {

  rpc Upload (stream UploadRequest) returns (UploadResponse) {

    ...

  }
}


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

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

grpc_bidrectional-streaming-rpc

ストリーミング方式の通信を実施する。

まず、1 個の TCP 接続を確立し、そのなかに複数のストリームを同時に作成する。

次に、gRPC クライアントがストリーム上で複数個のリクエストを並行的に送信する。

これが終えると、もう一方のストリーム上でサーバーも複数個のレスポンスを並行的に返信する (逆にサーバーからもリクエストを送信できる)。

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

双方向ストリーミング RPC では、双方向の独立したストリーミングを実行するため、結果的に同時ストリーミングになる。

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 の違い

パケットの構造

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


リクエストの構造

▼ リクエストメタデータ

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

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

▼ レスポンスメタデータ

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


通信の多重化

▼ HTTP/1.1 の場合

TCP 接続を確立中、レスポンスの返信があるまで、次のリクエストを送信できない。

つまり、単一のリクエストとレスポンスが単一の TCP 接続を占有し、レスポンスの返信があるまで次のリクエスト送信を待たないといけない (HTTP HoL ブロッキング) 。

▼ gRPC の場合

TCP 接続を確立中、レスポンスの返信がなくても、次のリクエストを並列的に送信できる。

つまり、複数のリクエストとレスポンスが単一の TCP 接続を共有し、レスポンスがなくとも次のリクエストを並行的に送信できる。


レスポンスタイム

▼ 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 gRPC クライアントが処理を中断した。
500 2 Unknown いずれのステータスコードにも属していない不明なエラーである。
400 3 InvalidArgument 無効な引数を指定したリクエストである。
504 4 DeadlineExceeded 処理が完了する前にタイムアウト時間を超過した。正常な場合でも、タイムアウト時間の超過でこのエラーになることがある。
404 5 NotFound リクエストしたデータが存在しない。
409 6 AlreadyExists
403 7 PermissionDenied
429 8 ResourceExhausted gRPC クライアントがリクエスト送信しすぎている。
400 9 FailedPrecondition
499 10 Aborted
400 11 OutOfRange リクエストのパラメーターが正しくない。
501 12 Unimplemented
500 13 Internal gRPC サーバーがエラーを返却した。
503 14 Unavailable gRPC サーバー側で関数を実行する準備ができておらず、gRPC クライアント側で関数のコールに失敗している。gRPC クライアントから gRPC サーバーへのリクエスト送信は完了したが、レスポンスが返信されていない可能性がある。
500 15 DataLoss
401 16 Unauthenticated

▼ リトライすべきステータスコード

以下のステータスコードは、一時的な問題で発生している可能性がある。

そのため、リトライすると問題を解決できる可能性がある。

  • DeadlineExceeded (4)
  • ResourceExhausted (8)
  • DeadlineExceeded (4)

なお、Canceled は gRPC クライアントがこれ以上のリクエストを必要としていない可能性があり、不要である。


タイムアウト

▼ HTTP/1.1 の場合

TCP 接続とリクエスト/レスポンスにタイムアウト時間を適用する。

▼ 単項 RPC の場合

TCP 接続上に単一のストリーミングしかない。

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

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

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

TCP 接続上に複数のストリーミングがある。

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

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


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

前提

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

foo # JavaScript 製
⬇⬆️︎
⬇⬆️︎
bar # Go 製
⬇⬆️︎
⬇⬆️︎
baz # Python 製


proto ファイルを gRPC サーバー側に配置する場合

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

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

# foo サービス (JavaScript 製)
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 サービス (JavaScript 製)
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