RPCとは何か
RPC(Remote Procedure Call)は、ネットワークを通じて遠隔地のコンピュータプログラムからサービスを要求するためのプロトコルです。低レベルのネットワーク技術を意識することなく利用できます。RPCプロトコルはTCPやUDPなどの伝送プロトコルの存在を前提としており、通信プログラム間で情報を伝達します。OSI通信モデルにおいて、RPCは伝送層とアプリケーション層にまたがる機能を持ちます。
RPCはクライアント/サーバーモデルを採用しています。クライアントが呼び出しリクエストをサーバーに送信し、サーバーがパラメータを受け取って処理を行い、結果をクライアントに返却します。この仕組みにより、分散システムの開発が容易になります。
なぜRPCが必要なのか
アプリケーション開発が進展するにつれて、単一のアプリケーションでは対応しきれない状況が生まれます。業務が複雑化し、トラフィックが増えた際には、システムを複数の独立したアプリケーションに分割することが一般的です。しかし、共通のビジネスロジックが存在する場合、それらを独立したサービスとして切り出すことで、再利用性と保守性が向上します。このような場面で、効率的なアプリケーション間通信手段としてRPCが活躍します。これはまさにサービス指向アーキテクチャ(SOA)やマイクロサービスアーキテクチャの基盤となる考え方です。
RPCの動作原理と構成要素
Nelsonの論文によると、RPCの実装には以下の5つの要素があります:
- User(クライアント)
- User-stub(クライアント側スタブ)
- RPCRuntime(RPCランタイム)
- Server-stub(サーバー側スタブ)
- Server(サーバー)
クライアントはUser-stubを介してリモート呼び出しを行います。User-stubは呼び出し情報をエンコードし、RPCRuntimeを通じてサーバーへ転送します。サーバー側ではServer-stubがデコードを行い、ローカルの処理を実行し、結果をクライアントへ返します。
RPCフレームワークの主要コンポーネント
- RpcServer: リモートインターフェースの公開
- RpcClient: リモートインターフェースのインポート
- RpcProxy: インターフェースのプロキシ実装
- RpcInvoker: 呼び出し情報のエンコード・デコードと実行
- RpcProtocol: プロトコルのエンコード・デコード
- RpcConnector: クライアントとサーバー間の接続維持
- RpcAcceptor: クライアントからのリクエスト受信
- RpcProcessor: 呼び出しプロセスの制御
- RpcChannel: データ伝送チャネル
代表的なJava RPCフレームワーク
- Thrift: 複数言語に対応したサービス開発フレームワーク。C++、Java、Pythonなど多くの言語をサポート。
- Dubbo: アリババが開発した分散サービスフレームワーク。高性能なNIO通信、動的サービスディスカバリ、負荷分散などを提供。
- Spring Cloud: マイクロサービス向けツール群。設定管理、サービスディスカバリ、サーキットブレーカーなどの機能を含む。
RPCとメッセージキューの比較
| 比較項目 | RPC | メッセージキュー |
|---|---|---|
| 同期性 | 同期呼び出しが可能 | 非同期単方向通信 |
| リアルタイム性 | 即時応答 | 遅延処理可能 |
| 信頼性 | 直接通信 | 中間ノード依存 |
| 適用場景 | 同期応答が必要な処理 | バッチ処理、負荷分散 |
RPCフレームワークの核心技術
1. サービスの公開と取得
リモートサービスの利用には、インターフェース定義やデータ構造などの情報を共有する必要があります。ThriftのIDLファイルやWebサービスのWSDLファイルなどがこれに該当します。クロスプラットフォーム対応ではコードジェネレータによるスタブ生成が一般的ですが、同じ言語内では実行時コード生成も可能です。
Javaにおけるポリモーフィズム(多態性)の場合は、どの実装クラスを呼び出すかを明示的に指定する必要があります。例えば、同じインターフェースに複数の実装がある場合、エクスポート時に識別子を付与し、呼び出し時にその識別子を渡すことで適切な実装を選択します。
2. リモートプロキシオブジェクト
クライアントが使用するのは実際にはローカルのプロキシオブジェクトです。JavaではJDK動的プロキシまたはバイトコード生成技術を使用して実現します。動的プロキシは使いやすいもののパフォーマンスは劣り、バイトコード生成は効率的だが可読性に欠けるというトレードオフがあります。
3. 通信機構
RPCはHTTPやTCPなどのプロトコル上で動作します。WebサービスはHTTPベースのRPCですが、TCPベースの方がパフォーマンスが優れています。
- TCP vs HTTP: TCPは伝送層プロトコル、HTTPはアプリケーション層プロトコルであり、より低レベルのTCPの方が高速。
- メッセージID: 各メッセージにユニークIDを割り当てることで、コネクションの再利用が容易になる。
- IO方式: 高並列処理にはブロッキングIOではなくNIO(非同期IO)を使用。
- マルチコネクション: 大容量データ転送時は複数コネクションによるバッファ分散が効果的。
- ハートビート: 負荷分散装置経由の場合、定期的にハートビート信号を送信して接続を維持。
4. シリアライゼーション
RPCのパフォーマンスに影響を与える主な要素は伝送方式とシリアライゼーションです。
シリアライゼーション方式
Java標準のシリアライゼーションは高負荷時にボトルネックとなるため、Protobuf、Kryo、Hessian、Jacksonなどの代替技術が利用されます。
エンコード内容
-- 呼び出し情報 -- 1. インターフェースメソッド(名前、パラメータ) 2. メソッド引数(型、値) 3. 呼び出し属性(タイムアウト時間、隠しパラメータ) -- 戻り情報 -- 1. 戻り値 2. ステータスコード 3. 例外情報
プロトコルメッセージ設計例
-- メッセージヘッダー -- magic : プロトコル識別子 header size : ヘッダーサイズ version : プロトコルバージョン st : シリアライズ種別 hb : ハートビートフラグ rp : レスポンスフラグ status code : ステータスコード message id : メッセージID body size : ボディサイズ -- メッセージボディ -- xml/json/binaryなど任意の形式でシリアライズ