2012-01-06 13 views
8

Ho un servizio .NET 4 C# che utilizza le librerie TPL per il threading. Di recente l'abbiamo utilizzato anche per utilizzare il pool di connessioni, poiché una connessione stava diventando un collo di bottiglia per l'elaborazione.Database pooling pooling con servizio multi-thread

In precedenza, stavamo usando una clausola di blocco per controllare la sicurezza del thread sull'oggetto di connessione. Poiché il lavoro eseguiva il backup, la coda esisteva come attività e molti thread (attività) sarebbero in attesa sulla clausola di blocco. Ora, nella maggior parte degli scenari, i thread attendono l'I/O del database e i processi di lavoro MOLTO più velocemente.

Tuttavia, ora che sto usando il pool di connessioni, abbiamo un nuovo problema. Una volta raggiunto il numero massimo di connessioni (100 di default), se vengono richieste ulteriori connessioni, si verifica un timeout (vedere Pooling info). Quando ciò accade, viene generata un'eccezione dicendo "Timeout della richiesta di connessione".

Tutti i miei IDisposables utilizzano le istruzioni e gestisco correttamente le mie connessioni. Questo scenario si verifica a causa della richiesta di più lavoro rispetto a quella che il pool può elaborare (che è previsto). Capisco perché questa eccezione è lanciata e sono consapevole dei modi di gestirla. Un semplice tentativo si sente come un hack. Mi rendo anche conto che posso aumentare il periodo di timeout tramite la stringa di connessione, tuttavia non mi sembra una soluzione solida. Nel progetto precedente (senza raggruppamento), gli elementi di lavoro venivano elaborati a causa del blocco all'interno dell'applicazione.

Qual è un buon modo di gestire questo scenario per garantire che tutto il lavoro venga elaborato?

+0

puoi pubblicare alcuni dei tuo codice? Sarebbe utile vedere come stai creando e pianificando le attività. –

+0

Le attività vengono create più o meno così: Task.Factory.StartNew (() => ProcessItem (item)); Non sto tenendo traccia degli oggetti compito abbastanza da limitare la creazione di compiti (se è questa la strada che stavi pensando). Non tutte le attività richiedono il lavoro del database e non tutto il lavoro del database utilizza lo stesso database (ce ne sono molte diverse, oracle, mssql, ecc.). –

+0

È in grado di gestire i sovraccarichi in fase di caricamento, oppure le richieste vengono costantemente inviate troppo velocemente per essere elaborate? Se è il caso, dovrai ammettere la sconfitta e il fallimento a un certo punto. –

risposta

10

Un altro approccio consiste nell'utilizzare uno semaphore attorno al codice che recupera le connessioni dal pool (e, si spera, li restituisce). Un sempahore è come un'istruzione di blocco, tranne per il fatto che consente un numero configurabile di richiedenti alla volta, non solo uno.

Qualcosa del genere dovrebbe fare:

//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

Mi piace, non avevo realizzato che fosse (potenzialmente) così facile. Proverò questo e riferirò. –

+1

Ci sono, naturalmente, alcuni pericoli nel farlo. In particolare, i semafori ignorano il re-entrancy, quindi se la tua chiamata a DoSomeDatabaseLogic() può ricorsivamente cercare di rientrare nel semaforo, hai il potenziale di deadlock. Come esempio, immagina che 100 thread stiano già usando slot per semafori. Quindi ciascuno dei thread tenta di rientrare e blocca l'attesa su uno slot libero (che non accadrà mai). È necessario essere consapevoli della possibilità e del codice attentamente. –

+0

La parte DoSomeDatabaseLogic che sto utilizzando qui è un'unità di lavoro, ed è al momento thread-safe, quindi non penso che dovrebbe essere un problema. Nel mio scenario di test, ho provato questo semaforo e funziona davvero. Questo è quello che stavo cercando, grazie! –

2

Si potrebbe cercare di controllare il grado di parallelismo utilizzando il metodo Parallel.ForEach() come segue:

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

In questo caso ho scelto di impostare il livello a 100, ma è possibile scegliere un valore che ha senso per l'attuale implementazione del pool di connessioni.

Questa soluzione presuppone ovviamente che sia disponibile una raccolta di articoli di lavoro. Se, tuttavia, stai creando nuovi compiti tramite un meccanismo esterno come le richieste web in ingresso, l'eccezione è in realtà una buona cosa. A quel punto suggerirei di utilizzare la struttura di dati di coda simultanea in cui è possibile posizionare gli elementi di lavoro e inserirli come thread di lavoro disponibili.

+0

Sto usando Task.Factory.StartNew() per creare i miei compiti. Non vedo un sovraccarico che accetti ParllelOptions come parametro. –

+0

L'approccio precedente presuppone un set disponibile di oggetti 'item' per creare attività' ProcessItem (item) '. Questo potrebbe non essere rappresentativo del tuo scenario. Il meccanismo sopra usa 'Parallel.ForEach()' al posto di 'Task.Factory.StartNew()'. –

+0

+1, mi sono imbattuto nel problema simile che Andy ha pubblicato e sto usando Parallel.ForEach. Questo è utile per me. Non conoscevo questa opzione fino a quando non la menzioni ora. –

0

La soluzione più semplice consiste nell'aumentare il timeout della connessione per il periodo di tempo in cui si desidera bloccare una richiesta prima di restituire un errore. Ci deve essere un certo periodo di tempo che è "troppo lungo".

Questo utilizza efficacemente il pool di connessioni come una coda di lavoro con un timeout. È molto più facile che provare a implementare uno tu stesso. Dovresti controllare che il pool di connessione sia corretto (FIFO).

+0

Ho menzionato che ho considerato questo nella domanda, tuttavia non posso fare a meno di pensare che sia un cattivo modo per gestirlo. Sicuramente risolverà il problema a breve termine, ma forse il vero problema potrebbe essere che il servizio è avido rispetto all'elaborazione del lavoro? Non posso fare a meno di scuotere l'idea che con 1 connessione funzionasse perfettamente, anche se un po 'lento. Il miglioramento della velocità di elaborazione è troppo grande per tornare a una connessione. –

Problemi correlati