1

編集 私の質問が十分に明確に述べられていないことに気づき、大幅に編集しました。
これは少し自由回答形式の質問ですので、事前にお詫び申し上げます。

簡単に言えば、IIS スタイルの非同期要求処理を Azure ワーカー ロールに実装したいと考えています

それは非常に単純かもしれませんし、非常に難しいかもしれません - 私は研究する場所へのポインタを探しています.

今回の実装では Azure ワーカーとサービス バス キューを使用しますが、一般的な原則は、ワーカー プロセスが着信要求をリッスンして処理するシナリオすべてに適用できます。

IIS の機能

IIS には、固定サイズのスレッドプールがあります。すべてのリクエストを同期的に処理する場合、並行して処理できるリクエストの最大数 == maxthreads. ただし、リクエストを処理するために低速の外部 I/O を実行する必要がある場合、これは非常に非効率的です。これは、サーバーがアイドル状態になる可能性があるため、外部 I/O が完了するのを待ってすべてのスレッドが拘束される可能性があるためです。

MSDNから:

Web サーバーでは、.NET Framework は、ASP.NET 要求の処理に使用されるスレッドのプールを維持します。リクエストが到着すると、そのリクエストを処理するためにプールからスレッドがディスパッチされます。要求が同期的に処理される場合、要求が処理されている間、要求を処理するスレッドはブロックされ、そのスレッドは別の要求を処理できません。

スレッド プールは、多くのブロックされたスレッドを収容するのに十分な大きさにすることができるため、これは問題にならない可能性があります。ただし、スレッド プール内のスレッドの数は制限されています。複数の同時実行時間の長い要求を処理する大規模なアプリケーションでは、使用可能なすべてのスレッドがブロックされる可能性があります。この状態は、スレッド スターベーションと呼ばれます。この条件に達すると、Web サーバーは要求をキューに入れます。要求キューがいっぱいになると、Web サーバーは HTTP 503 ステータス (Server Too Busy) で要求を拒否します。

この問題を克服するために、IIS には、要求を非同期で処理できる巧妙なロジックがあります。

非同期アクションが呼び出されると、次の手順が発生します。

  1. Web サーバーは、スレッド プール (ワーカー スレッド) からスレッドを取得し、着信要求を処理するようにスケジュールします。このワーカー スレッドは、非同期操作を開始します。

  2. ワーカー スレッドは、別の Web 要求を処理するためにスレッド プールに返されます。

  3. 非同期操作が完了すると、ASP.NET に通知されます。

  4. Web サーバーは、スレッド プール (非同期操作を開始したスレッドとは別のスレッドである可能性があります) からワーカー スレッドを取得し、応答のレンダリングを含め、残りの要求を処理します。

ここで重要な点は、非同期リクエストが戻るときです。戻りアクションは、最初の着信リクエストを処理するスレッドの同じプールの 1 つで実行されるようにスケジュールされています。これは、システムが同時に実行する作業の量を制限していることを意味し、これを再現したいと考えています。

私がしたいこと

Azure Service Bus キューおよび場合によっては TCP ソケットで着信作業要求をリッスンする Worker ロールを作成したいと考えています。IIS のように、スレッドプールの最大サイズを設定し、ワーカーが並行して実行する実際の作業量を制限したいと考えています。ワーカーが既存のリクエストを処理するのに忙しい場合 (新しい受信リクエストであろうと、以前の非同期呼び出しからのコールバックであろうと)、いくつかのスレッドが解放されるまで、新しい受信リクエストを取得したくありません。

同時に開始するジョブの数を制限することは問題ではありません。これは簡単に制御できます。実際に同時に作業している数を制限しています。

100 スレッドのスレッドプールを想定しましょう。

  • 電子メールを送信するための 100 の要求が届き、各電子メールが SMTP サーバーに送信されるのに 5 秒かかります。サーバーが同時に 100 個のリクエストのみを処理するように制限すると、CPU が完全にアイドル状態になるまで、サーバーは 5 秒間他の操作を行うことができなくなります。したがって、「リクエスト処理時間」の 99% が外部 I/O の待機に費やされ、サーバーがまだ非常に静かであるため、同時に 1,000 通または 10,000 通のメールを送信し始めてもかまいません。したがって、着信リクエストを制限なしで受け入れ続けることで対処できる特定のシナリオ (または、非同期呼び出しを開始するまでリクエストの開始を制限するだけです。BeginSend が呼び出されるとすぐに、戻って開始します)別のリクエストを処理します)。

  • 代わりに、データベースにアクセスしてデータを読み取り、重い計算を行ってからデータベースに書き戻すタイプのリクエストがあるとします。非同期にする必要がある 2 つのデータベース リクエストがありますが、リクエスト処理時間の 90% がワーカーに費やされます。したがって、上記と同じロジックに従って非同期呼び出しを開始し、スレッドを続行するために必要なことは何でもリターンに任せると、サーバーが非常に過負荷になることになります。

どういうわけか、IIS が行うことは、非同期呼び出しが返されたときに同じ固定サイズのスレッド プールが使用されるようにすることです。これは、大量の非同期呼び出しを開始し、それらが戻ってスレッドの使用を開始した場合、IIS はそれらの戻りが完了するまで新しい要求を受け入れないことを意味します。特に複数の負荷分散されたサーバーとサーバーが作業を選択するキューシステムがある場合は、サーバーに適切な負荷がかかるため、これは完璧です。

私は、これを行うのは非常に簡単かもしれないという卑劣な疑いを持っています.私が欠けている基本的なものがあります. あるいは、めちゃくちゃ難しいのかもしれません。

4

6 に答える 6

1

スレッドプールの作成は、WindowsAzureから独立していると見なす必要があります。ワーカーロールインスタンスは事実上Windows2008Server R2(またはSP2)であるため、実際に違いはありません。OnStart()またはから設定する必要がありますRun()

あなたがやりたかったことの1つは、より多くの/より少ないワーカーインスタンスにスケーリングするときの決定要因としてキューの長さを使用することでした。サービスバスキューはキューの長さをアドバタイズしないことに注意してください。WindowsAzureキュー(ストレージとサービスバスに基づく)はアドバタイズします。Windows Azureキューでは、メッセージを同期的にポーリングする必要があります(Service Busキューには長いポーリング操作があります)。おそらく、ここでServiceBusキューとWindowsAzureキューの違いを確認することをお勧めします。

于 2012-05-30T00:14:43.243 に答える
1

過去にSmartThreadPoolプロジェクトをインスタンスごとのプールとして使用したことがあります。正しく読んでいれば、必要なコールバックとワーカー制限機能がすべて含まれているはずです。私の会社では、メッセージ バス要求を非同期的に読み取るという正確な目的のために、現在 Azure で実行しています。

于 2012-05-30T23:34:21.477 に答える
1

私はこれを掘り下げてきましたが、実際には比較的簡単であることがわかりました。 http://www.albahari.com/threading/には良い情報があり、実際にその Web サイトが宣伝している本を購入することになりました。

私が見つけたのはそれです。

  • アプリケーションにはデフォルトで利用可能な ThreadPool があります
  • ThreadPool で使用可能なスレッドの数を制限できます
  • ThreadPool の Thread で使用QueueUserWorkItemまたはTask.Factory.StartNew開始するジョブを実行する場合
  • Begin...フレームワーク (メソッドなど)で非同期 IO 呼び出しの 1 つを使用するとWebcClient.DownloadStringAsync、コールバックも ThreadPool のスレッドで実行されます (IO 要求自体で何が起こるかは、この説明の範囲外です)。

ここまでは順調ですね。問題は、私が好きなだけ呼び出しを続けることができTask.Factory.StartNew、ThreadPool は、それらにサービスを提供する空きスレッドができるまで、単純に作業をキューに入れることです。したがって、Azure ワーカーの場合、ワーカーが既存の要求 (および既存の要求からのコールバック) の処理で忙しくても、キューを簡単に空にすることができます。それが私の問題の核心です。私が望むのは、実際にリクエストを処理するための空きスレッドがいくつかできるまで、キューから何も取り出さないことです。

これは、これを実現する方法の非常に簡単な例です。本質的にAutoResetEvent、前のタスクが実際に開始されるまでキューから別のタスクを開始しないようにするために を使用しています。確かに、空きスレッドができる前に実際にキューから何かを取り出しますが、全体として、これによりワーカーの異常な過負荷が回避され、より多くのワーカーを起動して負荷を共有できるようになります。

ThreadPool.SetMaxThreads(5, 1000); // Limit to 5 concurrent threads
ThreadPool.SetMinThreads(5, 10); // Ensure we spin up all threads

var jobStart = new AutoResetEvent(true);

// The "listen" loop
while (true) 
{   
    var job = this.jobQueue.Dequeue();
    jobStart.WaitOne(); // Wait until the previous job has actually been started
    Task.Factory.StartNew(
        () =>
            {
                jobStart.Set(); // Will happen when the threadpool allocates this job to a thread
                this.Download(job);
            });

}

これは、タイムアウトを設定したり、妥当な時間内にスレッドを割り当てられない場合に作業項目をキューに戻したりするなど、より洗練されたものにすることができます (おそらくそうするべきです)。別の方法は、キューのリッスンを開始する前に空きスレッドがあるかどうかを確認するために使用することThreadPool.GetAvailableThreadsですが、それはかなりエラーが発生しやすいと感じます。

于 2012-06-04T14:21:33.687 に答える
1

専用の WCF インスタンス (ホストされている WAS または IIS ではない) を使用して、長時間実行される要求をバッファリングすることを検討しましたか? 独自の専用アプリ プールがあり、ASP.NET HTTP 要求と競合しない IIS とは別の最大値設定があります。(HTTP リクエストは

次に、IIS 非同期メソッドを使用して、制限されたアプリ プールで WCF を呼び出します。

于 2012-05-30T21:27:03.377 に答える
0

私が理解していることから、特定のタイプのメッセージを同時に処理するために使用されるスレッドの数を制限したいと考えています。

1つのアプローチは、新しいスレッドで呼び出されたメッセージプロセッサを次のようなもので単純にラップすることです

try
{
   Interlocked.Increment(ref count)

   Process(message);
}
finally 
{
    Interlocked.Decrement(ref count)
}

ラッパーを呼び出す前に、「カウント」がしきい値カウントより小さいかどうかを確認してください。カウントが十分に少なくなるまで、それ以上のメッセージのポーリング/処理を停止します。

編集コメントに基づいて詳細情報を追加

Frans さん、インフラストラクチャとビジネス コードが結合されている理由がわかりません。非同期で実行する新しいスレッドでタスクとしてサービスされるビジネス プロセスを配置すると、追加の IO バウンド呼び出しを非同期で実行することについて心配する必要はありません。これは、プログラミングがより簡単なモデルです。

これが私が考えていることです。

// semi - pseudo-code

// Infrastructure – reads messages from the queue 
//    (independent thread, could be a triggered by a timer)
while(count < maxCount && (message = Queue.GetMessage()) != null)
{
    Interlocked.Increment(ref count);

  // process message asynchronously on a new thread
  Task.Factory.StartNew(() => ProcessWrapper(message));     
}

// glue / semi-infrastructure - deals with message deletion and exceptions 
void ProcessWrapper(Message message)
{
   try
   {
      Process(message);
      Queue.DeleteMessage(message);
   }
   catch(Exception ex)
   {
      // Handle exception here.
      // Log, write to poison message queue etc ...
   }
   finally 
   {
      Interlocked.Decrement(ref count)
   }
}

// business process
void Process(Message message)
{
  // actual work done here
  ;
}
于 2012-05-30T00:41:25.093 に答える