2009-12-08 6 views
5

Estoy buscando una buena estrategia para hacer frente a interbloqueos de bases de datos desde una aplicación Java 6; varios hilos paralelos podrían, potencialmente, escribir en la misma tabla al mismo tiempo. La base de datos (Ingres RDMBS) matará aleatoriamente una de las sesiones si detecta un interbloqueo.Multithreading: evitar y ocuparse de los interbloqueos de la base de datos

¿Cuál sería una técnica aceptable para hacer frente a la situación de estancamiento, dados los siguientes requisitos?

  • el tiempo total transcurrido debe mantenerse tan pequeño como sea razonablemente posible
  • matando a una sesión incurrirá en un significativa (medible) deshacer
  • hilos de tiempo no tienen manera de
    comunicarse entre sí, es decir la estrategia debe ser autónomo

hasta ahora, la estrategia que se me ocurrió es algo a lo largo de estas líneas:

short attempts = 0; 
boolean success = false; 
long delayMs = 0; 

Random random = new Random(); 
do { 
    try { 
     //insert loads of records in table 'x' 
     success = true; 
    } catch (ConcurrencyFailureException e) { 
     attempts++; 
     success = false; 
     delayMs = 1000*attempts+random.nextInt(1000*attempts); 

     try { 
       Thread.sleep(delayMs); 
      } catch (InterruptedException ie) { 
     } 
    } 
} while (!success); 

¿Se puede mejorar de alguna manera? p.ej. esperando una cantidad fija (número mágico) de segundos. ¿Existe una estrategia diferente que produzca mejores resultados?

Nota: Se usarán varias técnicas de nivel de base de datos para garantizar que los interbloqueos sean, en la práctica, muy raros. Además, la aplicación intentará evitar la programación de hilos que escriben en la misma tabla al mismo tiempo. La situación anterior será solo un "peor de los casos".

Nota: La tabla en la que se insertan los registros está organizada como una tabla dividida en heap y no tiene índices; cada hilo insertará registros en su propia partición.

Respuesta

10

Un enfoque comúnmente utilizado es alguna forma de retroceso exponencial. En lugar de su aproximación 1000*attempts+random, haga que el retraso sea una función exponencial del número de intentos. Esto garantiza una latencia mínima en los primeros o dos intentos, donde puede haber sido mala suerte que haya un punto muerto, pero le da retrasos mucho mayores más adelante, cuando está claro que la conexión está realmente congestionada.

Por supuesto, otro enfoque sería tratar de organizar los accesos a su base de datos para que los interbloqueos sean menos probables. Pero sin saber lo que hacen sus consultas (y cómo, y cuándo se ejecutan), es imposible decir si se puede hacer

+0

La función exponencial suena bien - ¡Intentaré simularla! Como mencioné en la nota, el diseño de la aplicación tendrá como objetivo evitar interbloqueos, incluida la organización favorable del acceso a la base de datos. Pero no hay garantía de que, en casos de esquina, se produzca un gran número de interbloqueos. – Adrian

+0

¿Puede proporcionar alguna referencia técnica para la técnica de "retroceso exponencial" que menciona? – Adrian

+2

Se usa comúnmente en protocolos de red para evitar la congestión. Consulte el artículo de la wiki: http://en.wikipedia.org/wiki/Exponential_backoff Pero la idea básica es simple. Simplemente usa algún tipo de función exponencial para determinar la demora en cada intento de reintento. Los detalles exactos se pueden modificar para que se ajusten a sus propósitos. Por supuesto, la implementación más simple posible sería retrasar por 2^n ms donde n es el número de intentos hasta ahora.Pero tal vez pienses que, para empezar, crece demasiado lento o comienza demasiado bajo o crece demasiado rápido. Luego, simplemente agrega un multiplicador o agrega algo a n – jalf

1

Así lo hicimos. Haz un bucle y vuelve a intentar la transacción hasta que finalice.

No nos metimos con retrasos aleatorios.

Además, hicimos la confirmación dentro del bloque try y la reversión en el manejador de excepciones.

Cuando tiene múltiples recursos bloqueables y múltiples transacciones simultáneas, el bloqueo es inevitable. Es una consecuencia lógica de contención para bloqueos.

Si evita la contención de bloqueos (es decir, bloqueo pesimista a nivel de tabla), también tiende a evitar la simultaneidad. Si puede definir transacciones que no contengan bloqueos, puede evitar el interbloqueo. El acceso concurrente a la misma tabla, sin embargo, es más o menos la definición de punto muerto.

Al cargar, las inserciones (especialmente en una tabla HEAP) pueden (a menudo) proceder en paralelo sin muchos problemas de contención. Si demora la creación de los índices, no hay otras actualizaciones durante el inserto.

Por lo tanto, puede evitar descartar los índices, cambiar la organización a un montón, cargar con múltiples procesos concurrentes (o subprocesos, generalmente es más rápido tener múltiples procesos), luego generar sus índices (y posiblemente reorganizar la mesa), es posible que pueda evitar interbloqueos.

Al hacer actualizaciones o eliminar, no ayuda mucho.

+0

En mis simulaciones (20 hilos) una demora cero provocará una gran cantidad de interbloqueos; un gran retraso realmente mejorará significativamente el tiempo total transcurrido. autocommit está activado, pero un punto muerto de la base de datos también implicará una reversión automática – Adrian

+0

@Adrian: tuvimos un manejo de errores bastante complejo, por lo que hicimos el rollback "solo para estar seguros". También estábamos trabajando en C, por lo que fue una "excepción virtual". Finalmente, tuvimos algunos C/Ingres n00bs haciendo la codificación, así que lo hicimos en caso de que hubieran ocultado pequeños errores de lógica. –

+0

@Adrian: En nuestra práctica real, la presencia de la ausencia de un retraso no hizo ninguna diferencia práctica. Un sistema operativo muy cargado presenta sus propios retrasos aleatorios. YMMV. No estoy debatiendo tu simulación. Te estoy diciendo lo que * hicimos *. No nos metimos con retrasos aleatorios. –

1

Si no necesita tener acceso simultáneo a la base de datos, una solución simple podría ser eliminarla y usar una cola de procesamiento de tareas para actualizar la base de datos, serializando el acceso a la base de datos a través de la cola. Me doy cuenta de que esto introducirá un elemento asíncrono en su aplicación, por lo que no sería adecuado para la mayoría de las aplicaciones iniciadas por el usuario o aplicaciones web en línea, pero podría valer la pena considerarlo para una aplicación de tipo lote/fuera de línea (probablemente no sea la respuesta que busca) pues aunque).

+0

La cola de procesamiento ralentizará considerablemente la aplicación, evitando atascos a toda costa, no es una opción. El servidor de producción es un servidor Unix de 4 procesadores, debe manejar una cantidad de hilos paralelos fácil – Adrian

0

Con una base de datos como Ingres, siempre encontrará algunos puntos muertos, por lo que debe suponer cualquier inserción, actualización o eliminación fallará y tendrá una estrategia de reintento en su lugar (como en su ejemplo). Debe diseñar su base de datos para que la contención se minimice y los bloqueos solo ocurran raramente. Si continuamente las transacciones fallan incluso después de varios intentos, entonces esto es una señal de que tendrá que hacer un rediseño importante de la base de datos (o pasar a un sistema como Oracle donde generalmente es posible diseñar aplicaciones para evitar interbloqueos mediante el uso adecuado de bloqueo a nivel de fila).

+0

no hay problema, pero agregaría que las transacciones de serialización pueden ser más rápidas opción si está recibiendo demasiados bloqueos/retrocesos/reintentos –

+0

Mover a otro RDBMS como Oracle no es una opción para este proyecto. Se intentó la serialización y es bastante más lenta que una ejecución "normal", con un número bajo de interbloqueos – Adrian

0

¿cómo es esto?

short attempts = 0; 
boolean success = false; 
long delayMs = 0; 

Random random = new Random(); 
do { 
try { 
    synchronized(ClassName.class) { 
     //insert loads of records in table 'x' 
     } 

    success = true; 
} catch (ConcurrencyFailureException e) { 
    attempts++; 
    success = false; 
    delayMs = 1000*attempts+random.nextInt(1000*attempts); 

    try { 
        Thread.sleep(delayMs); 
      } catch (InterruptedException ie) { 
    } 
    } 
} while (!success); 
Cuestiones relacionadas