2012-01-06 25 views
8

私は、スレッド化にTPLライブラリを使用している.NET 4 C#サービスを持っています。 1つの接続が処理用のボトルネックになっていたため、最近は接続プーリングも使用するように切り替えました。マルチスレッドサービスでのデータベース接続プーリング

以前は、接続オブジェクトのスレッドの安全性を制御するためにlock句を使用していました。作業がバックアップされると、キューはタスクとして存在し、多くのスレッド(タスク)がロック句を待機します。現在、ほとんどのシナリオでは、スレッドはデータベースIO上で待機し、プロセスを非常に高速に処理します。

ただし、接続プールを使用しているので、新しい問題があります。接続の最大数に達すると(デフォルトは100)、さらに接続が要求されるとタイムアウトが発生します(Pooling info参照)。この場合、「接続要求がタイムアウトしました」という例外がスローされます。

私のすべてのIDisposablesはステートメントを使用しています。私は接続を正しく管理しています。このシナリオは、プールが処理できる(期待されている)よりも多くの作業が要求されているために発生します。私はこの例外がなぜスローされるのか理解し、それを処理する方法を認識しています。単純なリトライはハックのように感じます。また、接続文字列を使用してタイムアウト時間を増やすこともできますが、これは固いソリューションのようには感じられません。以前の設計(プーリングなし)では、アプリケーション内のロックのために作業項目が処理されていました。

このシナリオを処理してすべての作業が確実に処理されるようにするにはどうすればよいでしょうか?あなたは次のようにParallel.ForEach()方法を使用して並列度を制御するために見ることができる

+0

あなたはあなたのコードの一部を投稿することができますか?タスクの作成方法とスケジューリング方法を確認すると便利です。 –

+0

タスクは、おおよそ次のように作成されます。Task.Factory.StartNew(()=> ProcessItem(item));私はタスクの作成を制限するのに十分なタスクオブジェクトを追跡していません(それがあなたが考えていたルートの場合)。すべてのタスクがデータベース作業を必要とするわけではなく、すべてのデータベース作業が同じデータベースを使用しているわけではありません(多くの異なるoracle、mssqlなどがあります)。 –

+0

これは負荷の急上昇を処理するためのものですか、または要求が絶えず速すぎて処理できないのですか?後者の場合、ある時点で敗北を認め、失敗を返さなければなりません。 –

答えて

10

もう1つの方法は、プールから接続を取得するコードの周りにsemaphoreを使用することです(うまくいけば返す)。 sempahoreはlockステートメントと似ていますが、1つだけでなく、同時に複数のリクエスタを設定できる点が異なります。

このような何かを行う必要があります

//Assuming mySemaphore is a semaphore instance, e.g. 
// public static Semaphore mySemaphore = new Semaphore(100,100); 
try { 
    mySemaphore.WaitOne(); // This will block until a slot is available. 
    DosomeDatabaseLogic(); 
} finally { 
    mySemaphore.Release(); 
} 
+0

私はこれが好きです、私はそれが(潜在的に)それほど簡単ではなかったことを認識しませんでした。私はこれを試して報告します。 –

+1

もちろん、これにはいくつかの危険性があります。具体的には、セマフォはリエントラントを知らないため、DoSomeDatabaseLogic()コールが再帰的にセマフォを再入力しようとすると、デッドロックが発生する可能性があります。たとえば、100スレッドが既にセマフォスロットを使用しているとします。その後、各スレッドは再入力を試み、空きスロットで待ってブロックします(これは起こりません)。可能性とコードを慎重に認識しておく必要があります。 –

+0

ここで使用しているDoSomeDatabaseLogicの部分は、1つの作業単位であり、現在はスレッドセーフであるため、問題ではないと思います。私のテストシナリオでは、私はこのセマフォを試してみましたが、実際に動作しています。これは私が探していたものです、ありがとう! –

2

:この場合

var items = ; // your collection of work items 
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 100 }; 
Parallel.ForEach(items, parallelOptions, ProcessItem) 

を私は100に度を設定することを選んだが、あなたはのために理にかなって値を選択することができます現在の接続プールの実装。

この解決方法は、作業項目のコレクションが前面にあることを前提としています。ただし、着信Webリクエストなどの外部メカニズムを使用して新しいタスクを作成している場合、例外は実際には良いことです。この時点では、作業項目を配置してワーカー・スレッドが使用可能になったときにそれらをポップすることができる同時キュー・データ構造を使用することをお勧めします。

+0

私はタスクを作成するためにTask.Factory.StartNew()を使用しています。 ParllelOptionsをパラメータとして受け入れるオーバーロードはありません。 –

+0

上記のアプローチは 'ProcessItem(item)'タスクを作成するために利用可能な 'item'オブジェクトのセットを想定しています。これはあなたのシナリオを代表するものではないかもしれません。上記のメカニズムは、 'Task.Factory.StartNew()'の代わりに 'Parallel.ForEach()'を使います。 –

+0

+1、私はAndyが投稿した同様の問題に遭遇し、私はParallel.ForEachを使用しています。これは私のために役立ちます。あなたが今言及するまで、私はそのような選択肢を知らなかった。 –

0

最も簡単な解決策は、失敗を返す前にリクエストをブロックしたい時間の長さに接続タイムアウトを増やすことです。 「あまりにも長い」時間があるはずです。

これは、タイムアウトのある作業キューとして接続プールを効果的に使用します。自分で実装しようとするよりもはるかに簡単です。接続プールが公平(FIFO)であることを確認する必要があります。

+0

私は質問でこれを考慮したことを言及しますが、私は助けることはできませんが、それを処理するための悪い方法だと感じています。それは間違いなく短期間で問題を解決しますが、実際の問題はサービスが処理作業に関して貪欲であるということでしょうか?私は助けることはできませんが、少し遅い場合、それが完全に働いた1つの接続で考えを振る。処理速度の改善が大きすぎて、1つの接続に戻ることはできません。 –

関連する問題