🌐

gRPCについての軽い説明

2022/05/25に公開

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*の方が効率的

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