web-dev-qa-db-ja.com

クラスター内のAkkaアクターの発見

最近、Akkaとアクターベースのシステムの概念に頭を悩ませようとしています。私は今までにAkkaの基本をかなりよく理解していますが、クラスタリングとリモートアクターに関してはまだいくつかのことに苦労しています。

Play Framework 2.0に付属のWebSocketチャットの例 を使用して問題を説明しようとします。WebSocketを保持し、現在接続されているユーザーのリストを保持するアクターがいます。アクターは基本的に、技術的にも論理的にもチャットルームを表します。単一のサーバーで単一のチャットルームが実行されている限り、これは完全に正常に機能します。

サーバーのクラスター(単一ノードが追加または削除されている)で実行されている多くの動的チャットルーム(新しい部屋はいつでも開閉できます)について話しているときに、この例をどのように拡張する必要があるかを理解しようとしています。現在の需要に応じて)。このような場合、ユーザーAはサーバー1に接続し、ユーザーBはサーバー2に接続できます。両方が同じチャットルームで話している可能性があります。各サーバーには、適切なユーザーにイベント(メッセージ)を受信して​​公開するためのWebSocketインスタンスを保持するアクター(チャットルームごとに?)が引き続き存在します。ただし、論理的には、現在接続されているユーザー(または同様のタスク)のリストを保持するチャットルームアクターは、サーバー1またはサーバー2のいずれかに1つだけ存在する必要があります。

ZeroMQやRabbitMQのようなメッセージングシステムを追加せずに、できれば「純粋なakka」でこれをどのように達成しますか?

これは私がこれまでに思いついたものです。これが意味があるかどうかを教えてください。

  1. ユーザーAがサーバー1に接続し、WebSocketを保持するアクターが割り当てられます。
  2. アクターは、アクティブなチャットルームの「チャットルームアクター」が接続されたクラスターノードのいずれかに存在するかどうかを(ルーター?EventBus?何か他のものを使用して)チェックします。そうではないので、何らかの方法で新しいチャットルームアクターの作成を要求し、このアクターとの間で今後のチャットメッセージを送受信します。
  3. ユーザーBはサーバー2に接続し、アクターは彼のWebSocketにも割り当てられます。
  4. また、要求されたチャットルームのアクターがどこかに存在するかどうかを確認し、サーバー1で見つけます。
  5. サーバー1のチャットルームアクターは、特定のチャットルームのハブとして機能し、「接続された」すべてのチャットメンバーアクターにメッセージを送信し、着信アクターを配信します。

サーバー2がダウンした場合、チャットルームアクターをサーバー2で再作成するか、サーバー2に移動する必要がありますが、これは現在の私の主な関心事ではありません。アクターのこの動的な発見が、Akkaのツールセットを使用して、基本的に独立したさまざまなマシンにどのように広がるのか、最も疑問に思っています。

私はかなり前からAkkaのドキュメントを見てきました。そのため、ここで明らかなことを見逃しているのかもしれません。もしそうなら、私を啓発してください:-)

43
Lunikon

私は基本的にチャットルームの例の非常に拡張されたバージョンであるプライベートプロジェクトに取り組んでおり、akkaと全体の「分散型」思考に関するスタートアップの問題もありました。だから私は私の拡張チャットルームをどのように「解決」したかをあなたに伝えることができます:

追加の構成をあまり必要とせずに、複数回簡単に展開できるサーバーが必要でした。私は、すべてのオープンユーザーセッション(ActorRefの単純なシリアル化)およびすべてのチャットルームのストレージとしてredisを使用しています。

サーバーには次のアクターがあります。

  • WebsocketSession:1人のユーザーへの接続を保持し、ユーザーからの要求を処理し、システムからのメッセージを転送します。
  • ChatroomManager:これは中央ブロードキャスターであり、サーバーのすべてのインスタンスにデプロイされます。ユーザーがチャットルームにメッセージを送信したい場合、WebSocketSession-Actorはすべての情報をChatroomManager-Actorに送信し、ChatroomManager-Actorはメッセージをチャットルームのすべてのメンバーにブロードキャストします。

だからここに私の手順があります:

  1. ユーザーAは、新しいWebsocketSessionを割り当てるサーバー1に接続します。このアクターは、このアクターへの絶対パスをredisに挿入します。
  2. ユーザーAはチャットルームXに参加し、チャットルームXは絶対パス(これをユーザーセッションの一意のIDとして使用します)をredisに挿入します(各チャットルームには「接続」セットがあります)
  3. ユーザーBがサーバー2-> redisに接続します
  4. ユーザーBがチャットルームXに参加-> redis
  5. ユーザーBは、次のようにチャットルームXにメッセージを送信します。ユーザーBは、Websocketを介してセッションアクターにメッセージを送信します。セッションアクターは、(いくつかのチェックの後)アクターメッセージをチャットルームマネージャーに送信します。このアクターは、実際にredis(akkaのactorFor-メソッドで使用される絶対パス)からチャットルームのユーザーリストを取得し、各セッションアクターにメッセージを送信します。次に、これらのセッションアクターはWebSocketに書き込みます。

各ChatroomManager-actorで、ActorRefキャッシュを実行して、速度を向上させました。これはあなたのアプローチとは異なると思います。特に、これらのチャットルームマネージャーはすべてのチャットルームのリクエストを処理します。しかし、1つのチャットルームに1人の俳優がいることは、私が避けたかった単一障害点です。さらに、これにより、さらに多くのメッセージが発生します。例:

  • ユーザーAとユーザーBはサーバー1にいます。
  • チャットルームXはサーバー2にあります。

ユーザーAがユーザーBと会話したい場合、両方ともサーバー1のチャットルームアクターを介して通信する必要があります。

さらに、(ラウンドロビン)ルーターなどのakkaの機能を使用して、各システムにChatroomManager-actorの複数のインスタンスを作成し、多くのリクエストを処理しました。

シリアル化とredisを組み合わせて、akkaリモートインフラストラクチャ全体をセットアップすることに数日を費やしています。しかし、今では、redisを使用してActorRefs(ip + portで絶対パスとしてシリアル化)を共有するサーバーアプリケーションのインスタンスをいくつでも作成できます。

これはあなたをもう少し助けるかもしれません、そして私は新しい質問を開いています(私の英語についてはしないでください;)。

12
th3hamm0r

複数のマシン間でスケールアウトするための鍵は、可変状態を可能な限り分離しておくことです。分散キャッシュを使用してすべてのノード間で状態を調整できますが、これにより、多数のノードにスケールアウトするときに同期とボトルネックの問題が発生します。理想的には、チャットルームのメッセージと参加者について知っている1人の俳優がいる必要があります。

問題の核心は、チャットルームが単一のマシンで実行されている単一のアクターによって表されているかどうか、または実際にそのような部屋が存在するかどうかです。秘訣は、チャットルームの名前などの識別子を使用して、特定のチャットルームに関連するリクエストをルーティングすることです。名前のハッシュを計算し、数に応じて、n個のボックスから1つを選択します。ノードは現在のチャットルームを認識し、適切なチャットルームアクターを安全に検索または作成できます。

Akkaでのクラスタリングとスケールアウトについて説明している次のブログ投稿をご覧ください。

http://blog.softmemes.com/2012/06/16/clustered-akka-building-akka-2-2-today-part-1/

http://blog.softmemes.com/2012/06/16/clustered-akka-building-akka-2-2-today-part-2/

10
SoftMemes

Zookeeper + Norbertを使用して、どのホストが上下しているかを確認します。

http://www.ibm.com/developerworks/library/j-zookeeper/

これで、チャットルームサーバーファーム内のすべてのノードが論理クラスター内のすべてのホストを認識できるようになりました。ノードがオフラインになる(またはオンラインになる)と、コールバックが返されます。すべてのノードは、現在のクラスターメンバーの並べ替えられたリストを保持し、チャットルームIDをハッシュし、リストサイズで変更して、特定のチャットルームをホストする必要があるノードであるリスト内のインデックスを取得できるようになりました。 1を追加して再ハッシュし、2番目のインデックスを選択して(新しいインデックスを取得するまでループが必要)、冗長性のためにチャットルームの2番目のコピーを保持する2番目のホストを計算できます。 2つのチャットルームホストのそれぞれに、すべてのチャットメッセージをチャットルームメンバーである各Websocketアクターに転送するチャットルームアクターがあります。

これで、カスタムAkkaルーターを使用して両方のアクティブなチャットルームアクターを介してチャットメッセージを送信できます。クライアントはメッセージを1回送信するだけで、ルーターはハッシュmodを実行し、2人のリモートチャットルームアクターに送信します。 Twitterスノーフレークアルゴリズムを使用して、送信されるメッセージの一意の64ビットIDを生成します。次のリンクにあるコードのnextId()メソッドのアルゴリズムを参照してください。 datacenterIdとworkerIdは、norbertプロパティを使用して設定し、異なるサーバーで衝突するIDが生成されないようにすることができます。

https://github.com/Twitter/snowflake/blob/master/src/main/scala/com/Twitter/service/snowflake/IdWorker.scala

これで、すべてのメッセージの2つのコピーが、2つのアクティブなチャットルームアクターのそれぞれを介して各クライアントエンドポイントに送信されます。各Websocketクライアントアクターで、スノーフレークIDのビットマスクを解除して、メッセージを送信するdatacenterId + workerId番号を学習し、クラスター内の各ホストから見た最大のチャットメッセージ番号を追跡します。次に、特定の送信者ホストの特定のクライアントですでに表示されているメッセージよりも高くないメッセージは無視します。これにより、2人のアクティブなチャットルームアクターを介して着信するメッセージのペアが重複排除されます。

ここまでは順調ですね;いずれかのノードが停止しても、残っているチャットルームのコピーが1つ失われることはないという点で、回復力のあるメッセージングが可能になります。メッセージは、2番目のチャットルームを介して自動的に中断されることなく流れます。

次に、クラスターからドロップアウトするノード、またはクラスターに追加されるノードを処理する必要があります。各ノード内でnorbertコールバックを取得して、クラスターメンバーシップの変更について通知します。このコールバックでは、カスタムルーターを介して、新しいメンバーシップリストと現在のホスト名を示すakkaメッセージを送信できます。現在のホスト上のカスタムルーターはそのメッセージを確認し、その状態を更新して新しいクラスターメンバーシップを認識し、特定のチャットルームトラフィックを送信する新しいノードのペアを計算します。新しいクラスターメンバーシップのこの確認応答は、ルーターによってすべてのノードに送信されるため、すべてのサーバーがメンバーシップの変更に追いつき、メッセージを正しく送信していることをすべてのサーバーが追跡できます。

メンバーシップの変更後も、存続しているチャットルームは引き続きアクティブである可能性があります。この場合、すべてのノードのすべてのルーターは通常どおりに送信を続けますが、新しい2番目のチャットルームホストにも投機的にメッセージを送信します。その2番目のチャットルームはまだ稼働していない可能性がありますが、メッセージはサバイバーを介して流れるため、問題はありません。メンバーシップの変更後に存続しているチャットルームがアクティブでなくなった場合、すべてのホスト上のすべてのルーターが最初に3つのホストに送信します。サバイバーと2つの新しいノード。 akkaデスウォッチメカニズムを使用すると、すべてのノードが、存続しているチャットルームのシャットダウンを最終的に確認して、2つのホストを介したチャットトラフィックのルーティングに戻ることができます。

次に、状況に応じて、チャットルームを存続しているサーバーから1つまたは2つの新しいホストに移行する必要があります。生き残ったチャットルームアクターは、ある時点で、新しいクラスターメンバーシップについて通知するメッセージを受け取ります。まず、チャットルームメンバーシップのコピーを新しいノードに送信します。このメッセージは、新しいノードの正しいメンバーシップを持つチャットルームアクターの新しいコピーを作成します。サバイバーがチャットルームを保持する必要のある2つのノードのいずれかでなくなった場合、サバイバーは廃止モードになります。デコミッショニングモードでは、メッセージはチャットルームメンバーではなく、新しいプライマリノードとセカンダリノードにのみ転送されます。 Akkaメッセージ転送はこれに最適です。

廃止されたチャットルームは、各ノードからのnorbertクラスターメンバーシップ確認メッセージをリッスンします。最終的には、クラスター内のすべてのノードが新しいクラスターメンバーシップを確認したことがわかります。その後、転送するメッセージをこれ以上受信しないことがわかります。その後、自殺することができます。 Akkaホットスワップは、廃止措置の動作を実装するのに最適です。

ここまでは順調ですね;ノードクラッシュのメッセージを失うことのない、回復力のあるメッセージング設定があります。クラスタメンバーシップが変更された時点で、チャットルームを新しいノードにコピーするためのノード内トラフィックが急増します。また、すべてのサーバーがどのチャットルームが2つのサーバーを移動したかに追いつくまで、ノードへのメッセージのノード内転送が急増しています。システムをスケールアップしたい場合は、ユーザートラフィックが少なくなるまで待ってから、新しいノードをオンにするだけです。チャットルームは、新しいノード全体に自動的に再配布されます。

上記の説明は、次の論文を読み、それをakkaの概念に翻訳することに基づいています。

https://www.dropbox.com/s/iihpq9bjcfver07/VLDB-Paper.pdf

7
simbo1905