Service Fabric Reliable Actors の概要

Reliable Actors は、Virtual Actor パターンに基づく Service Fabric アプリケーション フレームワークです。 Reliable Actors API は、Service Fabric による拡張性と信頼性の保証の上に構築された、シングル スレッドのプログラミング モデルを提供します。

アクターとは

アクターは、シングル スレッド実行のコンピューティングと状態の、分離されて独立したユニットです。 アクター パターンは、同時実行システムまたは分散システム用のコンピューティング モデルです。このモデルでは、これらの多数のアクターが同時に、互いに独立して実行されます。 アクターは、互いに通信することができ、さらにアクターを作成することができます。

どのようなときに Reliable Actors を使用するか

Service Fabric Reliable Actors は、アクター設計パターンの実装です。 他のソフトウェア設計パターンと同様に、特定のパターンを使用するかどうかの判断は、ソフトウェア設計の問題がパターンに適しているかどうかに基づいて行われます。

アクター設計パターンは、分散システムの多くの問題とシナリオに適していますが、パターンの制限とパターンを実装するフレームワークの制限を慎重に検討する必要があります。 一般的なガイダンスとして、次のような場合に、問題やシナリオをモデル化するためにアクター パターンを検討してください。

  • 問題空間に、状態とロジックの小さな分離されて独立したユニットが多数 (1,000 以上) 含まれている。
  • アクターのセット全体の状態に対するクエリなどの、外部コンポーネントとの重要なやり取りを必要としない、シングル スレッド オブジェクトを操作したい。
  • アクター インスタンスが、予期できない遅延がある呼び出し元を、I/O 操作を発行してブロックすることがない。

Service Fabric のアクター

Service Fabric では、アクターは Reliable Actors フレームワーク内に実装されます。これは、Service Fabric Reliable Services 上に構築される、アクターとパターンをベースにしたアプリケーション フレームワークです。 記述する各 Reliable Actors サービスは、実際にはパーティション分割されたステートフルな Reliable Service です。

各アクターは、アクター型のインスタンスとして定義されます。これは、.NET オブジェクトが .NET 型のインスタンスであるのと同様です。 たとえば、電卓の機能を実装するアクター型がある場合や、クラスター全体のさまざまなノードで分散されるその型のアクターが多数存在する場合があります。 このようなアクターはそれぞれ、アクター ID で一意に識別されます。

アクターの有効期間

Service Fabric アクターは仮想アクターです。つまり、その有効期間は、メモリ内表現に関連付けられていません。 したがって、明示的に作成したり、破棄したりする必要はありません。 Reliable Actors ランタイムは、アクター ID への要求の初回受信時に、自動的にそのアクターをアクティブ化します。 アクターが一定期間使用されていない場合、Reliable Actors ランタイムはメモリ内オブジェクトをガベージ コレクトします。 また、アクターを後で再アクティブ化する場合に備えて、アクターの存在に関する情報を保持します。 詳細については、「アクターのライフ サイクルとガベージ コレクション」を参照してください。

この仮想アクターの有効期間の抽象化によって、仮想アクター モデルの結果として、いくつかの注意事項が発生します。実際には、Reliable Actors の実装は、ときどきこのモデルから逸脱します。

  • アクターは、そのアクター ID に初めてメッセージが送信されたときに、自動的にアクティブ化されます (アクター オブジェクトが構築されます)。 しばらくしてから、アクター オブジェクトはガベージ コレクトされます。 その後、もう一度そのアクター ID を使用すると、新しいアクター オブジェクトが構築されます。 アクターの状態は、状態マネージャーに格納されると、オブジェクトの有効期間よりも長く保持されます。
  • アクター ID に対していずれかのアクター メソッドを呼び出すと、そのアクターがアクティブ化されます。 このような理由で、アクター型ではランタイムによってコンストラクターが暗黙的に呼び出されます。 そのため、サービス自体はアクターのコンストラクターにパラメーターを渡すことができても、クライアント コードはパラメーターをアクター型のコンストラクターに渡すことができません。 結果として、クライアントからの初期化パラメーターがアクターで必要な場合、他のメソッドが呼び出されるときまで、部分的に初期化された状態でアクターが構築されることがあります。 アクターのアクティブ化のための、クライアントからの単一のエントリ ポイントはありません。
  • Reliable Actors では暗黙的にアクター オブジェクトが作成されますが、ユーザーが明示的にアクターとその状態を削除できます。

分散とフェールオーバー

Service Fabric アクターは、拡張性と信頼性を実現するために、クラスター全体にアクターを分散し、障害が発生したノードのアクターを、正常に稼働しているノードに必要に応じて自動的に移行します。 これは、 パーティション分割された、ステートフルな Reliable Serviceの抽象化です。 分散、拡張性、信頼性、および自動フェールオーバーは、アクターが Actor Serviceと呼ばれるステートフルな Reliable Service 内で実行されていることによって、すべてが実現されています。

アクターは Actor Service のパーティション全体に分散され、これらのパーティションは Service Fabric クラスター内のノード全体に分散されます。 各サービス パーティションには、アクターのセットが含まれています。 Service Fabric は、分散とサービス パーティションのフェールオーバーを管理します。

たとえば、既定のアクター パーティション配置を使用して 3 つのノードに 9 つのパーティションをデプロイしているアクター サービスは、次のように分散されます。

Reliable Actors distribution

Actor Framework は、パーティション スキームとキー範囲設定を管理します。 これは、いくつかの選択を簡略化しますが、次のような考慮事項も伴います。

  • Reliable Services では、パーティション スキーム、キー範囲 (範囲パーティション スキームを使用する場合)、およびパーティション数を選択できます。 Reliable Actors は範囲パーティション スキーム (uniform Int64 スキーム) に制限され、完全な Int64 キー範囲を使用する必要があります。
  • 既定では、アクターは均等に分散するように、パーティションにランダムに配置されます。
  • アクターがランダムに配置されるため、アクターの操作には常にネットワーク通信が必要になることを想定しておかなければなりません。また、メソッド呼び出しデータのシリアル化および逆シリアル化、遅延とオーバーヘッドの発生なども考慮する必要があります。
  • 高度なシナリオでは、特定のパーティションに割り当てられる Int64 アクター ID を使用して、アクターのパーティション配置を制御できます。 ただし、そうした場合、パーティション間でアクターの分散が不均等になることがあります。

アクター サービスがパーティション分割される方法の詳細については、 アクターのパーティション分割の概念の部分を参照してください。

アクターの通信

アクター間のやり取りは、インターフェイスを実装するアクターと、同じインターフェイスを介してアクターへのプロキシを取得するクライアントとで共有されるインターフェイス内で定義されます。 このインターフェイスは、アクター メソッドを非同期で呼び出すために使用されるので、インターフェイスの各メソッドはタスクを返す必要があります。

メソッド呼び出しとその応答が、最終的にはクラスター全体でネットワーク要求になるため、引数と返されるタスクの結果の型は、プラットフォームがシリアル化できる必要があります。 具体的には、 データ コントラクト シリアル化可能でなければなりません。

アクター プロキシ

Reliable Actors API は、アクター インスタンスとアクター クライアント間の通信を提供します。 アクターと通信するために、クライアントは、アクター インターフェイスを実装するアクター プロキシ オブジェクトを作成します。 クライアントは、プロキシ オブジェクトでメソッドを呼び出すことによって、アクターと対話します。 アクター間の通信だけでなく、クライアントとアクター間の通信でもアクター プロキシを使用できます。

// Create a randomly distributed actor ID
ActorId actorId = ActorId.CreateRandom();

// This only creates a proxy object, it does not activate an actor or invoke any methods yet.
IMyActor myActor = ActorProxy.Create<IMyActor>(actorId, new Uri("fabric:/MyApp/MyActorService"));

// This will invoke a method on the actor. If an actor with the given ID does not exist, it will be activated by this method call.
await myActor.DoWorkAsync();
// Create actor ID with some name
ActorId actorId = new ActorId("Actor1");

// This only creates a proxy object, it does not activate an actor or invoke any methods yet.
MyActor myActor = ActorProxyBase.create(actorId, new URI("fabric:/MyApp/MyActorService"), MyActor.class);

// This will invoke a method on the actor. If an actor with the given ID does not exist, it will be activated by this method call.
myActor.DoWorkAsync().get();

アクター プロキシ オブジェクトの作成には、アクター ID とアプリケーション名という 2 つの情報が使用されていることに注意してください。 アクター ID は、アクターを一意に識別します。アプリケーション名は、アクターがデプロイされている Service Fabric アプリケーションを識別します。

クライアント側の ActorProxy(C#)/ActorProxyBase(Java) クラスは、必要な解決策を実行して、ID によってアクターを検索し、このアクターによって通信チャネルを開きます。 また、通信のエラーやフェールオーバーが発生した場合にアクターの検索も再試行します。 その結果、メッセージの配信には次のような特徴があります。

  • メッセージ配信は、ベスト エフォートです。
  • アクターは、同じクライアントから重複するメッセージを受け取る可能性があります。

コンカレンシー

Reliable Actors ランタイムは、アクター メソッドにアクセスするためのターンに基づくアクセス モデルを提供します。 これは、アクター オブジェクトのコード内で、ある時点でアクティブにできるスレッドが 1 つだけであることを意味します。 ターンに基づくアクセスは、データ アクセスの同期メカニズムを必要としないので、同時実行システムを大幅に簡略化できます。 また、システムの設計で、各アクター インスタンスのシングル スレッド アクセスの性質に特別な配慮が必要であることも意味しています。

  • 1 つのアクター インスタンスは、一度に複数の要求を処理することはできません。 アクター インスタンスが同時要求の処理を期待される場合は、アクター インスタンスがスループット ボトルネックになる可能性があります。
  • 2 つのアクター間に循環要求がある場合に、同時にいずれかのアクターに外部要求が行われると、互いにデッドロックすることがあります。 アクター ランタイムは、デッドロック状態を中断するために、自動的にアクター呼び出しをタイムアウトし、呼び出し元に例外をスローします。

Reliable Actors communication

ターンに基づくアクセス

ターンは、他のアクターまたはクライアントからの要求に応じたアクター メソッドの完全な実行、あるいは タイマーとアラーム のコールバックの完全な実行で構成されます。 これらのメソッドとコールバックが非同期でも、アクター ランタイムはこれらをインターリーブしません。 ターンは、新しいターンが許可される前に完全に完了する必要があります。 つまり、現在実行しているアクター メソッドまたはタイマーとアラームのコールバックは、メソッドまたはコールバックの新しい呼び出しが許可される前に完全に完了する必要があります。 実行がメソッドまたはコールバックから返され、メソッドまたはコールバックによって返されたタスクが完了した場合は、そのメソッドまたはコールバックは完了したと見なされます。 ターンごとのコンカレンシーは、メソッド、タイマーおよびコールバックが異なる場合でも優先されることに注意してください。

アクター ランタイムは、ターンの開始時にアクターごとのロックを取得し、ターンの終了時にロックを解除することで、ターンごとのコンカレンシーを強制します。 そのため、ターンごとのコンカレンシーは、アクター全体ではなく、アクターごとに強制されます。 アクターのメソッドおよびタイマーとアラームのコールバックは、別のアクターの代わりに同時に実行できます。

上記の概念を以下の例で示します。 2 つの非同期メソッド (つまり、Method1Method2)、タイマー、およびアラームを実装するアクター型があるとします。 以下の図は、このアクター型に属する 2 つのアクター (ActorId1ActorId2) の代わりにこれらのメソッドとコールバックを実行する場合のタイムラインの例を示しています。

Reliable Actors ランタイムのターンごとのコンカレンシーとアクセス

この図は、次の規則に従っています。

  • 各垂直線は、特定のアクターの代わりにメソッドまたはコールバックを実行する場合の論理フローを示しています。
  • 各垂直線上にマークされているイベントは発生順になっており、古いイベントの下に新しいイベントが続きます。
  • タイムラインでは、異なるアクターに対応する異なる色が使用されています。
  • 強調表示は、アクターごとのロックがメソッドまたはコールバックの代わりに保持される期間を示すために使用されています。

次のような、考慮すべき重要な点がいくつかあります。

  • クライアント要求 xyz789 に応じて ActorId2 の代わりに Method1 を実行しているときに、ActorId2Method1 を実行するように求める別のクライアント要求 (abc123) が到着する場合があります。 しかし、Method1 の 2 回目の実行は、前の実行が完了するまで開始されません。 同様に、Method1 がクライアント要求 xyz789 に応じて実行されている間に、ActorId2 によって登録されたアラームが開始されます。 アラームのコールバックが実行されるのは、Method1 の両方の実行が完了した場合のみです。 これはすべてターンごとのコンカレンシーが ActorId2 に対して強制されるためです。
  • 同様に、ターンごとのコンカレンシーは ActorId1 に対しても強制されます。図に示されているように、ActorId1 の代わりに Method1Method2およびタイマーのコールバックが順次実行されます。
  • ActorId1 の代理としての Method1 の実行は、ActorId2 の代理としての実行と重複しています。 これは、ターンごとのコンカレンシーが、アクター全体ではなくアクター内でのみ強制されるためです。
  • メソッドやコールバックのいくつかの実行では、メソッドやコールバックによって返される Task(C#)/CompletableFuture(Java) は、メソッドが応答した後に完了します。 その他の実行では、メソッドやコールバックが応答するまでに非同期操作は既に完了しています。 いずれの場合でも、アクターごとのロックは、メソッドとコールバックが応答し、非同期操作が完了した後にのみ解放されます。

再入

アクター ランタイムでは、既定で再入が許可されます。 つまり、アクター A のアクター メソッドがアクター B に対してメソッドを呼び出してから、アクター B がアクター A に対して別のメソッドを呼び出す場合、実行が許可されます。 これは、メソッドが同じ論理呼び出しチェーン コンテキストの一部であるためです。 すべてのタイマーとアラームの呼び出しは新しい論理呼び出しコンテキストで始まります。 詳細については、「 Reliable Actors の再入 」を参照してください。

コンカレンシーの保証の範囲

アクター ランタイムは、これらのメソッドの呼び出しを制御する状況でこのようなコンカレンシーを保証します。 たとえば、クライアント要求の受信に対する応答として行われるメソッド呼び出しおよびタイマーとアラームのコールバックに対して、このような保証を提供します。 ただし、アクター コードがアクター ランタイムによって提供されるメカニズム以外でこれらのメソッドを直接呼び出す場合、ランタイムはコンカレンシーを保証できません。 たとえば、メソッドが、アクター メソッドによって返されるタスクに関連付けられていない一部のタスクのコンテキストで呼び出される場合、ランタイムはコンカレンシーを保証することはできません。 アクターが独自に作成するスレッドからメソッドが呼び出される場合、ランタイムはコンカレンシーを保証できません。 そのため、バックグラウンド操作を実行するには、アクターは、ターンごとのコンカレンシーを優先する アクターのタイマーおよびアクターのアラーム を使用する必要があります。

次のステップ

最初の Reliable Actors サービスの開発を始めます。