2012-01-06 10 views
8

Tengo un servicio .NET 4 C# que está utilizando las bibliotecas TPL para enhebrar. Recientemente lo cambiamos para usar también la agrupación de conexiones, ya que una conexión se estaba convirtiendo en un cuello de botella para el procesamiento.agrupación de conexión de base de datos con servicio de subprocesos múltiples

Anteriormente, estábamos usando una cláusula de bloqueo para controlar la seguridad de la hebra en el objeto de conexión. Como el trabajo haría una copia de seguridad, la cola existiría como tareas, y muchos hilos (tareas) estarían esperando en la cláusula de bloqueo. Ahora, en la mayoría de los escenarios, los hilos esperan en la base de datos IO y los procesos de trabajo MUCHO más rápido.

Sin embargo, ahora que estoy usando la agrupación de conexiones, tenemos un problema nuevo. Una vez que se alcanza el número máximo de conexiones (100 por defecto), si se solicitan conexiones adicionales, hay un tiempo de espera (ver Pooling info). Cuando esto sucede, se lanza una excepción que dice "Tiempo de espera de solicitud de conexión agotado".

Todos mis IDisposables están dentro del uso de declaraciones, y estoy administrando adecuadamente mis conexiones. Este escenario ocurre debido a que se solicita más trabajo del que el grupo puede procesar (lo que se espera). Entiendo por qué se lanza esta excepción y estoy al tanto de las formas de manejarla. Un simple reintento se siente como un truco. También me doy cuenta de que puedo aumentar el tiempo de espera a través de la cadena de conexión, pero eso no parece una solución sólida. En el diseño anterior (sin agrupación), los elementos de trabajo se procesarían debido al bloqueo dentro de la aplicación.

¿Cuál es una buena forma de manejar este escenario para garantizar que todo el trabajo se procese?

+0

Se puede publicar algunos de su código? Sería útil ver cómo está creando y programando Tareas. –

+0

Las tareas se crean más o menos así: Task.Factory.StartNew (() => ProcessItem (item)); No estoy haciendo un seguimiento de los objetos de la tarea lo suficiente como para limitar la creación de tareas (si esa es la ruta que estaba pensando). No todas las tareas requieren el trabajo de la base de datos, y no todo el trabajo de la base de datos está utilizando la misma base de datos (hay muchas diferentes, oráculo, mssql, etc.). –

+0

¿Esto es para manejar sobrecargas en la carga, o las solicitudes están llegando constantemente demasiado rápido para que usted las procese? Si es el último, deberá admitir la derrota y el fracaso de retorno en algún momento. –

Respuesta

10

Otro enfoque es utilizar un semaphore alrededor del código que recupera las conexiones del grupo (y, con suerte, las devuelve). Un semparo es como un enunciado de bloqueo, excepto que permite un número configurable de solicitantes a la vez, no solo uno.

Algo como esto debería hacer:

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

Me gusta esto, no me di cuenta de que era (potencialmente) tan fácil como eso. Intentaré esto e informaré. –

+1

Existen, por supuesto, algunos peligros al hacer esto. Específicamente, los semáforos ignoran la reentrada, de modo que si su llamada DoSomeDatabaseLogic() puede intentar volver a ingresar de forma recursiva al semáforo, tiene la posibilidad de que se produzcan interbloqueos. Como ejemplo de eso, imagina que 100 hilos ya están usando ranuras de semáforo. Luego, cada uno de los hilos intenta volver a entrar, y bloquea la espera en una ranura libre (lo que nunca sucederá). Debe ser consciente de la posibilidad y el código cuidadosamente. –

+0

La parte DoSomeDatabaseLogic que estoy usando aquí es una unidad de trabajo, y actualmente es segura para subprocesos, por lo que no creo que deba ser un problema. En mi escenario de prueba, probé este semáforo y de hecho está funcionando. Esto es lo que estaba buscando, ¡gracias! –

2

Se podría buscar para controlar el grado de paralelismo utilizando el método Parallel.ForEach() de la siguiente manera:

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

En este caso he elegido para establecer el grado en el 100, pero se puede elegir un valor que tenga sentido para su implementación de conjunto de conexiones actual.

Por supuesto, esta solución supone que tiene una colección de elementos de trabajo por adelantado. Sin embargo, si está creando nuevas Tareas a través de algún mecanismo externo, como las solicitudes web entrantes, la excepción es realmente una buena cosa. En ese punto, le sugiero que haga uso de la estructura de datos de colas concurrente donde puede colocar los elementos de trabajo y abrirlos cuando los hilos de trabajo estén disponibles.

+0

Estoy usando Task.Factory.StartNew() para crear mis tareas. No veo una sobrecarga que acepte ParllelOptions como parámetro. –

+0

El enfoque anterior supone un conjunto disponible de objetos 'item' para crear tareas' ProcessItem (item) 'para. Esto puede no ser representativo de su escenario. El mecanismo anterior usa 'Parallel.ForEach()' en lugar de 'Task.Factory.StartNew()'. –

+0

+1, encontré el problema similar que Andy publicó y estoy usando Parallel.ForEach. Esto es útil para mi No sabía de esa opción hasta que lo menciones ahora. –

0

La solución más simple es aumentar el tiempo de espera de conexión al tiempo que está dispuesto a bloquear una solicitud antes de devolver la falla. Debe haber un período de tiempo que sea "demasiado largo".

Esto utiliza efectivamente el grupo de conexiones como una cola de trabajo con un tiempo de espera excedido. Es mucho más fácil que tratar de implementar uno tú mismo. Debería verificar que el grupo de conexiones sea justo (FIFO).

+0

Menciono que he considerado esto en la pregunta, sin embargo, no puedo evitar sentir que es una mala forma de manejarlo. Seguramente resolverá el problema a corto plazo, pero tal vez el verdadero problema podría ser que el servicio es codicioso con respecto al trabajo de procesamiento. No puedo evitar sacudir la idea de que con 1 conexión funcionó perfectamente, aunque un poco lento. La mejora de la velocidad de procesamiento es demasiado grande para volver a una conexión. –

Cuestiones relacionadas