次の方法で共有


スケーラビリティ

スケーラビリティという用語は、多くの場合、誤用されます。 このセクションでは、デュアル定義について説明します。

  • スケーラビリティは、マルチプロセッサ システム (2、4、8、32、または複数のプロセッサ) で使用可能な処理能力を十分に利用する機能です。
  • スケーラビリティは、多数のクライアントにサービスを提供する機能です。

これら 2 つの関連する定義は、一般に スケールアップと呼ばれます。 このトピックの最後では、のスケールアウトに関するヒントを提供します。

この説明では、スケーラブルなサーバーがより一般的な要件であるため、スケーラブルなクライアントではなく、スケーラブルなサーバーを記述することに重点を置いています。 このセクションでは、RPC サーバーと RPC サーバーのみのコンテキストでのスケーラビリティについても説明します。 ここでは、競合の削減、グローバル メモリの場所でのキャッシュ ミスの頻繁な回避、誤った共有の回避など、スケーラビリティに関するベスト プラクティスについては説明しません。

RPC スレッド モデル

RPC 呼び出しがサーバーによって受信されると、サーバー ルーチン (マネージャー ルーチン) は、RPC によって提供されるスレッドで呼び出されます。 RPC は、ワークロードの変動に応じて増減するアダプティブ スレッド プールを使用します。 Windows 2000 以降、RPC スレッド プールのコアは完了ポートです。 RPC による完了ポートとその使用法は、ゼロから低競合のサーバー ルーチンに合わせて調整されます。 つまり、一部がブロックされた場合、RPC スレッド プールはサービス スレッドの数を積極的に増やします。 これは、ブロックがまれであるという前提に基づいて動作し、スレッドがブロックされた場合、これはすぐに解決される一時的な条件です。 この方法により、競合が少ないサーバーの効率が向上します。 たとえば、高速システム エリア ネットワーク (SAN) 経由でアクセスされる 8 プロセッサ 550 MHz サーバーで動作する void 呼び出し RPC サーバーは、200 を超えるリモート クライアントから 1 秒あたり 30,000 を超える void 呼び出しを処理します。 これは、1 時間あたり 1 億 8000 万件を超える通話を表します。

その結果、サーバー上の競合が高い場合に、アグレッシブなスレッド プールが実際に邪魔になります。 たとえば、リモートでファイルにアクセスするために使用される頑丈なサーバーを想像してみてください。 サーバーが最も簡単なアプローチを採用しているとします。RPC がサーバー ルーチンを呼び出すスレッドでファイルを同期的に読み書きします。 また、多くのクライアントにサービスを提供する 4 プロセッサ サーバーがあるとします。

サーバーは 5 つのスレッドで始まります (これは実際には異なりますが、わかりやすくするために 5 つのスレッドが使用されます)。 RPC は、最初の RPC 呼び出しを取得すると、その呼び出しをサーバー ルーチンにディスパッチし、サーバー ルーチンが I/O を発行します。 まれに、ファイル キャッシュが見落とされ、結果の待機がブロックされます。 ブロックされるとすぐに、5 番目のスレッドが解放されて要求が取得され、6 番目のスレッドがホット スタンバイとして作成されます。 各 10 回の I/O 操作でキャッシュが失われ、100 ミリ秒 (任意の時間値) でブロックされると仮定し、4 プロセッサ サーバーが 1 秒あたり約 20,000 呼び出し (プロセッサあたり 5,000 呼び出し) を処理すると仮定すると、単純なモデリングでは、各プロセッサが約 50 個のスレッドを生成すると予測されます。 これは、ブロックされる呼び出しが 2 ミリ秒ごとに来ると想定し、100 ミリ秒後に最初のスレッドが再び解放されるため、プールは約 200 スレッド (プロセッサあたり 50) で安定します。

スレッドの数が多いと、サーバーが遅くなり、新しいスレッドの作成速度が遅くなるため、実際の動作はより複雑になりますが、基本的な考え方は明らかです。 サーバー上のスレッドがブロックを開始し、何かを待機し始めると、スレッドの数がすぐに増えます (I/O やリソースへのアクセスなど)。

RPC と受信要求をゲートする完了ポートは、サーバー内の使用可能な RPC スレッドの数を、マシン上のプロセッサの数と同じ数に維持しようとします。 つまり、4 プロセッサ サーバーでは、スレッドが RPC に戻ると、4 つ以上の使用可能な RPC スレッドがある場合、5 番目のスレッドは新しい要求を取得できません。代わりに、現在使用可能なスレッドの 1 つがブロックされている場合はホット スタンバイ状態になります。 5 番目のスレッドが、使用可能な RPC スレッドの数がプロセッサの数を下回らずにホット スタンバイとして十分な時間待機すると、解放されます。つまり、スレッド プールは減少します。

多くのスレッドを持つサーバーを想像してみてください。 前述のように、RPC サーバーは多くのスレッドで終わりますが、スレッドが頻繁にブロックされる場合に限ります。 スレッドがブロックされることが多いサーバーでは、RPC に戻るスレッドは、現在使用可能なすべてのスレッドがブロックされ、処理要求が与えられるため、すぐにホット スタンバイ リストから取り出されます。 スレッドがブロックされると、カーネル内のスレッド ディスパッチャーはコンテキストを別のスレッドに切り替えます。 このコンテキスト切り替え自体は CPU サイクルを消費します。 次のスレッドは、異なるコードを実行し、異なるデータ構造にアクセスし、異なるスタックを持ちます。つまり、メモリ キャッシュのヒット 率 (L1 キャッシュと L2 キャッシュ) が大幅に低くなり、実行速度が低下します。 同時に実行される多数のスレッドでは、ヒープ、サーバー コード内の重要なセクションなど、既存のリソースの競合が増加します。 これにより、リソースのコンボイが形成されるにつれて競合がさらに増加します。 メモリが少ない場合、スレッドの数が多くなり、増加するスレッドによって発生するメモリ負荷によってページ フォールトが発生し、スレッドがブロックされる速度がさらに高まり、さらに多くのスレッドが作成されます。 ブロックする頻度と使用可能な物理メモリの量によっては、サーバーがコンテキスト切り替え率の高いパフォーマンスレベルで安定するか、実際の作業を実行せずにハード ディスクとコンテキストの切り替えに繰り返しアクセスするだけになるまで低下する可能性があります。 もちろん、この状況は軽いワークロードでは表示されませんが、負荷の高いワークロードでは問題がすぐに表面化します。

これを防ぐにはどうすればよいですか? スレッドがブロックされることが予想される場合は、呼び出しを非同期として宣言し、要求がサーバー ルーチンに入ったら、I/O システムや RPC の非同期機能を使用するワーカー スレッドのプールにキューに入れます。 サーバーが RPC 呼び出しを行っている場合は、それらの呼び出しを非同期にし、キューが大きくなりすぎないようにします。 サーバー ルーチンがファイル I/O を実行している場合は、非同期ファイル I/O を使用して I/O システムに対する複数の要求をキューに入れ、少数のスレッドだけがキューに入れ、結果を取得します。 サーバー ルーチンがネットワーク I/O を実行している場合は、システムの非同期機能を使用して要求を発行し、応答を非同期的に取得し、できるだけ少ないスレッドを使用します。 I/O が完了するか、サーバーが行った RPC 呼び出しが完了したら、要求を配信した非同期 RPC 呼び出しを完了します。 これにより、サーバーを可能な限り少数のスレッドで実行できるため、パフォーマンスが向上し、サーバーがサービスを提供できるクライアントの数が増えます。

スケールアウト

指定されたクライアント アドレスからのすべての要求が同じサーバーに送信されるように NLB が構成されている場合は、RPC をネットワーク負荷分散 (NLB) で動作するように構成できます。 各 RPC クライアントは接続プールを開くので (詳細については、RPC とネットワークを参照)、指定されたクライアントのプールからのすべての接続が同じサーバー コンピューター上に行われる必要があります。 この条件が満たされている限り、NLB クラスターは、スケーラビリティに優れている可能性のある 1 つの大規模な RPC サーバーとして機能するように構成できます。