Closed3

gRPCの雑なメモ

koheiyamayamakoheiyamayama

適当に訳しつつ感想やらまとめらを書いていくの巻

gRPCはprotocol buffersをInterface Definition Language(IDL)とメッセージ交換フォーマットとして使っている。

概要

gRPCの世界では、あるクライアントアプリケーションは異なるマシン上で動いているサーバアプリケーションのメソッドを直接呼び出すことができる。まるでローカルオブジェクトのように。
そして、それはあなたが分散システムを作ることを簡単にする。
多くのRPCシステムと同様に、gRPCはサービスを定義すること、特定のパラメータと返り値でリモートにある呼び出し可能なメソッドを特定するという二つのアイディアに基づいている。
サーバサイドでは、サーバはこのインターフェイスを実装し、クライアント呼び出しを扱うためにgRPCサーバを動かす。
クライアントサイドでは、そのクライアントはサーバと同じメソッドを提供しているスタブ(スタブはいくつかの言語では単にクライアントと呼ばれている)を持っている。

gRPCサーバとクライアントは様々な環境で動き、喋ることができるし、gRPCをサポートする言語であれば実装可能である。
また最新のGoogleのAPIはgRPCのインターフェイスを持っているので、開発者はそれを使うことができる。

感想

まるでローカルオブジェクトのようにっていう表現がいい感じに表している。
クライアントとサーバがさも同一プロセスであるかのようにコードを書くことができるっていうのいいところ。それを実現するためにクライアントとサーバのインターフェイスがprotoファイルを元に生成されて、パラメータと返り値が決められるっていうのが重要。

Protocol Buffersの取り扱い

デフォルトでgRPCはProtocol Buffersを使っている。Protocol BuffersはGoogleの構造化データをシリアライズするための、成熟したオープンソースメカニズムである。
さくっとPBの一例を見てみる。

まず、PBを使うってことは、はあなたがシリアライズしたいデータをprotoファイルで定義することだ。
PBデータはmessageというデータ構造であり、そこでは各々のメッセージがfieldsと呼ばれるname-valueのペアを含む小さいレコードである。
こんな感じ。

message Person {
  string name = 1;
  int32 id = 2;
  bool has_ponycopter = 3;
}

上のコードのようにあなたがデータ構造を特定すると、protoファイルからあなたが好きなプログラミング言語でデータアクセスクラスを生成するために protoc(protocol buffer compile?)を使うことができる。

これらはそれぞれのフィールド(person.name, person.id, person.has=ponycopterとか)に対してシンプルなアクセサーを生のバイトから/へ全体の構造をパース/シリアライズするためのメソッドとして提供する。

例えば、あなたの選んだ言語はC++であるなら、上の例protoコードをコンパイルすると、Personクラスを生成する。あなたはPerson protocol buffer messageを集める、シリアライズする、詰め替えるためにあなたのアプリケーションでこのクラスを使うことができる。

あなたは普通のprotoファイル内でRPCメソッドを持つgRPC Serviceを定義する。RPCメソッドにはパラメータと返り値がProtocol Buffer messageとして指定されている。

// The greeter service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

上のコードをprotocでコンパイルするとさっきの例のようにprotoファイルからクライアントとサーバのコードを生成できる。

感想

これを踏まえてgetting startedかなんかのコードを読む。
が、概要はなんとなーくわかった。

koheiyamayamakoheiyamayama

Core Concepts

Service Definition

多くのRPCシステムと同じように、gRPCはサービスを定義し、メソッドのシグネチャを持つリモートで呼び出されうるメソッドを指定するというアイディアに基づいている。
デフォルトでgRCPはprotocol bufferを使っている。それは他の大体の何かを使うこともできる。

gRPCはあなたに4種類のサービスメソッドを定義させる。

  1. クライアントがサーバへ1つのリクエストを送り、1つのレスポンスを得る、要するに普通の関数呼び出しである。これをUnary RPCsと呼ぶ。
rpc SayHello(HelloRequest) returns (HelloResponse);
  1. クライアントがサーバへリクエストを送ると、戻り値のメッセージシーケンスを読み込むためのストリームを受け取る。クライアントはメッセージがなくなるまで、ストリームからデータを読み込む。gRPCは個々のRPC呼び出し内ではメッセージの順番を保証する。これをServer Streaming RPCsと呼ぶ。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
  1. クライアントはメッセージシーケンスを書き、それらをサーバへ送る。サーバがメッセージを読み込み終わり、レスポンスを返すのを待つ。再びgRPCは個々のRPC呼び出し内ではメッセージの順番を保証する。これをClient Streaming RPCsと呼ぶ。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
  1. クライアントとサーバの両サイドがストリームの読み書きを使ってメッセージシーケンスを送る。2つのストリームに対する操作は独立しており、クライアントとサーバはお互いが好きなタイミングでストリームの読み書きができる。例えば、サーバはレスポンスを書き込む前にクライアントが全てのメッセージを書き終わるのを待つことができる。もしくは部分的にメッセージを読み込み、レスポンスを書き込むとかもできる。他にもいくつかの読み込みと書き込みの組み合わせがある。メッセージの順番はストリームごとに保持されている。この方式をBidirectional streaming RPCs という。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);

感想

全体的にストリームがよう分からん。つまり、2,3,4番が分からん。
分からんポイントはストリームは要するに何かしらのデータ構造で、これはクライアントとサーバによって生成、消費されるっぽいのだが、具体的にどんなデータ構造でどういう消費がされるのか分からない。実際に使ってみることとする。

Using the API

protoファイル内でのサービス定義から始めて、gRPCがクライアントとサーバサイドのコードを生成するプラグインをprotocは提供する。
gRPCユーザはだいたいがクライアントサイドでこれらのAPIを呼び出し、サーバサイドに対応するAPIを実装する。

  • サーバサイドでは、そのサーバはサービスによって宣言されているメソッドを実装し、クライアントのメソッド呼び出しを扱うためにgRPCサーバを起動する。gRPCの内部ではincoming requestをデコードし、サービスメソッドを実行し、サービスレスポンスをエンコードする。
  • クライアントサイドでは、クライアントはサービスに定義されているメソッドと同じメソッドを実装するスタブというローカルオブジェクトを持つ。スタブはいくつかの言語では単にクライアントと呼ばれている。gRPCはリクエストをサーバに送り、サーバのprotocol buffer responseを返す。

感想

proto fileにServiceが定義されていて、そのServiceはいくつかのメソッドを持つ。
そのproto fileをprotocを使ってコンパイルすると、サーバとクライアントに似たようなクラスとメソッドの雛形が生成される。例えば、proto fileにPerson service、person.sayHelloメソッドが定義されているとしたら、サーバとクライアントにはpersonクラス(モジュールかもしれないし、構造体かもしれない)とsayHelloメソッド(関数もかもしれない)が定義されたコードが生成される。
クライアントはsayHelloメソッドを呼び出すと、サーバサイドに定義されているsayHelloメソッドが呼び出される。
クライアントサイドからあたかもpersonオブジェクトのsayHelloメソッドを呼び出したかに見えるわけだ。
Rubyと相性良さそう。

Synchronous vs. asynchronous

サーバからのレスポンスが返ってくるまで処理をブロックする、同期的なRPC呼び出しはRPCが強く望む手続き呼び出しに最も近い。一方で、ネットワークは非同期であり、多くのケースではスレッドをブロッキングせずにRPCを開始できる。
たいていのプログラミング言語のgRPC APIは同期的、非同期的な方法の両方を提供している。
それは各言語のドキュメントを見れば分かるだろう。

感想

まるでローカルオブジェクトのようっていう感じだと、同期的な呼び出しを期待することが多いと思ってしまったが、これは俺があまり非同期なプログラミングに慣れていないからなのだろう。

RPC life cycle

このセクションではgRPC ClientがgRPC Serverのメソッドを呼び出した時に何が起こるのか、より詳細に説明する。
より詳細な実装は各言語のドキュメントを見てください。

Unary RPC(単一RPC)

まず、クライアントがサーバにリクエストを送り、リクエストを受け取るという最も単純なRPCを考える。

  1. そのクライアントはスタブメソッドを呼び出すと、サーバはクライアントのmetadata、メソッド名、該当する場合はdeadlineを渡されて呼び出されたと通知される。
  2. そのサーバはサーバ自身の初期metadata(何らかのレスポンスの前に送り返されなければならない)を送り返す。もしくはクライアントのリクエストを待つ。どちらが先に起こるかはアプリケーションによる。
  3. そのサーバはクライアントのリクエストを受け取ると、レスポンスを生成するために必要なあらゆる動作をします。そのレスポンスは詳細なステータス(status codeと任意のステータスメッセージ)と任意のmetadataをともに送信されます。
  4. そのレスポンスステータスがOKなら、クライアントはクライアントサイドの呼び出しが完了したというレスポンスを得る。

Server Streaming RPC

あるserver streaming RPCはunary RPCと似ている。サーバがクライアントのリクエストに対するレスポンスとしてメッセージストリームを返すということ以外は。
サーバは全てのメッセージを送り返した後に、サーバの詳細な状態(status codeと任意のステータスメッセージ)と任意のmetadataがクライアントへ送られる。

Client Streaming RPC

あるClient streaming RPCはunary RPCと似ている、クライアントが1つのメッセージではなく複数メッセージを送るというテイン以外で。
サーバは、通常、クライアントのメッセージをすべて受信した後に、(ステータスの詳細とオプションの末尾のメタデータとともに)1つのメッセージで応答します(必ずしもそうするとは限りません)。

Bidirectional Streaming RPC

双方向Streaming PRCでは、その呼出はクライアントのメソッド呼び出しとサーバがクライアントのmetadata、メソッド名、deadlineを受け取ることで初期化される。
そのサーバはその初期メタデータを送り返すか、クライアントがメッセージをストリーミングし始めるか選ぶことができる。

クライアントとサーバサイドのストリーム処理はアプリケーションによる。2つのストリームは独立しており、クライアントとサーバはメッセージを書き込む前にクライアントの全てのメッセージを受け取るまで待つことができる、もしくはサーバとクライアントは"ping-pong"をすることができる、つまりサーバがリクエストを受け取り、レスポンスを返す時にそのクライアントはそのレスポンスをもとに別のリクエストを送ることができる。

Deadlines/Timeouts

gRPCはDEADLINE_EXCEEDEDエラーで切断されるまでRPCが完了するのをクライアントがどのくらい待つか指定することができる。
サーバサイドでは、サーバはあるRPCがタイムアウトしたか、RPCを完了するためにどのくらいの時間が残されているか問い合わせることができる。

deadline or timeoutのどちらを指定するかは実装言語による。いくつかの言語ではtimeoutが使われているし、deadlineが使われている言語もある。

RPC Termination

gRPCではクライアントとサーバの両方が独立しており、その呼出の成功の判断を各々がしている。なので、クライアントとサーバの結論が合わない可能性がある。
つまり、例えば、あなたはサーバサイドであるRPCが成功したと判断したが、クライアントサイドでは失敗したと見なす場合がある。具体例として、サーバサイドは全てのレスポンスを返したとして成功とみなしたが、クライアントはレスポンスがdeadlineまでに返ってきておらず失敗とみなした、みたいなケースがある。

Cancelling an RPC

クライアントとサーバはRPCをいつでもキャンセルできる。キャンセルはRPCを即座に停止し、それ以上動作しないようにする。
が、キャンセルされるまでに変更されたことはロールバックされないので、気をつけたし。

Metadata

metadataはkey-valueのリストの形をしたある特定のRPC呼び出しに関する情報であり、keyは文字列、valueはたいていは文字列だが、バイナリデータの場合もある。メタデータは gRPC 自体には不透明であり、クライアントがサーバに呼び出しに関連する情報を提供し、またその逆も可能です。
metadataへのアクセスは言語による。

Channels

gRPC Channelは特定のホストとポート上のgRPCサーバへコネクションを提供する。それはクライアントスタブを作る時に利用される。クライアントはgRPCのメッセージ圧縮のようなデフォルトの挙動を修正するためにchannel引数を指定できる。channelはconnectedとidleという状態を持っている
gRPCがどのようにchannelを閉じているかは言語による。

感想

Server Streamingは一回のメソッド呼び出しに付き複数のレスポンスを必要とする場合に、それら複数のリクエストを1つのまとまりとして認識し、クライアントのメソッド呼び出しが完了したメッセージをそのまとまりが全て処理された時に送り返すっていうもの(たぶん)。
双方向Streamingはアプリケーションによって実装が違うっぽい。
metadataの説明が意味分からんかった。
概念的な話はまぁなんとなく分かった。

koheiyamayamakoheiyamayama

Guides

Authentication

感想

Benchmarking

感想

Error Handling

感想

Performance Best Practices

感想

このスクラップは2022/11/14にクローズされました