2

今日は、MVC3 Web ロールの AsyncController 内から長時間実行されるブロック プロセス (5 ~ 30 秒) の待機をシミュレートしたいと思いました。ただし、最初は1秒から始めて、物事を進めました。はい、ブロック操作は現在、I/O 完了ポートで外部サービスに対して非同期に実行できないため、これの賢明さには疑問がありますが、この特定の状況でのパフォーマンスの制限を確認したかったのです。

私の Web ロールでは、6 つの小さなインスタンスをデプロイしました。唯一のコントローラーは AsyncController で、1000 ミリ秒のブロッキング操作をシミュレートするための 2 つの単純なメソッドがありました。

MVC3 Web ロール コントローラーは単純に次のとおりです。

public class MessageController : AsyncController
{
    public void ProcessMessageAsync(string id)
    {
        AsyncManager.OutstandingOperations.Increment();
        Task.Factory.StartNew(() => DoSlowWork());
    }

    public ActionResult ProcessMessageCompleted()
    {
        return View("Message");
    }

    private void DoSlowWork()
    {
        Thread.Sleep(1000);
        AsyncManager.OutstandingOperations.Decrement();
    }
}

次に、Amazon EC2 から Web ロールにストレスを適用しました。12 台のサーバーを使用して、負荷をゆっくりと増やし、1 秒あたり 550 リクエストに近づきました。これを超えてプッシュしようとすると、明らかなスレッドの枯渇とそれに続くエラーが発生しました。CLR スレッドの制限に達していたと思いますが、これは CPU あたり 100 スレッドであると理解しています。AsyncController のオーバーヘッドと、1000 ミリ秒のブロッキング操作で 1 サーバーあたり 1 秒あたり平均 550/6 = 92 リクエストという計算は、その結論に合っているようです。

これは本当ですか?他の人が同様のことを言っているのを見たことがあります。このタイプの負荷では、インスタンスあたり 1 秒あたり 60 ~ 80 リクエストに達しました。このシステムの負荷は、主に実行時間の長い操作で構成されるため、5000 ミリ秒のタスクがオンラインになると、1000 ミリ秒で 1 秒あたり 92 のリクエストが大幅に減少します。

ブロッキング I/O のリクエストを複数の個別の Web ロール フロント エンド経由でルーティングして、この負荷をより多くのコアに分散させる以外に、1000 ミリ秒のブロック時間で 1 秒あたり 90 程度のリクエストというこの明らかな制限を超える方法はありますか? ここで明らかな間違いを犯しましたか?

4

1 に答える 1

4

申し訳ありませんが、この購入Task.Factory.StartNewは、単に使用することですべての問題を解決できると主張するすべてのブログによって誤解されていると言わざるを得ませんが、そうではありません.

Task.Factory.StartNew を使用した負荷テスト

私があなたのコードで行った次の負荷テストを見てください (さらに悪化させるために、スリープを 1 秒ではなく 10 秒に変更しました)。このテストでは、合計 2500 のリクエストを実行する 200 人の固定ユーザーをシミュレートします。そして、スレッド スタベーションが原因で失敗したリクエストの数を確認します。

ここに画像の説明を入力

ご覧のとおり、Task で AsyncController を使用している場合でも、スレッド スタベーションは依然として発生しています。長時間実行されているプロセスが原因である可能性がありますか?

TaskCreationOptions.LongRunning を使用したロード テスト

タスクが長時間実行されているかどうかを指定できることをご存知ですか? この質問を見てください: TaskCreationOptions.LongRunning を使用しない場合の奇妙な動作

LongRunning フラグを使用しない場合、タスクは独自の (専用) スレッドではなく、スレッドプール スレッドでスケジュールされます。これが動作の変化の原因である可能性があります。LongRunning フラグを設定せずに実行していると、プロセス内の他のスレッドが原因でスレッドプールが枯渇している可能性があります。

コードを 1 行変更するとどうなるか見てみましょう。

    public void ProcessMessageAsync(string id)
    {
        Task.Factory.StartNew(DoSlowWork, TaskCreationOptions.LongRunning);
        AsyncManager.OutstandingOperations.Increment();
    }

ロード テストを見てみましょう。

ここに画像の説明を入力

今何があったの?

ご覧のとおり、LongRunning オプションは大きな違いを生んでいるようです。ログを追加して、内部で何が起こっているかを確認しましょう。

    public void ProcessMessageAsync(string id)
    {
        Trace.WriteLine(String.Format("Before async call - ThreadID: {0} | IsBackground: {1} | IsThreadPoolThread: {2} | Priority: {3} | ThreadState: {4}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsBackground,
            Thread.CurrentThread.IsThreadPoolThread, Thread.CurrentThread.Priority, Thread.CurrentThread.ThreadState));
        Task.Factory.StartNew(DoSlowWork, TaskCreationOptions.LongRunning);
        AsyncManager.OutstandingOperations.Increment();
    }

    ...

    private void DoSlowWork()
    {
        Trace.WriteLine(String.Format("In async call - ThreadID: {0} | IsBackground: {1} | IsThreadPoolThread: {2} | Priority: {3} | ThreadState: {4}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsBackground,
               Thread.CurrentThread.IsThreadPoolThread, Thread.CurrentThread.Priority, Thread.CurrentThread.ThreadState)); 
        Thread.Sleep(10000);
        AsyncManager.OutstandingOperations.Decrement();
    }

LongRunning なし:

Before async call - ThreadID: 11 | IsBackground: True | IsThreadPoolThread: True | Priority: Normal | ThreadState: Background
Async call - ThreadID: 11 | IsBackground: True | IsThreadPoolThread: True | Priority: Normal | ThreadState: Background

LongRunning の場合:

Before async call - ThreadID: 48 | IsBackground: True | IsThreadPoolThread: True | Priority: Normal | ThreadState: Background
Async call - ThreadID: 48 | IsBackground: True | IsThreadPoolThread: False | Priority: Normal | ThreadState: Background

ご覧のとおり、LongRunning を使用しないと、実際にはスレッド プールのスレッドを使用しているため、枯渇が発生します。この場合、LongRunning オプションはうまく機能しますが、本当に必要かどうかを常に評価する必要があります。

注: Windows Azure を使用しているため、数分間非アクティブ状態が続くとロード バランサーがタイムアウトになることを考慮する必要があります。

于 2012-09-23T08:17:06.297 に答える