gRPC@RPC-API¶
はじめに¶
本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。
01. gRPCの仕組み¶
アーキテクチャ¶
RPCフレームワークの一つで、Protocol Bufferを使用してRPC (リモートプロシージャーコール) を実行する。
従来のHTTP/1.1ではなく、HTTP/2 (例:gRPCなど) を使用する。
RESTful-APIに対するリクエストではリクエストのヘッダーやボディを作成する必要がある。
一方で、リモートプロシージャーコールであれば通信先の関数を指定して引数を渡せばよく、まるで自身の関数のようにコールできる。
- https://qiita.com/gold-kou/items/a1cc2be6045723e242eb#%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%A7%E9%AB%98%E9%80%9F%E5%8C%96
- https://openstandia.jp/oss_info/grpc/
- https://syu-m-5151.hatenablog.com/entry/2022/04/12/130411
- https://atmarkit.itmedia.co.jp/ait/articles/1501/26/news009.html
02. 通信方式¶
gRPCの通信方式とは¶
gRPCでは、gRPCクライアントとgRPCサーバーの間の通信方式に種類がある。
通信方式は、proto
ファイルで定義する。
Unary RPC (単項RPC)¶
▼ 単項RPCとは¶
まず、1
個のTCPコネクションを確立し、その中に1
個のストリームを作成する。
次に、gRPCクライアントが1
個のリクエストを送信し、これが終えると受信後に1
個のレスポンスを返信する。
一番よく使用する。
service Request {
rpc Request (Request) returns (Response) {
...
}
}
Server Streaming RPC (サーバーストリーミングRPC)¶
▼ サーバーストリーミングRPCとは¶
まず、1
個のTCPコネクションを確立し、その中に複数のストリームを作成する。
次に、gRPCクライアントが1
個のリクエストを送信し、これが終えるとサーバーは複数個のレスポンスを並行的に返信する。
任意のタイミングで、サーバーからまとめてレスポンスさせたい場合に使用する。
service Notification {
rpc Notification (NotificationRequest) returns (stream NotificationResponse) {
...
}
}
Client Streaming RPC (クライアントストリーミングRPC)¶
▼ クライアントストリーミングRPC とは¶
まず、1
個のTCPコネクションを確立し、その中に複数のストリームを作成する。
次に、gRPCクライアントが複数個のリクエストを並行的に送信し、これが終えるとサーバーは1
個のレスポンスを返信する。
gRPCクライアントからのリクエストのデータサイズが大きくなる場合 (例:アップロードサービス) に使用する。
service Upload {
rpc Upload (stream UploadRequest) returns (UploadResponse) {
...
}
}
Bidirectional Streaming RPC (双方向ストリーミングRPC)¶
▼ 双方向ストリーミング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の場合、複数個のリクエストとレスポンスを並行的に送受信する (多重化)。
そのため、重複しない通信時間が合計のレスポンスタイムになる。
この時、リクエストとレスポンスの多重化により、帯域幅を無駄なく使用できるため、レスポンスタイムが短くなる。
- https://www.thoughtworks.com/insights/blog/microservices/scaling-microservices-gRPC-part-one
- https://levelup.gitconnected.com/scaling-microservices-with-grpc-and-envoy-72a64fc5bbb6
- https://zenn.dev/zawawahoge/articles/8690c7bd521099#http%2F2%E3%81%AE%E5%BC%B7%E3%81%BF%EF%BC%9A%E5%A4%9A%E9%87%8D%E5%8C%96
ステータスコード¶
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 |
- https://grpc.io/docs/guides/error/#error-status-codes
- https://zenn.dev/hsaki/books/golang-grpc-starting/viewer/errorcode#http%E3%81%AE%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%81%A8%E3%81%AE%E9%81%95%E3%81%84
- https://zenn.dev/hsaki/books/golang-grpc-starting/viewer/errorcode#http%E3%81%AE%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%81%A8%E3%81%AE%E9%81%95%E3%81%84
- https://qiita.com/Hiraku/items/0549e4cf7079d22b27e8
タイムアウト¶
▼ 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