協調型分散システムのための RPC 設計

協調型分散システムに必要な Peer to Peer (以下PTP) RPC サービスインフラストラクチャを構築するための通信端点の設計について。コンポーネントと役割ベースでの記述となります。プロトコルは別途。
抜けている点など多々存在していると思いますのでご意見などをコメント頂けると幸いです。

概要

SOAP, Thrift, RMI など従来の RPC の多くはクライアント-サーバ型で構成されています。これは主 (Server) の提供するサービスを従 (Client) から利用するという一方通行の利用形態です。

この構成はサーバ側からクライアントへ通知を行う必要があるようなシステム設計とはうまく適合しません。例えばサーバで行なっている非同期処理の結果通知が必要な場合、ロングポーリングのような実装で回避するか、さもなくばサーバからクライアントへ逆方向の RCP 接続を行う必要があります。

分散システムにおける PTP 型の構成ではそれぞれのノードが互いに協調してサービスを提供します。前述のような C/S 型 RPC でも 2 接続を使えば双方向のサービス提供が実現できますが、 2 ノード間で通信するだけで 2 接続を使用するのはあまりスマートではありません。この記事では 1 または限られた数の TCP/IP 接続上で互いのサービスを呼び出すことのできる RPC 設計について記述します。

機能

  • 接続中のピアが互いの提供するサービスを呼び出すことが出来る。
  • 呼び出しに対して同期または非同期を選択可能。
  • 制限された接続リソース内で並列呼び出し (呼び出しごとに接続が発生しない)。
  • 接続を開始する方向に依存しない。
  • 一つの接続上で複数の RPC を並行して実施できる。
  • 分散トランザクションについては考慮しない。

設計

コンポーネント構成

Remote Service Interface
ピアの提供するサービスをアプリケーションから呼び出すためのインターフェース。
Service Implementation
ピアに提供しているサービスの実装。
Endpoint
通信端点。ネットワークを介した通信の場合は Socket であるが、基本的に入出力ストリームのペアであれば実装に依存しない。
Dispatcher
Endpoint の入力ストリームから読み込みを行いためのスレッド。データフレーム単位で Worker に渡す。
Worker
Dispatcher が受信したデータフレームの処理を代行するスレッド。
Port
通信相手からの応答受信時に呼び出す Callback を保持する領域。TCP/IP におけるポートと似た意味。
非同期呼び出し


非同期呼び出しはサービス側の応答を待たずにアプリケーションへ処理を返す呼び出し方法です。

  1. アプリケーションはインターフェースに対してコールバックを指定して呼び出しを行う。
  2. 開いているポートIDを割り当てコールバックを設定する。
  3. 呼び出し内容とポートIDをカプセル化して相手に送信する。
  4. 呼び出し処理はここでリターンする。
  5. 通信相手からポートID付きの応答を受信する。
  6. ポートIDのコールバックを呼び出す。
同期呼び出し


同期呼び出しは非同期呼び出しの機構を応用して実現します。

  1. アプリケーションはインターフェースに対して呼び出しを行う。
  2. 開いているポートIDを割り当てコールバックを設定する。コールバックは内部で生成したものである。
  3. 呼び出し内容とポートIDを指定して相手に送信する。
  4. コールバックが呼び出されるまで待機する。
  5. 通信相手からポートID付きの応答を受信する。
  6. ポートIDのコールバックを呼び出す。
  7. コールバック時に指定された応答内容から出力を取得しリターンする。
サービス要求

  1. 通信相手からサービスの呼び出し要求を受け取った時、それをワーカーに渡す。
  2. ワーカーは渡された要求に該当する API を呼び出し結果とポートIDをカプセル化して送信する。

ポート

ポートはリモートプロシジャの応答のコールバック (あるいはそれに付随するリソースも) を保持しておく領域です。マルチスレッド環境または非同期呼び出しにおいて複数の呼び出し要求を並行的に実行できるよう、配列のような複数の領域を想定しています。

この設計では 1 回のリモート呼び出しあたり 1 ポートを消費します。1 ポートの専有期間は呼び出しを行ってから応答が返ってくるまでです。つまり、マルチスレッド環境で平行して呼び出しを行うケースではポート全体の消費割合が高くなりますし、同様に長時間かかる非同期処理を多数実行するような場合も占有率が高くなります。ポートの領域数はアプリケーション特性によって変えられるよう設計する必要があります。

空きポートの発見方法はラウンドロビンなどが良いかも知れません。最終的にすべてのポートが占有されていた場合、その処理は空きができるまで待機します。つまりポートはセマフォです。

ワーカー

サービス実装内に相手側の API を呼び出すような処理が含まれていた場合、ディスパッチャースレッドで相手の応答を待ってしまうためデッドロックが発生します。ワーカーはこの不正な挙動を防止するためのスレッドです。

1 端点のサービスが対応するのは 1 Peer のみです。Web サーバのように多数のクライアント要求を処理するわけではないため、ワーカーは基本的に 1 スレッドで十分ですが、呼び出し頻度が高くなるようなケースはスレッドプールを使用するなどの対処が必要です。

(以下書きかけ)

データフレーム

データフレームとは、通信上での支持単位です。
この設計に必須のデータフレームは以下の 2 種類です。

呼び出し
呼び出し先の API 指示子と入力パラメータをカプセル化したデータフレーム。
応答
ある呼び出しに対する出力パラメータをカプセル化したデータフレーム。

拡張として考えられるデータフレーム。

ストリーム
大きなデータを分割して送受信するためのデータフレーム。
制御
端点のクローズ手続きなどに必要であれば。

TCP/IP 接続

1 接続で十分かと思いますが、マルチスレッドで呼び出し頻度が高い場合はポート、ディスパッチャーと共に複数接続にしてもよいかと思います。

セキュリティ考察

通信相手が不正なポートに応答を返してきた場合

プロキシ、ファイアウォール等の中継

障害対応

  • 通信終了時にはポートに残って居る全てのコールバックに対してエラー通知を行います。
  • 予期しない切断は Dispatcher スレッドによって検知する事が出来ます。多分エラーハンドラーのようなコールバックをアプリケーションに提供する必要がある。

検討中

  • 非同期呼び出しのリターンを送信後にするか前にするか。
  • Gracefull な通信の開始と終了の手順。シーケンス。
  • コールバックなしの非同期呼び出しを提供するか。
  • 同期呼び出し時にタイムアウトを提供するか。