gRPCについての軽い説明
gRPCとは
gRPCはGoogleが開発したRPC実装です。
RPCとはRemote Procedure Callの略で日本語では遠隔手続呼び出しと呼ばれます。
平たくいうと、ネットワーク通信を介して別のコンピュータの関数を呼べる仕組みをRPCといいます。
gRPCの特徴としては以下が挙げられます。
- XMLよりパースが高速なProtocol Buffersを使っている
- インターフェースの定義と実際のプログラムを分離できる
- 多くの言語に対応している
- 通信が原則HTTP/2
本記事ではgRPCとは何かをさらっと理解してもらうことを目的に短めに記述します。
環境の整え方やprotocの使い方等については書いてないので、それを調べている方は他の記事を参照してください。
Protocol Buffers
gRPCを語る上で絶対に外せないものがProtocol Buffers (以下protobuf) です。
protobufはバイナリベースのシリアライズフォーマットで、シリアライズした結果は人間には読みづらい形になります (ここは全部テキストで見やすいJSONとは大きく異なる所です)。
protobufはIDL (インターフェース定義言語) でデータ構造などを定義します。
定義した構造を実際のプログラムとして生成するにはprotoc
というコマンドを用います。
message GreetData {
string my_name = 1;
}
protobufのデータ構造はmessage
で記述します (C言語とかで言うstruct
的な感じ)。
message
の中には内包するデータを一つ一つ記載していきます。
上記の例ではstring
型のmy_name
という名前のフィールドを定義しています。
ここで気になるのが最後についている = 1
ですね。
これはタグと呼ばれる機能でシリアライズされた後のデータでフィールドを区別するために用いられます。
同じ関数だけどフィールドが変わった場合とかに、既存のフィールド番号は使わないで新しい番号で定義することで、古いデータ型との整合性を保つことができます。
このタグ番号は1以上の数値を振ります。
1-15は1バイトでタグ番号とデータ型を保存するため頻繁に利用されるフィールドに使用することが推奨されています。
16-2047は2バイト必要です。
フィールドは後述するrepeated以外は0個か1個の値を持つことができます。
なのでprotobufは一つのフィールドに複数の値を登録できません。
スカラー型
データの型は次のものが使えます。
型名 | 対応するC++の型 | 対応するJavaの型 |
---|---|---|
double |
double |
double |
float |
float |
float |
int32 |
std::int32_t |
int |
int64 |
std::int64_t |
long |
uint32 |
std::uint32_t |
int |
uint64 |
std::uint64_t |
long |
sint32 |
std::int32_t |
int |
sint64 |
std::int64_t |
long |
fixed32 |
std::uint32_t |
int |
fixed64 |
std::uint64_t |
long |
sfixed32 |
std::int32_t |
int |
sfixed64 |
std::int64_t |
long |
bool |
bool |
boolean |
string |
std::string |
String |
bytes |
std::string |
ByteString |
-
int*
とuint*
は名前の通り符号付きか符号なしかが異なる -
int*
とsint*
はsint*
の方が負の値の扱いが得意- なので負の値が含まれる可能性が高い場合は
sint*
を推奨
- なので負の値が含まれる可能性が高い場合は
-
*int*
と*fixed*
は最初から指定したバイト数分だけ領域が確保されている- 2^28とか2^56以上の大きな値を使う場合には
*fixed*
の方が効率的
- 2^28とか2^56以上の大きな値を使う場合には
repeated
repeatedは配列のようなデータを定義できます。
repeatedは0以上のデータを含むことができます。
値の順序は保たれます。
message RepeatedTest {
repeated string users = 1;
}
enum
protobufでは列挙型を定義できます。
enum Status {
UNKNOWN = 0;
OK = 1;
NOT_FOUND_ERROR = 2;
ALREADY_EXISTS = 3;
}
enumでは最初の値は必ず0でなければならず、値が0の要素が必ず必要です。
map
mapは連想配列です。
message MapTest {
map<string, int32> map = 1;
}
mapはrepeatedにすることができません。
また、mapに格納したデータが格納した順序通りに並んでいることは保証されません。
oneof
oneofは複数あるデータのうちどれか一つのみ設定されるようにする型です。
意味合い的にはCのunionに近いかもしれません。
message OneofTest {
string name = 1;
oneof test_oneof {
string uuid = 2;
uint64 numeric_id = 3;
}
}
oneofの中ではrepeatedは使えません。
proto2とproto3
protobufもPythonよろしく2系と3系で言語仕様が異なります。
基本的にはproto3を使えば良いですが、特に指定しない場合はproto2扱いになると思われるので最初に以下を記述します。
syntax = "proto3";
gRPC
gRPCは通信するデータにprotobufを使うRPCです。
protobuf自体はmessage
の定義だけでなくservice
の定義もできます。
service GreetService {
rpc Greet(GreetRequest) returns (GreetResponse);
}
service
は中にrpc
という項目を持ちます。
これはRPCの関数一つに対応するもので、gRPCクライアントが呼び出せる部分で、gRPCサーバーでは実際の処理を実装する部分です。
()
の中には引数の型を記述します。引数はただ一つ必要です。複数渡すことも一つも渡さないこともできません。
returns ()
の部分はRPCの返り値の型を記述します。
stream
gRPCは引数と返り値の型にstream
をつけることができます。
stream
は一回のリクエストに複数のデータを送ったり、複数のデータを返したりすることができる機能です。
service StreamService {
rpc Unary(UReq) returns (URes); // 1
rpc ServerStreaming(SSReq) returns (stream SSRes); // 2
rpc ClientStreaming(stream CSReq) returns (CSRes); // 3
rpc BidirectionalStreaming(stream BSReq) returns (stream BSRes); // 4
}
stream
をつける場所によってUnary RPC、Server Streaming RPC、Client Streaming RPC、Bidirectional Streaming RPCに分けられます。
Unary RPCはstream
を全く使わないで作られたRPCです (1)。
つまり、リクエスト一つに対してレスポンス一つが返ってきます。
Server Streaming RPCはreturns
の方にstream
をつけたRPCです (2)。
これは一つのリクエストに対して複数のレスポンスが返ってきます。
対してClient Streaming RPCは引数にstream
がつきます (3)。
これはリクエストを複数送りレスポンスが一つのパターンです。
最後がどちらにもstream
がついたBidirectional Streaming RPCです (4)。
これは予想通りどちらも複数回メッセージを送ることができる形式です。
Discussion