RESTサービスの前にリクエストキューを配置するための最良のテクノロジーソリューション(フレームワーク/アプローチ)は何ですか。可用性を高めるためにRESTサービスのインスタンスの数を増やし、リクエストキューを前に配置して、サービスクライアントのサービス/トランザクション境界を形成できるようにします。
- Request Queue(java)には、優れた軽量のテクノロジー/フレームワークを選択する必要があります
- 競合するコンシューマーを実装するためのアプローチ。
目標に応じて、ここにはいくつかの問題があります。
まず、バックエンドでのリソースの可用性を促進するだけです。バックエンドでキュー要求を処理するサーバーが5台あるかどうかを検討してください。これらのサーバーの1つがダウンした場合、キューに入れられた要求はキューにフォールバックし、残りの4つのサーバーの1つに再配信する必要があります。
ただし、これらのバックエンドサーバーが処理している間、フロントエンドサーバーは実際の開始要求を保持しています。これらのフロントエンドサーバーの1つに障害が発生すると、それらの接続は完全に失われ、元のクライアントが要求を再送信する必要があります。
おそらく、より単純なフロントエンドシステムは障害のリスクが低いという前提があり、それはソフトウェア関連の障害にも確かに当てはまります。しかし、ネットワークカード、電源、ハードドライブなどは、人間のそのような誤った希望にかなりとらわれず、すべてを平等に罰します。したがって、全体的な可用性について話すときは、これを考慮してください。
設計に関しては、バックエンドは、JMSメッセージキューを待機し、各メッセージが到着したときに処理する単純なプロセスです。利用可能なこの例は多数あり、どのJMSサーバーも高レベルで適しています。必要なのは、メッセージ処理がトランザクション処理であることを確認することです。これにより、メッセージ処理が失敗した場合でも、メッセージはキューに残り、別のメッセージハンドラーに再配信できます。
JMSキューの主な要件は、クラスター化可能であることです。JMSサーバー自体は、システムの単一障害点です。JMSサーバーを失い、システムはほとんど水没しているため、サーバーをクラスター化し、コンシューマーとプロデューサーにフェイルオーバーを適切に処理させる必要があります。繰り返しになりますが、これはJMSサーバー固有であり、ほとんどの場合それを行いますが、JMSの世界ではかなり日常的なことです。
フロントエンドサーバーは、RESTリクエストの同期の世界からバックエンドプロセッサの非同期の世界へのブリッジであるため、フロントエンドは少し注意が必要です。REST要求は、ソケットから要求ペイロードを消費し、接続を開いたままにし、結果を処理し、結果を元のソケットに戻すという通常のRPCパターンに従います。
このハンドオフを明示するには、導入されたサーブレット3.0を処理する非同期サーブレットを確認する必要があります。これはTomcat 7、最新のJetty(バージョンは不明)、Glassfish3.xなどで使用できます。
この場合、リクエストが到着したときに、を使用して名目上同期サーブレット呼び出しを非同期呼び出しに変換しますHttpServletRequest.startAsync(HttpServletRequest request, HttpServletResponse response)
。
これによりAsynchronousContextが返され、開始されると、サーバーは処理スレッドを解放できます。次に、いくつかのことを行います。
この時点で、初期処理が完了し、doGet(またはサービスなど)から戻るだけです。AsyncContext.complete()を呼び出していないため、サーバーはサーバーへの接続を閉じません。IDによってマップにAsyncContextストアがあるので、当面の間安全に保管するのに便利です。
これで、リクエストをJMSキューに送信すると、リクエストのID(生成したもの)、リクエストのパラメーター、およびリクエストを行っている実際のサーバーのIDが含まれていました。処理の結果は元に戻る必要があるため、この最後のビットは重要です。オリジンは、リクエストIDとサーバーIDによって識別されます。
フロントエンドサーバーが起動すると、JMS応答キューをリッスンするスレッドも開始されました。JMS接続を設定するときに、「ABC123のServerIDのメッセージのみを表示する」などのフィルタを設定できます。または、フロントエンドサーバーごとに一意のキューを作成し、バックエンドサーバーがサーバーIDを使用して、応答を返すキューを決定することもできます。
バックエンドプロセッサがメッセージを消費すると、リクエストIDとパラメータを取得して作業を実行し、結果を取得してJMS応答キューに配置します。結果を返すときに、メッセージのプロパティとして元のServerIDと元のRequestIDが追加されます。
したがって、最初にフロントエンドサーバーABC123のリクエストを受け取った場合、バックエンドプロセッサは結果をそのサーバーにアドレス指定します。次に、そのリスナースレッドは、メッセージを受信したときに通知されます。リスナースレッドのタスクは、そのメッセージを取得して、フロントエンドサーバー内の内部キューに配置することです。
この内部キューは、リクエストペイロードを元の接続に送り返すことを仕事とするスレッドプールによって支えられています。これを行うには、メッセージから元のリクエストIDを抽出し、前述の内部マップからAsyncContextを検索してから、AsyncContextに関連付けられたHttpServletResponseに結果を送信します。最後に、AsyncContext.complete()(または同様のメソッド)を呼び出して、サーバーに完了を通知し、サーバーが接続を解放できるようにします。
ハウスキーピングの場合、フロントエンドサーバーに別のスレッドが必要です。このスレッドは、リクエストがマップ内で長時間待機していることを検出する役割を果たします。元のメッセージの一部は、リクエストが開始された時刻である必要があります。このスレッドは毎秒ウェイクアップし、マップをスキャンしてリクエストを探します。長すぎる(たとえば30秒)場合は、リクエストを別の内部キューに入れて、通知するように設計されたハンドラーのコレクションによって消費されます。リクエストがタイムアウトしたクライアント。
これらの内部キューが必要なのは、メインの処理ロジックがクライアントがデータを消費するのを待ってスタックしないようにするためです。接続が遅いなどの可能性があるため、他の保留中のリクエストをすべてブロックして、それらを1つずつ処理する必要はありません。
最後に、内部マップに存在しなくなったリクエストの応答キューからメッセージを受け取る可能性があることを考慮する必要があります。1つは、リクエストがタイムアウトした可能性があるため、もう存在しないはずです。もう1つは、そのフロントエンドサーバーが停止して再起動した可能性があるため、保留中のリクエストの内部マップは単に空になります。この時点で、存在しなくなったリクエストに対する応答があることを検出した場合は、単にそれを破棄する必要があります(まあ、ログに記録してから破棄します)。
これらのリクエストを再利用することはできません。ロードバランサーがクライアントに戻るようなことは実際にはありません。クライアントが公開されたエンドポイントを介してコールバックを行うことを許可している場合は、別のJMSメッセージハンドラーにそれらの要求を行わせることができることを確認してください。しかし、それはRESTのようなものではありません。このレベルの議論では、RESTはより多くのクライアント/サーバー/RPCです。
どのフレームワークが生のサーブレット(JAX-RS用のJerseyなど)よりも高いレベルで非同期サーブレットをサポートしているかについては、私には言えません。そのレベルでどのフレームワークがそれをサポートしているかわかりません。これはJersey2.0の機能のようですが、まだリリースされていません。他にもあるかもしれません、あなたは周りを見回さなければならないでしょう。また、サーブレット3.0に固執しないでください。サーブレット3.0は、しばらくの間(特にJetty)個々のコンテナで使用されていた手法の標準化にすぎないため、サーブレット3.0以外のコンテナ固有のオプションを確認することをお勧めします。
しかし、概念は同じです。重要なポイントは、フィルタリングされたJMS接続を備えた応答キューリスナー、AsyncContextへの内部要求マップ、およびアプリケーション内で実際の作業を行うための内部キューとスレッドプールです。
Javaである必要があるという要件を緩和する場合は、HAProxyを検討できます。これは非常に軽量で、非常に標準的であり、多くの優れた機能(リクエストプーリング/キープアライブ/キューイング)をうまく実行します。
ただし、リクエストキューイングを実装する前によく考えてください。トラフィックが極端にバーストしない限り、負荷がかかった状態でシステムのパフォーマンスを損なうだけです。
システムが1秒あたり100件のリクエストを処理できると仮定します。HTTPサーバーには制限付きワーカースレッドプールがあります。リクエストプールが役立つ唯一の方法は、1秒あたり100を超えるリクエストを受信している場合です。ワーカースレッドプールがいっぱいになると、リクエストがロードバランサープールに蓄積され始めます。それらはあなたがそれらを処理することができるより速く到着しているので、キューはより大きくなります...そしてより大きく...そしてより大きくなります。最終的には、このプールもいっぱいになるか、RAMが不足して、ロードバランサー(したがってシステム全体)が激しくクラッシュします。
Webサーバーがビジー状態の場合は、要求の拒否を開始し、オンラインで追加の容量を取得します。
リクエストを処理するために時間内に追加の容量を取得できる場合は、リクエストプーリングが確かに役立ちます。それはまたあなたを本当にひどく傷つける可能性があります。HTTPサーバーのワーカースレッドプールの前にあるセカンダリリクエストプールをオンにする前に、結果をよく検討してください。
私たちが使用する設計は、すべての要求を受信し、それらをメッセージキュー(つまりRabbitmq)にディスパッチするRESTインターフェースです。
次に、ワーカーはメッセージをリッスンし、特定のルールに従ってメッセージを実行します。すべてがダウンした場合でも、MQにリクエストがあり、リクエストの数が多い場合は、ワーカーを追加するだけです...
この基調講演をチェックしてください、それはこの概念の力を示しています!