アクターの参照、パス、アドレス

アクターの参照、パス、アドレス

この章では、分散型の場合もありえるアクターシステムに、アクターがどのようのして区別され配置されるのかについて記述します。 アクターシステム が固有のスーパービジョン階層で構成されていることと、アクター間のコミュニケーションが複数のネットワークノードを跨いでも透過的であることを中心の考えとして取り掛かりましょう。

../_images/ActorPath.png

上のイメージはアクターシステム内のとても重要なエンティティ間の関係を描いています。詳細については次を読んでください。

アクターの参照とは何か?

アクターの参照とは ActorRef のサブタイプで、主要な目的はアクターへのメッセージ送信をサポートすることです。それぞれのアクターは self フィールドを通して標準的 (ローカル) な参照へのアクセスを取得します。また、この参照は送信者の参照として、他のアクターへ送信する全てのメッセージにデフォルトで含まれます。反対に言えば、メッセージの処理中にアクターは、sender メソッドを通して現在のメッセージの送信者を示す参照へのアクセスを取得します。

アクターリファレンスはアクターシステムの設定に応じてサポートされるいくつかの異なるタイプがあります。

  • 純粋なローカルアクターの参照は、ネットワーク機能のサポートを設定していないアクターシステムによって使用されます。このアクターの参照は、リモートJVMへのネットワーク接続を行う送信を行う場合は機能しません。

  • リモーティングが有効である場合、ローカルアクターの参照は、同じJVM内のアクターである参照に対してもネットワーク機能をサポートしているアクターシステムが使用されます。また、他のネットワークノードに到達可能にするため、これらの参照はプロトコルとリモートアドレス情報も含んでいます。

  • ルーターのために使用されるローカルアクターのサブタイプがあります(例えば、Router トレイトをミックスインしたアクターです)。論理的な構造は前述のローカルアクターの参照と同じですが、メッセージをルーターに送信するとルータに直接ではなく、ルーターの子のどれか一つにメッセージをディスパッチします。

  • リモートアクターの参照は、リモート通信が到達可能なアクターとして表現され、例えば、それへのメッセージの送信はメッセージを透過的にシリアライズし、リモートのJVMへ送信されるでしょう。

  • 実用的な目的のため、ローカルアクターの参照のように振る舞う特別なアクターの参照がいくつかあります。

    • PromiseActorRef はアクターからの応答によって完了する意図を表す Promise の特別な表現です。akka.pattern.ask がこのアクターの参照を生成します。

    • DeadLetterActorRef は デッドレターサービスのデフォルトの実装で、Akka は、シャットダウンした、または存在しない送信先へメッセージを全てここに送ります。

    • EmptyLocalActorRef は、存在しないアクターパスを探した場合に Akka が返すものです。これは DeadLetterActorRef と同等のようですが、パスを保持しているため、Akka はネットワークを超えて送信することや、存在している他のアクターとパスを比較することができます。これらの内の幾つかはアクターが死ぬ前に獲得されているかもしれません。

  • それから、実際に見るべきではない一度限りの内部実装も幾つかあります。

    • アクターとして表現されず、ルートガーディアンに対する擬似スーパバイザーとしてだけ機能するアクターの参照があり、我々はこれを "時空の泡を歩く者" と呼んでします。

    • 一番最初のロギングサービスは、アクターの動作環境の生成が実際に稼働するよりも前に開始し、ログイベントを受け付け内容を直接標準出力に出力する偽のアクターの参照になります。これは、 Logging.StandardOutLogger です。

アクターパスとは何か?

アクターは厳密に階層構造のように作成されるため、アクターシステムのルートから下っていく親と子の間のスーパービジョンの連結を再帰的にたどる事で与えられるアクターの名前には一意に順序付けられています。

アクターパスは、アクターシステムを区別し、ルートガーディアンから対象のアクターへのパス要素が連結されているアンカーで構成されていいます。パス要素は途中にあるアクターの名前で、スラッシュで区切られています。

アクターの参照とパスの違いは何か?

アクターの参照は、単一のアクターを指し示し、そのライフサイクルはアクターのライフサイクルと一致します。アクターパスは、存在しているかどうかわからないアクターの名前として表現され、ライフサイクルを持たず、決して無効にはなりません。アクターパスはアクターを生成しなくても作ることができますが、アクターの参照は対応するアクターを生成しなければ作ることはできません。

アクターを生成したあと、それを終了し、同じアクターパスで新しいアクターを再度生成できます。この新しく生成されたアクターは、アクターの新しい実体です。これは以前と同じアクターではありません。古い実体へのアクターの参照は、新しい実体に対しては無効です。古いアクターの参照へ送信したメッセージは、たとえ同じパスであったとしても、新しい実体のほうへ届くことはないでしょう。

アクターパスのアンカー

それぞれのアクターパスはアドレス構成になっていて、プロトコルと、ルートを頂点に対象のアクターまでの間にある各階層のアクターの名前が続く形式になった場所が記述されています。以下が例です。

"akka://my-sys/user/service-a/worker1"                   // purely local
"akka.tcp://my-sys@host.example.com:5678/user/service-b" // remote

ここの、 akka.tcp は 2.4 リリースでデフォルトのリモート通信方法です。他の方法に差し替えもできます。ホストとポートの部分に表示される内容( 例では、host.example.com:5678 となっている箇所) は、使用する通信方法によって異なりますが、 URL 構造のルールの守るようになっています。

論理的なアクターパス

親のスーパービジョンのリンクをルートガーディアンまで辿って得られた一意のパスは、論理的なアクターパスと呼ばれます。このパスはアクターを生成してきた先祖の家系と確実に一致するので、アクターシステムのリモート構成(パスのアドレス構成も含まれる)が設定してあると、完全な決定性を持ちます。

物理的なアクターパス

論理的なアクターパスは1つのアクターシステム内で正常に機能する位置を表していますが、一方で、設定に基づくリモートデプロイでは、異なるアクターシステムのような別のネットワークホストにある親からアクターが生成される可能性があることを意味しています。この場合、ルートガディアンまで辿るアクターパスでは、ネットワークを横断する必要があり、コストがかかる操作になります。しかしながら、それぞれのアクターは物理的なパスも保有していて、そのパスはアクターが実際に所属しているアクターシステムのルートガーディアンから始まっています。他のアクターを照会する際に、このパスを送信者の参照として使うと、ルーティングの遅延を最小限に抑えた上で、直接応答ができるようになります。

物理的なアクターパスは決して複数のJVMやアクターシステムにまたがることは決してないという重要な一面があります。これは、先祖のどこかでリモートのスーパーバイザーを使っている場合は、アクターの論理的なパス(スーパービジョンの階層)と、物理的なパス(アクターの配置) が乖離するかもしれないということを意味しています。

どうやってアクターの参照を取得するか?

アクターの参照を取得する方法は、一般的に 2種類あります。アクターを生成することか、アクターを探すことかのどちらかで、後者の機能には具体的なアクターパスからアクターリファレンスを生成することと、論理的なアクターの階層を照会することの 2つ方法があります。

アクターの生成

アクターシステムは通常、 ActorSystem.actorOf を使用して、ガーディアンアクター配下のアクターを生成することから始まり、それから生成したアクターに含まれる ActorContext.actorOf を使って、 アクターのツリーを広げていきます。これらのメソッドは、新しく生成されたアクターの参照を返します。各アクターは、親、自分自身、子供に対して( ActorContext を通して) 直接アクセスできます。これらの参照は他のアクターへメッセージを送信し、直接返信を受け取ることができるかもしれません。

具体的なパスでアクターを探す

さらに、アクターの参照は、 ActorSystem.actorSelection メソッドでも検索できます。 セレクションは前述のアクターとのコミニュケーションに使用可能で、セレクションにメッセージが届いた時は、セレクションに対応するアクターが照会されます。

特定のアクターのライフサイクルが束縛された ActorRef を取得するためには、組み込みの Identify メッセージのようなメッセージをアクター送る必要があり、 sender() を使って応答したアクターの参照を取得します。

絶対パス 対 相対パス

ActorSystem.actorSelection に加えて、 ActorContext.actorSelection も あらゆるアクターの中で、 context.actorSelection として利用できます。これは、 ActorSystem のものと双子のように似ていて、アクターセレクションを生成できますが、パスの照会を、アクターのツリーのルートから開始する代わりに、現在のアクターの位置から開始します。 2つのドット ("..") で構成されるパス要素は、親のアクターにアクセスするために使います。次の例では、特定の兄弟にメッセージを送っています。

context.actorSelection("../brother") ! msg

もちろん絶対パスによる照会も、 context では普通の方法として使えます。

context.actorSelection("/user/serviceA") ! msg

予期したとおりに動くでしょう

論理的のアクター階層への照会

アクターシステムは階層のようなファイルシステムを形成しているので、Unix シェルでサポートされている方法と同じようなパスのマッチングが可能です。0から複数の実際のアクターをまとめるために、パス要素 (一部でも可) を、ワイルドカード ( «*»«?» ) で置き換えることができます。この結果は単一のアクターの参照ではないので、 ActorSelection とは異なる型を持ち、 ActorRef が持つ操作の全てをサポートしていません。セレクションは ActorSystem.actorSelectionActorContext.actorSelection メソッドを使ってまとめることができ、メッセージの送信をサポートします。

context.actorSelection("../*") ! msg

msg は現在のアクターも含むすべての兄弟に送信されるでしょう。 actorSelection を使って取得した参照について言えば、メッセージ送信を実行するためにスーパービジョン階層の横断が行われます。メッセージが受信者へ届けられる間でさえもセレクションにマッチしたアクターの確実な集合は変化するため、死活状態の変化をセレクションで監視することは不可能です。それをするためには、リクエストを送信してすべての応答を収集することで不確実性を解決し、送信者の参照を取り出し、そして発見したすべての具体的なアクターを監視します。このセレクションを解決する仕組みは、将来のリリースで改善されるかもしれません。

要約: actorOfactorSelection

注釈

上のセクションで幾つか詳細に解説したことは、以下のとおり簡単に要約できて、記録できます。

  • actorOf は新しいアクターを作る用途で使います。そして作成されたアクターはメソッドを実行したコンテキスト(あらゆるアクターや、アクターシステム)の直接の子供になります。

  • actorSelection はメッセージを配信するときに、既存のアクターを検索する用途で使います。よって、アクターを生成しません。あるいは、セレクションを生成してアクターの存在の検証目的でも使用します。

アクターの参照と パス等価性

ActorRef の等価性は ActorRef が対応する対象のアクターの実体そのものと目的が一致します。2つのアクターの参照が、同じパスを持ち同じアクターの実体を指している時、等価であると比較されます。終了したアクターを指している参照は、同じパスである別の(再生成した)アクターを比較しても等価ではありません。障害によるアクターのリスタートは、まだ同じアクターの実体であることに注意してください。つまり、リスタートは、 ActorRef の利用者に対しては 不可視となります。

もしコレクションのアクターの参照を記録する必要があり、正確なアクターの実体について気にする必要がないなら、キーとして ActorPath が使えます。その理由は、アクターパスを比較する際は、対象のアクターの識別子を考慮しないためです。

アクターパスの再利用

アクターが終了した時、その参照はデッドレターのメールボックスを指し、 DeathWatch は最後の状態変化を発行します。そして一般的に、死んだアクターが復活して戻ることは予期しないことでしょう(アクターのライフサイクルではこれは許容していません)。しばらくした後で同じパスのアクターを作ることは可能ですが、 - 今までに生成した利用可能なアクターの集合を保持することなしで反対を実施することは単に不可能であるため - これは良い習慣ではありません。 actorSelection で “死んだ” アクターに送ったメッセージは突然再び動き始めますが、この状態変化とあらゆる他のイベントとの間に、何の順序保証もありません。そのため、パスの新しい所有者は以前の所有者に宛てたメッセージを受け取るかもしれません。

それは非常に限られた状況では正しいことかもしれませんが、アクターのスーパーバイザーが操作を適切に制限していることを確認しておきます。その理由は、スーパバイザーが新しい子のアクターの生成が失敗する前に、その名前の適切に登録解除されることを発見できる唯一のアクターであるためです。

また、テスト対象が特定のパスでインスタンス化することに依存するとき、テスト中でも必要になります。この場合では、スーパーバイザーをモック化することがベストなので、テストの手続きの適切なポイントで、 Terminated メッセージを送り、名前の適切な登録解除を待機可能にします。

リモートデプロイ使用時の内部動作

アクターが子を生成するとき、アクターシステムのデプロイヤーは新しいアクターを同じ JVM に生成するか別のノード上に生成するかを決定します。後者のケースでは、アクターの生成はネットワーク接続経由で異なる JVM 、したがって異なるアクターシステムの中で起こされます。リモートシステムは新しいアクターを、この目的のために予約された特別なパスの配下に置き、新しいアクターのスーパーバイザーはリモートアクターの参照(生成を起こしたアクターとして扱う)になります。この場合、 context.parent (スーパーバイザーの参照) と context.path.parent (アクターのパスの親のノード) は同じアクターとして表現されません。しかしながら、スーパーバイザー内部にある子供の名前の検索は、リモートノードの中から発見し、論理的な構造を、例えば未解決のアクターの参照に送信するときなどに、保存します。

../_images/RemoteDeployment.png

アドレス部の用途は何か?

ネットワークを横断してアクターの参照を送信するとき、それはそのパスによって表現されます。そのため、パスは配下のアクターにメッセージを送信するのに必要なすべての情報を完全にエンコードしなくてはいけません。これはエンコーディングプロトコル、パス文字列のアドレス部のホストとポートによって成り立ちます。リモートノードからアクターシステムがアクターパスを受け取った時、パスのアドレスがアクターシステムのアドレスと一致するかチェックし、一致する場合アクターのローカル参照として解決します。そうでない場合、リモートアクターの参照として扱います。

アクターパスのトップレベルスコープ

パス階層のルートにはすべてのアクターの上位にいるルートガーディアンがいることがわかります。その名前は "/"' です。次のレベルは以下にあるもので構成されます。

  • "/user" はユーザーが作成するすべてのトップレベルアクターに対するガーディアンアクターで、ActorSystem.actorOf を使って作成したアクターは、このアクターの配下で見つかります。

  • "/system" は システムが作成したすべてのトップレベルアクターのガーディアンアクターです。たとえば、ロギングリスナーや、アクターシステムの開始時に設定によって自動的にデプロイされたアクターなどです。

  • "/deadLetters" はデッドレターアクターで、停止状態や存在しないアクターへ送信した全てのメッセージがここに再送されます(ただしベストエフォートベースであり、ローカル JVM 内であってもメッセージは失われるかもしれません)。

  • "/temp" は短命のシステムが作成したすべてのアクターに対するガーディアンです。例えば、 ActorRef.ask の実装などに使われます。

  • "/remote" はスーパーバイザーがリモートアクターの参照に所属するアクターの人工のパスです。

このようなアクターのネームスペース構造の必要性は、集中と非常に簡単な設計目標から生じています。全ての階層はアクターであり、全てのアクターの機能も同じ方法です。したがって、自分で作成したアクターを探すだけでなく、システムガーディアンも探すことができ、またメッセージを送ることもできます(この場合、メッセージをルールに従って破棄するでしょう)。このパワフルな法則は、思えることが苦ではないことを意味し、システム全体をより一体感と一貫性があるものにします。

もしアクターシステムのトップレベルの構造についてより知りたいなら、 トップレベルのスーパーバイザー を見てください。

Contents