メッセージ配信の信頼性

メッセージ配信の信頼性

Akkaは、1 台のコンピューターで複数のプロセッサコアを使用したり(スケールアップ)、ネットワークを跨って分散される (スケール アウト)信頼性の高いアプリケーションを構築することができます 。このための重要な抽象概念は、すべての相互作用がコードユニット(アクター)間でメッセージ送信を介して起こることです。アクター間のメッセージ送信方法の明確な意義がAkkaの重要なポイントです。

このあとの議論にいくつかのコンテキストを与えるために複数のネットワーク ホストにまたがるアプリケーションを検討します。通信の基本的なメカニズムはアクターがローカル JVM 上で送信していても、リモートアクターに送信していても同じですが、配信のレイテンシ (ネットワーク リンクの帯域幅やメッセージのサイズにも依存する) と信頼性には違いがあります。リモート メッセージ送信した場合は、明らかに間違いを起こす可能性のある、多くの複雑なステップがあります。別の側面として、ローカルの送信は送られるオブジェクトによる制約がなく、ただ同じJVM内のメッセージへ参照を渡すだけであるのに対して、リモート送信はメッセージサイズに上限を設けることになる。

すべての相互作用においてリモートが安全であるというアクターを書くことは悲観的な賭けです。これらのプロパティが常に保証されることに頼るだけであることを意味し、詳細は以下で説明します。 もちろんアクターの実装にいくつかのオーバーヘッドがあります。完全な位置透過性を犠牲にすることをいとわない場合(例えば、密接に協調するアクターのグループ場合)、常にそれらを同じ JVM に配置し、メッセージ配信の厳格な保証をお楽しみください。このトレードオフの詳細についてはこのあと説明します。

補助的に、ビルトイン上に強い信頼性を構築する方法についていくつかのポインターを与え、本章は、「デッドレターオフィス」の役割の議論に閉じることとします。

一般的なルール

メッセージ送信にはルールがあります( tell または !ask パターンの基礎にもなるメソッド)。

  • at-most-once delivery 、配信を保証しない

  • message ordering per sender–receiver pair

1つ目のルールは他アクターの実装でもありますが、2 つ目はAkka固有です。

ディスカッション: “at-most-once” とは何か?

デリバリーメカニズムのセマンティクスを記述するには、3 つの基本的なカテゴリがあります。

  • at-most-once デリバリーはそのメカニズムに渡したメッセージ毎に、そのメッセージは0回か1回配信されることを意味します。よりカジュアルに言うと、メッセージが失われる可能性があることを意味します。

  • at-least-once デリバリーは、そのメカニズムに渡したメッセージ毎に、少なくとも1回成功するようにその配信で潜在的に複数回試みが行われることを意味します。よりカジュアルに言うと、メッセージの複製が失われないことを意味します。

  • exactly-once デリバリーは、そのメカニズム渡したメッセージ毎に正確に1回の配信が受信者に対して行われることを意味します。そのメッセージは失われたり複製されたりしません。

1つ目は最も安価(最高のパフォーマンス、実装のオーバーヘッドが最も少ない)です。送信側やトランスポートメカニズムで状態を維持することがなく、fire-and-forget方式で行うことができるためです。2 つ目は、輸送時の損失に対抗するため再試行が必要になり、送信側で状態を維持して、受信側で承認メカニズムを持っていることを意味する。3つ目は、最も高価(その結果パフォーマンス最も悪い)です。2 番目のメカニズムに加えて、重複配達を除外するために受信側で状態を保つことが状態が必要なためです。

ディスカッション: なぜ配信が保証されないのか?

問題の核心は、この保証は正確に何を意味するかという疑問にあります。

  1. メッセージはネットワーク上で送信されるのか?

  2. メッセージは別のホストに受信されるのか?

  3. メッセージはターゲットとなるアクターのメールボックスに入れらているのか?

  4. メッセージは、ターゲットアクターで処理され始めているのか?

  5. メッセージは、ターゲットのアクターによって正常に処理されたか?

これらのそれぞれにはさまざまな課題とコストがあり、メッセージを渡すライブラリが遵守できない条件があることは明らかです。 たとえば、構成可能なメールボックスの種類や、No3で境界のあるメールボックスがやりとりする方法、またはNo5の「正常に」の部分を意味するものが何かを考えてください。

同じ流れに沿って、 「誰も信頼できるメッセージングが必要ではない」 という理由がある。送信者が対話が成功したかどうかを知るための唯一の意味のある方法は、ビジネスレベルの承認メッセージを受け取ることです。これはAkkaが独自に補うことのできるものではありません(我々が「意味を汲み取って実行する」フレームワークを書いているわけではなく、我々に望んでもいない)。

Akkaは分散コンピューティングを採用しており、メッセージの受け渡しによって通信の可否を明白にしているため、抽象性の破綻を欺いたりエミュレートしたりしようとしません。これは、Erlangで大きな成功を収めて使用されているモデルであり、ユーザーがそのアプリケーションを設計する必要があります。このアプローチの詳細は Erlang documentation _(section 10.9 と 10.10) で読むことができ、Akkaはこれに密接に従います。

この問題に関するもう1つの視点は、より高い信頼性を必要としないユースケースが実装コストを払わない基本的な保証のみを提供することです。必要最小限なものの上には常により高い信頼性を追加することは可能ですが、パフォーマンスを高めるためには遡って信頼性を除去することはできません。

ディスカッション: メッセージの順序

より具体的に言えば、 あるペアのアクターで、1番目から2番目に直接送信されたメッセージは、順序が狂って受信されません。 直接 という単語は、この保証は、メディエータや他のメッセージ伝播機能を使用する(特に明記されていない限り)ときではなく、 tell 演算子を使用して最終的な宛先に送信する場合にのみ適用されます。

保証は以下の通りです:

アクター 'A1' は "A2" にメッセージ "M1", "M2", "M3" を送信

アクター 'A3' は "A2" にメッセージ "M4", "M5", "M6" を送信

これは次のことを意味します。
  1. "M1" が配信される場合は、 "M2" と "M3" の前に届けられなければならい

  2. "M2" が配信される場合は、"M3" の前に届けられなければならない

  3. "M4" が配信される場合は、 "M5" と "M6" の前に届けられなければならない

  4. "M5" が配信される場合は、 "M6" の前に届けられなければならない

  5. "A2" は、"A1" からのメッセージと ''A3" のメッセージを交互に見ることができる

  6. 配信の保証がないので、いずれかのメッセージが失われる(すなわち "A2" に到着しない)かもしれない

注釈

Akkaの保証は、メッセージが受信者のメールボックスにエンキューされる順序に適用されることに注意することが重要です。メールボックスの実装がFIFOを尊重しない場合(例えば、 PriorityMailbox)、アクターによる処理の順序は、エンキューする順序から逸脱する可能性があります。

このルールは 推移的ではない ことに注意してください。

アクター "A" は、アクター "C" にメッセージ "M1" を送る

アクター "A" は、アクター "B" にメッセージ "M2" を送る

アクター "B" はアクター "C" にメッセージ "M2" を転送する

アクター "C" は順不同で "M1" と "M2" を受信するでしょう

因果推移の順序は、 "M2" がアクター "C" で "M1" の前に受け取られることはないと示唆している(いずれかが失われても)。この順序付けは、 "A" 、 "B" 、 "C" が異なるネットワークホスト上にある場合のメッセージ配信の待ち時間が異なるために違反する可能性があります。

注釈

アクターの作成は、親から子に送信されるメッセージとして扱われ、上記のセマンティクスと同じ意味を持ちます。この最初の作成メッセージで並べ替えることができる方法でアクターにメッセージを送信することは、アクターがまだ存在しないためにメッセージが到着しない可能性があることを意味します。メッセージが早すぎる場合は、リモートデプロイされたアクターR1を作成し、その参照を別のリモートアクターR2に送信し、R2にR1にメッセージを送信させることがあります。うまく定義された順序付けの例は、アクターを作成してすぐにそのアクターにメッセージを送信する親です。

通信障害

上記で説明した順序保証は、アクター間のユーザーメッセージに対してのみ保持されることに注意してください。アクターの子供の失敗は、通常のユーザーメッセージと一緒に順序付けれない特別なシステムメッセージによって伝えられます。特に:

子アクター "C" は親アクター "P" にメッセージ "M" を送信

小アクターは障害 "F" で失敗

親アクター "P" は、"M"、 "F" の順または "F" 、 "M" の順に2つのイベントを受け取る

その理由は、内部システムメッセージには独自のメールボックスがあるため、ユーザーおよびシステムメッセージのエンキュー呼び出しの順でデキューの順序を保証することができないためです。

JVM内(ローカル)のメッセージ送信のルール

このセクションで何をしているか注意してください!

このセクションで信頼性を高めることは、アプリケーションをローカルのみのデプロイメントに縛り付けるため推奨されません。アプリケーションは、マシンのクラスタ上で実行するのに適しているように(アクターにローカルなメッセージ交換パターンを単に採用するのではなく)別々に設計する必要があります。私たちの信条は「一度設計して、あなたが望むようにデプロイする」ことです。これを達成するには、 The General Rules にのみ頼るべきです。

ローカルメッセージ送信の信頼性

Akkaテストスイートは、ローカルコンテキストでメッセージを失わないようにする(また、エラーのない状態でもリモートデプロイを行う)ため、テストを安定させるために最善の努力をしています。しかし、ローカルの `` tell`` 操作は、通常のメソッド呼び出しがJVM上で行うことができるのと同じ理由で失敗することがあります:

  • StackOverflowError
  • OutOfMemoryError
  • その他の VirtualMachineError

さらに、Akka特有の理由でローカル送信が失敗する可能性があります。

  • メールボックスがメッセージを受け入れない場合(例:BoundedMailbox がいっぱい)

  • 受信側アクターがメッセージの処理中に失敗した場合、または既に終了(terminate)している場合

1つ目は明らかに設定の問題ですが、2つ目はいくつかの考えが必要です。処理中に例外があった場合、メッセージの送信者はフィードバックを得ません。その通知は代わりにスーパーバイザーに送られます。これは、一般に、外部のオブザーバーのための失われたメッセージと区別できません。

ローカルメッセージ送信の順序

厳密なFIFOメールボックスを前提とすると、前述のメッセージ順序保証の非推移性の注意は、特定の条件下で排除されます。注意点として、これらはかなり微妙ですが、将来のパフォーマンスの最適化によってこの段落全体が無効になる可能性さえあります。通常は適切な方法であっても適用できない状況の非網羅的なリストは:

  • トップレベルのアクターから最初の返信を受け取る前に、内部の暫定的なキューを保護するロックがあり、このロックは公平ではありません。その意味は、低レベルのスレッドスケジューリングに応じて、アクターの構成(比喩的には、詳細がより入り組んでいる)中に到着する異なる送信者からのエンキューリクエストを並べ替えることができるということです。完全に公正なロックがJVM上に存在しないので、これは修正不可能です。

  • ルーターの構築中、より正確には、ルート指定されたActorRefの間に、同じメカニズムが使用されるため、ルーターで展開されたアクターにも同じ問題が存在します。

  • 上記のように、問題はエンキュー時にロックが関係する場所で発生します。これは、カスタムメールボックスにも当てはまります。

このリストは慎重に記述されていますが、他の問題のあるシナリオが分析を逃れている可能性があります。

ローカル順序とネットワーク順序の関係

あるペアのアクターで、1番目から2番目に直接送信されたメッセージは、順序が狂って受信さません。 というルールは、TCPベースのAkkaリモートトランスポートプロトコルを使用してネットワーク経由で送信されたメッセージで保持されます。

前のセクションで説明したように、ローカルメッセージは、特定の条件の下で推移的な因果関係の順序に従います。この順序は、メッセージ配信のレイテンシの差により違反する可能性があります。例えば:

ノード1上のアクター A が、ノード3上のアクター C にメッセージ M1 を送信

ノード1上のアクター A が、ノード2上のアクター B にメッセージ M2 を送信

ノード2上のアクター B はノード3上のアクター C にメッセージ M2 を転送

アクター "C" は順不同で "M1" と "M2" を受信するでしょう

M1 がノード3に「移動」するのに、 M2 がノード2を介してノード3に「移動」するよりも時間がかかることがあります。

高いレベルの抽象化

Akkaのコアで小さく一貫性のあるツールセットをベースとし、Akkaは強力で高いレベルの抽象化も提供します。

メッセージングパターン

前に議論したように、信頼性のある配送の要求に対する直接的な答えは明示的なACK-RETRYプロトコルです。最も単純な形式では、次のことを必要とします。

  • 確認応答のメッセージと関連付けるため個々のメッセージを識別する方法

  • 時間内に確認応答がなければメッセージを再送するリトライ機構

  • 受信者が重複を検出して破棄する方法

3つ目は、確認メッセージが到着することが保証されていないために必要になります。ビジネスレベルの確認応答を持つACK-RETRYプロトコルは、Akka Persistenceモジュールの At-Least-Once Delivery でサポートされています。重複は、At-Least-Once Delivery で送信されたメッセージの識別子を追跡することで検出できます。3つ目の内容を実装するもう1つの方法は、メッセージをビジネスロジックのレベルで冪等に処理することです。

3つの要件すべてを実装するもう1つの例は、 reliable-proxy (現在、 At-Least-Once Delivery で置き換えられている)です。

イベントソーシング

イベントソーシング(およびシャーディング)は、大規模なウェブサイトを何十億ものユーザーにスケールさせるもので、その考え方は非常に単純です。コンポーネント(アクターと考える)がコマンドを処理すると、コマンドが引き起こす結果を表すイベントのリストが生成されます。これらのイベントは、コンポーネントの状態に適用されることに加えて保存されます。この方式の素晴らしい点は、イベントがストレージに追加されるだけで、何も変化していないことです。これは、このイベントストリームのコンシューマの完全な複製およびスケーリングを可能にします(すなわち、他のコンポーネントは、異なる場所でコンポーネントの状態を複製するか、または変更に反応するためにイベントストリームをコンシュームする可能性がある)。コンポーネントの状態が、マシンの障害やキャッシュからのプッシュによって失われた場合、イベントストリームをリプレイ(通常は処理を高速化するためにスナップショットを使用)して簡単に再構築できます。 Event sourcing はAkka Persistenceによってサポートされています。

明示的な確認応答を含むメールボックス

カスタムメールボックス型を実装することにより、一時的な障害を処理するために受信アクター側でメッセージ処理をリトライすることができます。このパターンは、配信保証が別の方法でアプリケーションの要件を満たすのに十分であるローカル通信で最も有用です。

Please note that the caveats for The Rules for In-JVM (Local) Message Sends do apply.

このパターンの実装例は mailbox-acking にあります。

デッドレター

配信できないメッセージ(およびこれが確認できるメッセージ)は、 /deadLetters という合成アクターに配信されます。この配信は、ベストエフォート方式で行われます。ローカルJVM内であっても失敗する可能性があります(例えば、アクター終了中)。信頼できないネットワーク転送を介して送信されたメッセージは、デッドレターとして表示されずに失われます。

デッドレターは何に使えばいいですか?

この機能の主な使い方はデバッグ用で、特にアクターによる送信が一貫性を持って到着しない場合(通常、デッドレターの検査が送信者または受信者が途中で間違って設定されていることを知らせる)です。この目的に役立てるには、可能であればデッドレターに送信しないようにすること、つまり、適切なデッドレターロガー(下記参照)を使用してアプリケーションを実行し、随時出力ログを削除することをお勧めします。実戦には他と同様に、常識的なことを慎重に適用する必要があります。終了したアクターに送信するのを避けると、デバッグ出力の分かりやすさよりも送信側のコードが複雑になることがあります。

デッドレターサービスは、配送保証において他のすべてのメッセージ送信と同じ規則に従いますので、保証された配送の実装に使用することはできません。

デッドレターを受け取る方法は?

アクターは、イベントストリームでクラス akka.actor.DeadLetter をサブスクライブすることができます。その方法については、 Event Stream (Java) または Event Stream (Scala)を参照してください。サブスクライブされたアクターは、その時点以降(ローカル)システムでパブリッシュされているすべてのデッドレターを受信します。デッドレターはネットワークを介して伝播されません。ネットワークノードごとに1つのアクターをサブスクライブして手動で転送する必要がある場合、それらを1か所で収集する必要があります。また、送信の失敗を決定付けるデッドレターがそのノードで生成されていると考えてください。つまり、リモート送信がローカルシステムになる(ネットワーク接続が確立できない)場合、またはリモートシステムでの場合(あなたが送信しているアクターがその時点で存在しない)です。

(通常)気にしないデッドレター

アクターが自分で終了しないとその度に、自身に送るいくつかのメッセージが失われる可能性があります。 akka.dispatch.Terminate メッセージが失われているということは、2つの終了要求が与えられたことを意味しますが、もちろん1つしか成功することはできません。同じように、親が終了したときに親がまだ子どもを監視している場合に、デッドレターでアクターのヒエラルキーを停止させながら、子供からの akka.actor.Terminated メッセージを監視することができます。

Contents