RPC

Protocol Bufferによってシリアライズしたバイナリを通信データとして用い、RPCを行う手助けとなるような仕組みもサポートされている。具体的には、serviceというキーワードを用いてprotoファイルに記述を追加すれば、RPCを行うためのメソッドのひな形や簡単なスタブクラスを自動生成することができる。

例えば、「Personを引数に渡して呼び出すと、"Hello, {Personの名前}"が戻ってくる」というようなリモートプロシージャを考えてみよう。そのために、以下のような定義をprotoファイルに追加してみる。

// RPCの戻り値となるメッセージタイプ
message HelloResponse {
  required string message = 1;
}
// RPCサービス
service HelloService {
  // リモートプロシージャ「hello」の宣言
  // Personを引数にとり、HelloResponseを戻り値とする
  rpc hello(Person) returns (HelloResponse);
}

HelloServiceがRPCの窓口となるサービスの定義であり、その本体において、rpcキーワードに続けて宣言されているのが個々のリモートプロシージャだ。

この記述を含んだprotoファイルをprotocでコンパイルすると、hello()という抽象メソッドを含んだ、「HelloService」という抽象クラスが作成される。RpcChannelというインタフェースに処理を委譲するよう実装されたスタブと、そのスタブのインスタンスを返すための「newStub()」というメソッドも、HelloServiceには宣言される。

以上がProtocol BufferによるRPC関連の処理だが、ここまでの説明を読んでも、「RPCを行うための具体的な手段についてはよくわからない」という方が多いのではないだろうか。

結局、protocは、RPCに伴う定型的なコードをほんの少し自動生成してくれるだけで、実際にオブジェクトをシリアライズし、ネットワークを介してサーバに送信し、サーバサイドで処理を行い、結果となるオブジェクトを返すといった処理は全て自前で用意しなくてはならない。Javaを用いるなら、以下のような実装を自分で行う必要があるということだ。

  • com.google.protobuf.RpcControllerの実装: RPCを途中でキャンセルしたり、結果が成功に終わったかどうかなど、単一のリモートプロシージャ呼び出しを制御するためのインタフェース
  • com.google.protobuf.RpcChannelの実装: 引数のシリアライズや戻り値のデシリアライズ、ネットワークへの送信処理など、RPCの全処理を行うためのcallMethod()というメソッドを持つインタフェース
  • サーバ側処理

こうした実装を全て行えば、例えば以下のようなコードでRPCを行うことができる。

// 引数として使用するPersonオブジェクト
Persons.Person shiraishi = ...
// ポート1050番で待ち受けているリモートプロシージャを呼び出す
HelloRpcChannel rpcChannel = new HelloRpcChannel("localhost", 1050);
// RPCコントローラの作成
RpcController controller = rpcChannel.newController();
// RPCチャネルから、サービスのスタブを生成
Persons.HelloService service = Persons.HelloService.newStub(rpcChannel);
// hello()プロシージャ呼び出し
service.hello(
  controller,
  shiraishi,
  new RpcCallback<Persons.HelloResponse>() {
    // プロシージャ呼び出しの結果を処理
    public void run(Persons.HelloResponse response) {
        System.out.println(response.getMessage());
    }
  });

なお、RPCを行うサンプルコードは、TCPソケットを直接使用した非常に低レベルなものであるため、ここでの掲載は差し控えさせていただく。興味のある方は筆者のブログを参照していただきたい。

オプション

最後に紹介する機能は、protoファイル内でオプションを指定する方法だ。protocが認識できるオプションには以下のようなものがある。

  • java_package: 生成されるソースコードのJavaパッケージを指定する。省略時はprotoファイルに指定したパッケージと同じとなる
  • java_outer_classname: 生成されるJavaソースコードの、一番外側に位置するクラスの名前を指定する。省略時は、protoファイルの名称と同じになる
  • optimize_for: SPEED(速度優先)かCODE_SIZE(コードサイズ優先)を指定することができる。省略時はCODE_SIZE。SPEEDを指定すると、生成されるC++/Javaソースコードが冗長になる代わりに、シリアライズやデシリアライズの速度が大幅に向上する。ただし公式ドキュメントには、「このオプションを有効にするのは、パフォーマンスプロファイルを行って、Protocol Bufferライブラリが処理時間に影響を与えていることがはっきりした時だけにせよ」という旨が記載されている

オプションの指定方法は、「option オプション名 = 値;」という文をprotoファイルに記述するだけだ。以下の例では、java_packageを使用してJavaパッケージの指定を行っている。

option java_package = "jp.co.mycom.journal";