前の記事で、エージェントのオーバーロードなどの問題について何度か言及しました。 これは何ですか これは何を脅かすのですか? これに対処する方法は? 今日はこのすべてについてお話します。
エージェントが処理するよりも多くのメッセージがエージェントに送信されると、エージェントの過負荷の問題が発生します。 その結果、メッセージキューのサイズは常に増加しています。 キューの増加はメモリを消費します。 メモリを消費すると、アプリケーションの速度が低下します。 速度の低下により、問題のあるエージェントはメッセージの処理をより長く開始し、メッセージキューの増加率が増加します。 これは、メモリ消費の高速化に貢献します。 これにより、アプリケーションの速度がさらに低下します。 これにより、問題のあるエージェントの作業がさらに遅くなります。その結果、アプリケーションはゆっくりと悲しげに低下し、完全に動作しなくなります。
この問題は、非同期メッセージとファイアアンドフォーゲットアプローチの使用による相互作用が輻輳を直接引き起こすという事実によってさらに悪化します(ファイアアンドフォーゲットは、エージェントAが着信メッセージM1を受信し、処理し、発信メッセージM2をエージェントBに送信する場合です。結果を気にしない)。
実際、送信が受信エージェントのキューにメッセージを配置することのみを担当する場合、受信者がメッセージフローを管理していなくても、送信者は送信時にブロックされません。 これにより、メッセージ送信者が受信者が負荷に対処できるかどうかを気にしないための前提条件が作成されます。
残念ながら、すべての状況に適した万能型のオーバーロードレシピはありません。 どこかに、過負荷のエージェントに宛てられた新しいメッセージを単に失う可能性があります。 どこかでこれは受け入れられませんが、キューに長すぎて既に関連性がなくなっている最も古いメッセージを捨てることができます。 どこかでメッセージをまったく失うことはできませんが、過負荷になるとメッセージ処理の別の方法に切り替えることができます。 たとえば、Webページに表示する写真のサイズ変更をエージェントが担当している場合、過負荷になると、エージェントはより粗いサイズ変更アルゴリズムに切り替える可能性があります。写真の品質は低下しますが、処理する写真のキューはより速く解消されます。
したがって、特定のタスクについては、過負荷から保護するための優れたツールを強化する必要があります。 このため、SObjectizerには長い間、ユーザーが「すぐに使用できる」既製のメカニズムはありませんでした。 ユーザーは自分で問題を解決しました。
私たちの経験では、過負荷から保護するための最も実用的な方法の1つは、コレクターとパフォーマーの2つのエージェントを使用するアプローチでした。 コレクターエージェントは、着信メッセージを蓄積して、適切なコンテナで処理します。 パフォーマーエージェントは、コレクターエージェントによって収集されたメッセージを処理します。
トリックは、コレクターエージェントとパフォーマーエージェントが異なる作業コンテキストにバインドすることです。 したがって、実行エージェントがスローダウンし始めても、これはコレクターの作業に影響しません。 原則として、コレクターエージェントはそのイベントを非常に迅速に処理します。通常、処理は新しいメッセージを別のキューに格納することから成ります。 この個別のキューがいっぱいの場合、コレクタエージェントは新しいメッセージにすぐに否定応答を返すことができます。 または、キューから古いメッセージをスローします。 また、コレクターエージェントは、この個別のキュー内のメッセージが費やした時間を定期的に確認できます。メッセージが処理を待機しすぎて関連性がなくなった場合、コレクターエージェントはキューからそれをスローします(イニシエーターに否定応答を送信する場合があります)このメッセージ)。
実行エージェントは、原則として、コレクタエージェントよりもはるかに長く着信メッセージを処理します。コレクタエージェントは論理的です。 実際に適用される作業の責任は、実行者にあります。 実行エージェント自体が、処理のためにコレクタエージェントにメッセージの次のバッチを要求します。
最も単純なケースでは、実行エージェントは現在のメッセージの処理を完了し、コレクタエージェントに要求を送信して、処理する次のメッセージを発行します。 この要求を受信すると、コレクタエージェントはキューから最初のメッセージを実行エージェントに発行します。
SObjectizerには、コレクターとパフォーマーのトピックに関するさまざまなバリエーションを示すいくつかの例が含まれています。 これらの例の説明は、プロジェクトWiki( No. 1 、 No。2 、 No。3 、 No。4 )にあります。
SObjectizerを使用した経験と多くの議論の結果により、複雑さと効率が異なる日曜大工の保護スキームを作成する可能性があるにもかかわらず、SObjectizer自体に基本的なメカニズムが必要であるという結論に達しました。 高度ではありませんが、すぐに使用できるようになっています。これは、ラピッドプロトタイピングで特に需要があります。
メッセージの制限 (いわゆるメッセージ制限 )がこのようなメカニズムになりました。 エージェントは、メッセージキューに保存できる特定のタイプのメッセージのインスタンス数を指定できます。 そして、「余分な」インスタンスで何をすべきか。 指定された制限を超えるとオーバーロードになり、SObjectizerは次のいずれかの方法でそれに応答します。
- あたかもそこにないかのように新しいメッセージをスローします。
- メッセージを別のmboxに転送します。 この反応は、他の受信者が過剰なメッセージを処理できるという仮定の下で実行されます。
- 余分なメッセージを別のタイプのメッセージに変換し、新しいメッセージをmboxに送信します。 この反応により、たとえば、メッセージの送信者に否定応答をすぐに送信できる場合があります。
- std :: abort()を呼び出してアプリケーションを終了します。 このオプションは、負荷が考えられるすべての制限を超え、実際に作業容量を復元する機会がない場合に適しています。したがって、キューにメッセージを蓄積し続けるよりも中断して再起動する方が適切です。
メッセージの制限が輻輳にどのように役立つかを見てみましょう(例のソースコードはこのリポジトリにあります )。
画像を特定のサイズにサイズ変更する要求を処理する必要があるとします。 このようなリクエストが10個以上蓄積される場合、別のアルゴリズムを使用して新しいリクエストを処理する必要がありますが、より高速ですが、精度は低くなります。 これが役に立たない場合は、特別な方法で追加のリクエストを記録し、イニシエーターに結果のサイズの空白の画像を送信します。
3つのエージェントを作成します。 最初のエージェントは通常の画像処理を実行します。 message_limitを使用してすばやくサイズ変更できない余分な画像は、2番目のエージェントに送信されます。
// , . class accurate_resizer final : public agent_t { public : accurate_resizer( context_t ctx, mbox_t overload_mbox ) // // . // , . : agent_t( ctx // resize_request // // . + limit_then_redirect< resize_request >( // 10 ... 10, // mbox. [overload_mbox]() { return overload_mbox; } ) ) {...} ... };
2番目のエージェントは、高速ですが、より粗い画像処理を実行します。 したがって、キュー内のメッセージの数には上限があります。 追加のメッセージは3番目のエージェントにリダイレクトされます。
// , . class inaccurate_resizer final : public agent_t { public : inaccurate_resizer( context_t ctx, mbox_t overload_mbox ) : agent_t( ctx + limit_then_redirect< resize_request >( // 20 ... 20, // mbox. [overload_mbox]() { return overload_mbox; } ) ) {...} ... };
3番目のエージェントは、サイズ変更の代わりに、空白の画像を生成します。 つまり すべてが悪く、負荷が非常に高いことが判明した場合、完全なof迷に陥るよりも、写真の代わりに空の場所を残す方が良いです。 ただし、空白の画像の生成はすぐには行われないため、3番目のエージェントには保留中の要求に対してある程度の制限があることが予想されます。 ただし、この制限を超えた場合は、std :: abortを使用してアプリケーション全体をバングすることをお勧めします。これにより、再起動後に再び動作を開始できます。 おそらく、要求フローの処理のブレーキは、アプリケーションのいくつかの不正な動作が原因であり、再起動すると、「ゼロから」最初からやり直すことができます。 したがって、3番目のエージェントは、制限を超えるとstd :: abortを強制的に呼び出します。 プリミティブ、しかし非常に効果的:
// , , // . class empty_image_maker final : public agent_t { public : empty_image_maker( context_t ctx ) : agent_t( ctx // 50- . // - , - // , // . + limit_then_abort< resize_request >( 50 ) ) {...} ... };
そして、一緒に、たとえば次の方法でエージェントを作成できます。
// . // mbox, . mbox_t make_resizers( environment_t & env ) { mbox_t resizer_mbox; // (.. // ), // active_obj. env.introduce_coop( disp::active_obj::create_private_disp( env )->binder(), [&resizer_mbox]( coop_t & coop ) { // , .. // mbox- "" . auto third = coop.make_agent< empty_image_maker >(); auto second = coop.make_agent< inaccurate_resizer >( third->so_direct_mbox() ); auto first = coop.make_agent< accurate_resizer >( second->so_direct_mbox() ); // , // . // . resizer_mbox = first->so_direct_mbox(); } ); return resizer_mbox; }
一般に、メッセージ制限メカニズムにより、単純なケースでは、カスタムコレクターエージェントおよびパフォーマーの開発を省くことができます。 それらを完全に置き換えることはできませんが(たとえば、メッセージ制限はキューから最も古いメッセージを自動的に破棄しません-これは、ディスパッチャからのリクエストのキューイングによるものです)。
それでは、簡単に要約してみましょう。 非同期メッセージを介して排他的に対話するエージェントでアプリケーションが構成されており、ファイアアンドフォーゲットアプローチを使用する場合、エージェントのオーバーロードはほぼ保証されます。 理想的には、エージェントを保護するには、行動ロジックがタスクに合わせて調整されたコレクターとパフォーマンスのエージェントのペアのようなものを使用する必要があります。 しかし、完璧なソリューションが必要ではなく、「安くて明るい」場合は、SObjectizerが「すぐに使用できる」メッセージの制限が役立ちます。
PS。 オーバーロードは、アクターモデルのフレームワーク内のアクター/エージェントだけでなく、CSPチャネルを使用する場合にも可能です。 そのため、SObjectizerでは、CSPチャネルの類似物であるメッセージチェーンにも、メッセージ制限に多少似た過負荷保護が含まれています。