Share via


WCF サービスが負荷の下でゆっくりとスケールアップされる可能性がある

この記事は、Windows Communication Foundation (WCF) サービスが読み込み中にゆっくりとスケールアップされる可能性がある場合に発生するエラーを解決するのに役立ちます。

元の製品バージョン: Windows Communication Foundation
元の KB 番号: 2538826

現象

WCF サービスが要求のバーストを受け取ると、既定の .NET I/O 完了ポート (IOCP) スレッド プールが必要に応じて迅速にスケールアップされず、結果として WCF 応答時間が長くなる可能性があります。 受信した要求の実行時間と数に応じて、WCF の実行時間が、要求を処理したり、受信負荷を維持するのに十分な IOCP スレッドがプロセスによって作成されるまで、受信した要求ごとに約 500 ミリ秒だけ線形に増加することがあります。 この問題は、実行時間が長いサービスでより明確です。 IOCP スレッド プールのスケーラビリティの問題は、通常、プロセスの初期読み込み時には確認されません。

原因

WCF サービスの機能に影響を与える 3 つの変数は、受信要求とほぼ同じレートでスケールアップできます。

  1. WCF 調整

  2. .NET CLR Threadpool.GetMinThreads

  3. 調整値の前に受信要求ボリュームに対応するパターンで IOCP スレッドが作成されなくなった .NET CLR IOCP スレッド プールの Threadpool.GetMinThreads バグ。

この記事では、.NET IOCP スレッド プール #3 の問題を解決する方法について説明します。 WCF の調整または値が原因で調整の問題が発生した GetMinThreads 場合、このソリューションはそれらのスロットルを回避しません。 シナリオの特定に関するガイダンスについては、以下の 「詳細情報 」セクションを参照してください。 IOCP スレッド作成バグは、.NET Frameworkの次のリリース 4.0 以降で対処する必要があります。 このスケーラビリティの問題は、.NET CLR Worker スレッド プールには存在しません。

解決方法

WCF サービスの実行を別のスレッド プールに移動すると、このソリューションを実装する少量のオーバーヘッドが発生する可能性があります。 パフォーマンスの結果は、WCF サービスによって異なります。 個々の結果について各 WCF サービスをテストします。

注:

WCF サービス コードの完了を待機している間に受信スレッドをブロックしない WCF リスナーを使用する場合は、このソリューションを適用します。

WCF リスナー 推奨されるソリューション
HTTP 同期モジュール (既定値は 3.x) - 統合アプリケーション プールで使用されます 非同期ハンドラーに切り替えてから、この記事のソリューションを適用するか、またはプライベート スレッド プールを使用します。 (この表のリンクを参照してください)
HTTP 非同期モジュール (既定値は 4.x) - 統合アプリケーション プールで使用されます この記事のコード ソリューションを適用します。
ISAPI - クラシック モード アプリケーション プールで使用されます プライベート スレッド プールを適用します。 (この表のリンクを参照してください)
tcp.Net この記事のコード ソリューションを適用します。

上記の表に従ってこの記事のソリューションを適用できない場合、プライベート スレッド プールを使用する例は、MSDN の記事「 Foundations: Synchronization Contexts in WCF」で確認できます。

.NET CLR Worker スレッド プールで WCF サービスを実行するこのソリューションを実装する手順:

  1. WCF 調整のしきい値は、許容可能な応答時間内に予想されるバースト ボリュームを処理するのに十分な高さである必要があります。

  2. WCF サービスに .NET CLR の既定のスレッド プール (Worker または IOCP) のいずれかを使用する場合は、同時に実行すると予想される数の最小スレッド数 (スレッド作成調整が開始される値) を確認する必要があります。

  3. サービスに次のコードを実装し、.NET CLR Worker スレッド プールで WCF サービスを実行します。

    このクラスは、実行を .NET CLR Worker スレッド プールに移動するために使用されます。

    public class WorkerThreadPoolSynchronizer : SynchronizationContext
    {
        public override void Post(SendOrPostCallback d, object state)
        {
         // WCF almost always uses Post
            ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
        }
    
        public override void Send(SendOrPostCallback d, object state)
        {
         // Only the peer channel in WCF uses Send
            d(state);
        }
    }
    

    次に、カスタム属性クラスを作成する必要があります。

    [AttributeUsage(AttributeTargets.Class)]
    public class WorkerThreadPoolBehaviorAttribute : Attribute, IContractBehavior
    {
        private static WorkerThreadPoolSynchronizer synchronizer = new WorkerThreadPoolSynchronizer();
    
        void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }
    
        void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }
    
        void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
        {
        dispatchRuntime.SynchronizationContext = synchronizer;
        }
    
        void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
        {
        }
    }
    

    次に、カスタム属性を WCF サービスに適用します。 例:

    [WorkerThreadPoolBehavior]
    public class Service1 : IService1
    {
        public string GetData(int value)
        {
            int iSleepSec = (value * 1000);
            System.Threading.Thread.Sleep(iSleepSec);
            return string.Format("You slept for: {0} seconds", value);
        }
    }
    

詳細

WCF では、WCF サービス コードを実行するために .NET CLR IOCP スレッド プールが使用されます。 この問題は、.NET CLR IOCP スレッド プールが、要求のバーストをすぐに処理するのに十分な速さでスレッドを作成できない状態になったときに発生します。 新しいスレッドが 500 ミリ秒あたり 1 の割合で作成されると、応答時間が予期せず増加します。

WCF サービスが .NET CLR IOCP スレッド プールも利用するテクノロジを使用している場合、この問題がより明確になる可能性があります。 たとえば、Windows Server AppFabric キャッシュ クライアントでは、このスレッド プールを小さな範囲で利用します。

前述の WCF 調整制限に達していない場合は、.NET CLR IOCP スレッド プールで問題が発生しているかどうかを判断するのに役立つ情報を次に示します。

.NET CLR スレッド プールでは、値を使用して、スレッドの作成の調整を開始するタイミングを決定します。 この設定は、 ThreadPool.GetMinThreads(Int32, Int32) メソッド を呼び出すか、 を使用してプロセス ダンプを分析することによって決定できます。SOS デバッガー拡張機能 SOS.dll (SOS デバッグ拡張機能)

0:000> !C:\windows\Microsoft.NET\Framework64\v4.0.30319\sos.threadpool
CPU 使用率: 0%
ワーカー スレッド: 合計: 16 実行: 0 アイドル: 16 MaxLimit: 250 MinLimit: 125
キューでの作業要求: 0
タイマーの数: 35
完了ポート スレッド:合計: 26 Free: 0 MaxFree: 16 CurrentLimit: 28 MaxLimit: 1000 MinLimit: 125

観察される問題は、.NET CLR IOCP スレッド プールが、スレッド プールの MinLimit 値の前に新しいスレッドが 500 ミリ秒 (1 秒あたり 2 つ) ごとに作成される条件に入ったときです。 スレッド作成の遅延にも影響を与える可能性があるその他の予期される要因は、メモリ負荷や CPU 使用率の高さです。

WCF サービスをホストしているプロセスを監視します。 設定した最小しきい値より前にスレッドのスケールアップに関する問題が発生した場合は、.NET CLR IOCP スレッド プールで問題が発生している可能性があります。 これが該当するかどうかを判断するには、パフォーマンスを使用して、受信要求率と比較してプロセス スレッドの作成率を監視する必要があります。 これを実現するには、次のパフォーマンス カウンターをログに記録または表示します (HTTP バインドを使用して IIS (WAS) でホストされる WCF 4.0 サービスの例を次に示します)。

カウンター インスタンス
プロセス/スレッド数 すべての W3WP(x) インスタンス
HTTP サービス要求キュー/到着率 <ApplicationPool WCF サービスのホスト>
ASP.NET Apps v(4 または 2) / 実行中の要求 <WCF アプリケーション インスタンス>
ASP.NET Apps v(4 または 2) / 要求の実行時間 <WCF アプリケーション インスタンス>

WCF パフォーマンス カウンターも有効にしている場合は、次のように使用できます。

WCF パフォーマンス カウンター

到着率 (クライアント要求パターン) が同じパターンに従っているときに、スレッド数が徐々に増加するのが通常です。 これは、受信要求が即時に急増し、スレッド数が 1 秒あたり 2 スレッドの割合で徐々に増加し、WCF 応答時間が増加して問題が発生した場合のみです。

このスクリーンショットは、しばらくして .NET IOCP スレッド プールのスケーラビリティの問題が発生したワーカー プロセスを示しています。 プロセスが最初に開始されると、通常、IOCP スレッドは受信要求の読み込みに並行して作成されます。 この AppPool (W3WP.EXE) では、2 つの WCF サービスが実行されていました。 1 つのサービスでは、10:22:14 と 10:23:34 に 100 要求のバーストを受信した既定の .NET IOCP スレッド プールを使用していました。 2 つ目の WCF サービスでは、上記の回避策を使用して .NET Worker スレッド プールで実行し、10:22:54 に 100 要求のバーストを受信しました。 この状態に入った後、IOCP スレッド プールを機能するスケーラブルな状態に復元するには、プロセスのリサイクルが必要です。

スレッド プールのスケーラビリティが検出された後のワーカー プロセスを示すスクリーンショット。