AWSクラウドでのリアルタイムデータ処理。 パート2

記事の最初の部分では 、生物学的研究の結果を保存および分析する公共サービスに取り組んでいるときに遭遇した問題の1つについて説明しました。 顧客から提供された要件と、既存の製品に基づいたいくつかの可能な実装オプションが考慮されました。



画像



今日は、実装されたソリューションに焦点を当てます。



提案されたアーキテクチャ



画像



フロントエンド



ユーザーリクエストはフロントエンドに送られ、フォーマットへの準拠が検証され、バックエンドに転送されます。 クライアントが独立してそのようなイメージを構築したい場合、各リクエストは最終的にイメージまたはポイントのセットとともにフロントエンドに戻ります。



将来的には、LRUキャッシュをインストールして、ユーザーセッションの期間に比例して、要素の寿命が短い重複した結果を保存することができます。



バックエンド



そのようなリクエストごとに、バックエンド





サブタスクの処理は、RPCスタイル(タスクの設定、待機、結果の取得)で各サブタスクの並列キューイングによって発生します。 このために、バックエンドアプリケーションに対してグローバルなスレッドプールが使用されます。 このプール内の各スレッドは、メッセージの送信、待機、結果の受信など、ブローカーとの対話を担当します。



さらに、既知のサイズのスレッドのプールを使用すると、同時に処理されるメッセージサブタスクの数を制御できます。 また、よく知られているサブタスクを処理するスレッドの起動により、現在実行されている一般タスクを正確に計画し、各一般タスクの可用性を予測できます。



画像



安定性を確保するには、次の3つに従う必要があります。



  1. 1つのサブタスクの処理時間/その時点でキューに入れられたサブタスクの数-このパラメーターを増やす場合、キューのスループット容量を増やす必要があります。
  2. 各共通タスクができるだけ短時間で処理されるように、サブタスク処理に優先順位を付けます。
  3. 処理中の一般的なタスクの数は、中間結果をメモリに保持する必要があるためにバックエンドでJVMヒープオーバーフローを回避することです。




項目2と3は、スレッドプールのサイズとサブタスクのキューへのアプローチを操作することで達成されます。 平均サブタスク処理時間(ポイント1)を変更する場合、サブタスクを処理するための作業ノードの数を増やすか、それに応じて減らす必要があります。



労働者



RabbitMQキューのサブスクライバーはスタンドアロンアプリケーションであり、明確にワーカーと呼ばれます。 それぞれがEC2インスタンスの1つを完全に占有し、CPU、RAM、およびネットワーク帯域幅を最も効率的に使用します。



バックエンドで形成されたサブタスクは、一部のワーカーによって消費されます。 そのようなサブタスクを処理するプロセスは、ワーカーが自分の種類とは無関係に動作するため、グローバルコンテキストを意味しません。



重要な点は、Amazon S3 が任意のデータへのランダムアクセスを提供することです。 つまり、500 MBのファイルをダウンロードするのではなく、このリクエストのほとんどはこのリクエストを処理するために必要ではなく、本当に必要なものだけを読むことができます。 つまり、一般的なタスクを正しい方法でposdachkiに分割することにより、同じデータの二重の読み取りが常に行われないようにすることができます。



実行時エラー(メモリ不足、障害など)の場合、タスクは単純にキューに戻り、そこで自動的に別のノードに分散されます。 システムの安定性のため、各ワーカーはcronで定期的に再起動して、メモリリークやJVMヒープオーバーフローの問題を回避します。



スケーリング



アプリケーションノードの数を変更する必要につながるいくつかの理由があります。



  1. サブタスクの平均処理時間の増加は、最終的に必要な時間枠内でユーザーに最終結果を提供する際に問題につながります。
  2. ワーカーノードに適切な負荷がありません。
  3. CPUまたはメモリのバックエンドの過負荷。




問題1と2を解決するために、EC2が提供するAPIを使用し、インスタンスで動作する別個のモジュールスケーラーを作成しました。 新しいインスタンスはそれぞれ、事前に構成されたオペレーティングシステムのイメージ(Amazon Machine Image、AMI)に基づいて作成され、スポットリクエストを通じて起動されるため、ホスティングのコストを約5倍節約できます。



このアプローチの欠点は、インスタンスのスポットリクエストを作成してからコミッショニングまでに約4〜5分かかることです。 この時点で、ピーク負荷はすでに過ぎている可能性があり、ノードの数を増やす必要性は自然に消える可能性があります。



このような状況に陥る頻度を減らすために、リクエストの数、ユーザーの地理的位置、および時刻に関する統計を使用します。 その助けにより、「事前に」作業単位の数を増減します。 ほとんどすべてのユーザーは、営業日中にのみ当社のサービスを使用します。 そのため、米国(特に米国西部)および中国での就業日の初めに急増が顕著に見られます。 また、キューの輻輳に関する問題が引き続き発生する場合は、4〜5分で問題を解決できます。



問題3はまだ解決されておらず、私たちにとって最も脆弱な場所です。 データアクセス制御、その詳細と場所に関する知識、および計算されたデータの後処理(削減ステップ)の3つの事柄の現在の接続性は考案されており、別々のレイヤーに処理する必要があります。



公平に言うと、ReduceプロセスはSystem.arraycopy(...)になり、1つのバックエンドインスタンスのメモリ内のデータの合計量(クエリ+既成のサブタスクの一部)が1 GBを超えたことは決してなく、簡単に収まります。 JVMヒープ。



展開



既存のシステムの変更は、テストのいくつかの段階を経ます。







記載されているサブシステムの変更は、基本的に、新しいタイプのソースデータのパフォーマンスとサポートに関するものです。 したがって、通常は単体テストと統合テストで十分です。



「実稼働」ブランチからのビルドが成功するたびに、TeamCityは、アプリケーションを起動するためのパラメーターセットを制御する、すぐに使用できるJARおよびスクリプトであるアーティファクトを公開します。 事前に準備されたAMIから新しいインスタンスを起動する(または既存のAMIをリロードする)と、起動スクリプトはTeamCityから最後の本番ビルドをダウンロードし、ビルドで提供されるスクリプトを使用してアプリケーションを起動します。



したがって、新しいバージョンを実稼働環境にデプロイするために必要なことは、テストの終了を待って「マジック」ボタンをクリックしてインスタンスを再起動することだけです。 実行中のインスタンスのセットを制御し、タスクフローを異なるRabbitMQキューに分割して、ユーザーグループのA / Bテストを実行できます。



愛人メモ







そして最後に、私は言います...



このレビュー記事では、かなり典型的な問題を解決するためのアプローチについて説明しました。 このプロジェクトは発展を続けており、毎日ますます機能的になっています。 私たちの経験を聴衆と共有し、質問に答えたいです。



All Articles