2011-04-22 13 views
6

He tenido algunos problemas con el uso de BerkeleyDB. Tengo varias instancias del mismo código apuntando a un único repositorio de archivos de base de datos, y todo funciona bien durante 5-32 horas, luego, de repente, hay un interbloqueo. Las instrucciones del comando se detienen justo antes de ejecutar una llamada de creación de cursor o db_get o db_put. Así que simplemente estoy preguntando por la forma correcta de manejar estas llamadas. Aquí está mi disposición general:¿Cuál es la forma correcta de acceder a BerkeleyDB con Perl?

Así es como se crean el medio ambiente y DB:

my $env = new BerkeleyDB::Env ( 
    -Home => "$dbFolder\\" , 
    -Flags => DB_CREATE | DB_INIT_CDB | DB_INIT_MPOOL) 
    or die "cannot open environment: $BerkeleyDB::Error\n"; 

my $unsortedHash = BerkeleyDB::Hash->new (
    -Filename => "$dbFolder/Unsorted.db", 
    -Flags => DB_CREATE, 
    -Env => $env 
    ) or die "couldn't create: $!, $BerkeleyDB::Error.\n"; 

Una sola instancia de este código se ejecuta, va a un sitio y guarda las direcciones URL para ser analizados por otra instancia (no tengo el conjunto de bandera de modo que cada DB está bloqueado cuando uno está bloqueado):

 $lk = $unsortedHash->cds_lock(); 
     while(@urlsToAdd){ 
      my $currUrl = shift @urlsToAdd; 
      $unsortedHash->db_put($currUrl, '0'); 
     } 
     $lk->cds_unlock(); 

se comprueba periódicamente si un cierto número de artículos están en Unsorted:

$refer = $unsortedHash->db_stat(); 
$elements = $refer->{'hash_ndata'}; 

Antes de añadir cualquier elemento a cualquier DB, primero comprueba todos los DBs para ver si ese elemento ya está presente:

if ($unsortedHash->db_get($search, $value) == 0){ 
    $value = "1:$value"; 
}elsif ($badHash->db_get($search, $value) == 0){ 
    $value = "2:$value"; 
.... 

Este siguiente código viene después, y muchos casos de que se ejecutan en paralelo. Primero, obtiene el siguiente ítem sin clasificar (que no tiene el valor ocupado '1'), luego establece el valor en ocupado '1', luego hace algo con él, luego mueve la entrada DB completamente a otro DB (es removidos de clasificar y almacenar en otro DB):

my $pageUrl = ''; 
my $busy = '1'; 
my $curs; 
my $lk = $unsortedHash->cds_lock(); #lock, change status to 1, unlock 
########## GET AN ELEMENT FROM THE UNSORTED HASH ####### 
while(1){ 
    $busy = '1'; 
    $curs = $unsortedHash->db_cursor(); 
    while ($busy){ 
     $curs->c_get($pageUrl, $busy, DB_NEXT); 
     print "$pageUrl:$busy:\n"; 
     if ($pageUrl eq ''){ 
      $busy = 0; 
     } 
    } 
    $curs->c_close(); 
    $curs = undef; 

    if ($pageUrl eq ''){ 
     print "Database empty. Sleeping...\n"; 
     $lk->cds_unlock(); 
     sleep(30); 
     $lk = $unsortedHash->cds_lock(); 
    }else{ 
     last; 
    } 
} 

####### MAKE THE ELEMENT 'BUSY' AND DOWNLOAD IT 


$unsortedHash->db_put($pageUrl, '1'); 
$lk->cds_unlock(); 
$lk = undef; 

y en cualquier otro lugar, si llamo db_put o db_del en cUALQUIER DB, se envuelve con una cerradura de este modo:

print "\n\nBad.\n\n"; 
     $lk = $badHash->cds_lock(); 
     $badHash->db_put($pageUrl, '0'); 
     $unsortedHash->db_del($pageUrl); 
     $lk->cds_unlock(); 
     $lk = undef; 

Sin embargo , mis comandos db_get flotan libremente sin bloqueo, porque no creo que la lectura necesite un bloqueo.

He revisado este código un millón de veces y el algoritmo es hermético. Entonces, me pregunto si estoy implementando alguna parte de este error, utilizando mal las cerraduras, etc. O si hay una forma mejor de evitar el bloqueo (o incluso diagnosticar el bloqueo) con BerkeleyDB y Strawberry Perl.

ACTUALIZACIÓN: Para ser más específicos, el problema se produce en un servidor Windows 2003 (1,5 GB de RAM, no estoy seguro si esto es importante). Puedo ejecutar bien toda esta configuración en mi máquina con Windows 7 (4 GB de RAM). También empecé a imprimir las estadísticas de bloqueo utilizando la siguiente:

La adición de este indicador a la creación medio ambiente:

-MsgFile => "$dbFolder/lockData.txt" 

Y luego llamar a esto cada 60 segundos:

my $status = $env->lock_stat_print(); 
print "Status:$status:\n"; 

el estado es siempre devuelto como 0, que es éxito.Aquí está el último informe de estadísticas:

29 Last allocated locker ID 
0x7fffffff Current maximum unused locker ID 
5 Number of lock modes 
1000 Maximum number of locks possible 
1000 Maximum number of lockers possible 
1000 Maximum number of lock objects possible 
40 Number of lock object partitions 
24 Number of current locks 
42 Maximum number of locks at any one time 
5 Maximum number of locks in any one bucket 
0 Maximum number of locks stolen by for an empty partition 
0 Maximum number of locks stolen for any one partition 
29 Number of current lockers 
29 Maximum number of lockers at any one time 
6 Number of current lock objects 
13 Maximum number of lock objects at any one time 
1 Maximum number of lock objects in any one bucket 
0 Maximum number of objects stolen by for an empty partition 
0 Maximum number of objects stolen for any one partition 
3121958 Total number of locks requested 
3121926 Total number of locks released 
0 Total number of locks upgraded 
24 Total number of locks downgraded 
9310 Lock requests not available due to conflicts, for which we waited 
0 Lock requests not available due to conflicts, for which we did not wait 
8 Number of deadlocks 
1000000 Lock timeout value 
0 Number of locks that have timed out 
1000000 Transaction timeout value 
0 Number of transactions that have timed out 
792KB The size of the lock region 
59 The number of partition locks that required waiting (0%) 
46 The maximum number of times any partition lock was waited for (0%) 
0 The number of object queue operations that required waiting (0%) 
27 The number of locker allocations that required waiting (0%) 
0 The number of region locks that required waiting (0%) 
1 Maximum hash bucket length 

de la que soy cuidado con esto:

8 Number of deadlocks 

¿Cómo se producen estos callejones sin salida, y cómo se resolvieron? (todas las partes del código todavía están en ejecución). ¿Qué es exactamente un punto muerto, en este caso?

Respuesta

3

En resumen, debe realizar la detección de interbloqueo. Puedo ver dos posibilidades para hacer eso. Primero, puede usar el db_deadlock utility. En segundo lugar, y quizás de manera más conveniente, puede especificar el indicador -LockDetect al abrir su entorno, un indicador que no se explica exactamente en profundidad en el Perl docs for BerkeleyDB.pm.

Ambas formas parecen funcionar bien para mí en la versión 4.5.20. (¿Cuál es su version, por cierto?)

Ahora para el detalle.

Especificar la bandera -LockDetect es realmente solo eso. Hay un par de valores para elegir. Elegí DB_LOCK_DEFAULT y parecía funcionar bien. Con más pistas sobre lo que está pasando, sin dudas podrías ser más elegante.

Ejecución de la utilidad db_deadlock podría hacerse así:

db_deadlock -h your/env/dir -v -t 3 # run as daemon, check every 3 seconds 
db_deadlock -h your/env/dir -v  # run once 

he aquí un fragmento del manual db_deadlock:

Esta utilidad se debe ejecutar como un demonio en el fondo, o el Berkeley subyacente DB las interfaces de detección de interbloqueos se deben llamar de otra forma, siempre que haya múltiples subprocesos o procesos que accedan a una base de datos y al menos uno de ellos esté modificándola.

yo llegamos a la conclusión de que ambas formas funcionan bien para la realización reiterada de una prueba con dos escritores y un lector, que sería un punto muerto un par de veces, mientras que poner nuevas entradas en la base de datos en una sucesión rápida (100 por segundo) , o pasando por un cursor de todas las teclas en la base de datos.

El método de bandera parece tratar con bloqueos muy rápidamente, no se notaron en mis pruebas.

Por otro lado, se ejecuta la utilidad db_deadlock con salida detallada en paralles con los guiones es instructiva en el que se ve cómo se bloquean y luego continúan después de los armarios han sido abortados, especialmente cuando se combina con la db_stat utility:

db_stat -Cl # Locks grouped by lockers 
db_stat -Co # Locks grouped by object 
db_stat -Cp # need_dd = 1 ? 
db_stat -CA # all of the above plus more 

Me falta la experiencia para explicar todos los detalles, pero se puede ver que en situaciones bloqueadas hay ciertas entradas allí, mientras que en otras no. Consulte también la sección titulada Berkeley DB Concurrent Data Store locking conventions (¿qué es IWRITE?) En el Berkeley DB Programmer's Reference Guide.

Usted está preguntando cómo ocurrieron estos bloqueos. No puedo decir exactamente, pero sí veo que son que se producen con acceso concurrente. También preguntas cómo se resolvieron. No tengo idea. En mis escenarios de prueba, los scripts bloqueados simplemente se bloquean. ¿Tal vez en su escenario alguien ejecutó la detección de interbloqueo sin que usted lo supiera?

Para completar, su aplicación podría simplemente colgar porque un hilo no ha cerrado los recursos antes de salir. Puede suceder si solo se usa Ctrl-C en un proceso y no hay ningún controlador de limpieza listo para cerrar los recursos. Pero ese no parece ser tu problema.

Si se convierte en su problema, debe revisar la sección en Handling failure in Data Store and Concurrent Data Store applications en la Guía de referencia.

CDS y DS no tienen ningún concepto de recuperación. Como CDS y DS no admiten transacciones y no mantienen un registro de recuperación, no pueden ejecutar la recuperación. Si la base de datos se corrompe en DS o CDS, solo puede eliminarla y volver a crearla. (Tomado moreless textualmente del Berkeley DB Book by Himanshu Yadava.)

Finalmente, hay videos tutoriales en el sitio de Oracle, incluyendo one on using CDS by Margo Seltzer.

1

Si bien no es una solución de BerkeleyDB, es posible que pueda usar un bloqueo alternativo a través de Win32 :: Mutex, que usa mutexes de Windows subyacentes. Un ejemplo muy simple es el siguiente:

#!perl -w 
use strict; 
use warnings; 

use Win32::Mutex; # from Win32::IPC 

my $mutex = Win32::Mutex->new(0, 'MyAppBerkeleyLock'); 

for (1..10) { 
    $mutex->wait(10*1000) or die "Failed to lock mutex $!"; 
    print "$$ has lock\n"; 
    sleep(rand(7)); 
    $mutex->release(); 
} 
+0

Estoy un poco confundido acerca del trabajo de Mutex, especialmente en relación con Berkeley, ¿podría dar un ejemplo que coloque un artículo en un DB usando el Mutex? ¿Cómo evita que otros procesos accedan al DB? –

+1

Es una "cosa" que tienen las ventanas, que cualquier subproceso en cualquier proceso en la máquina puede intentar bloquear. La llamada a la espera() bloquea hasta que el hilo tenga el bloqueo. Luego, harías todo el acceso a tu base de datos en lugar de mi llamada de sleep(). Esencialmente haces el bloqueo en lugar de BDB. Intenta ejecutar un par de procesos del script de arriba para ver cómo funciona. – Alex

4

Sin embargo, mis comandos db_get son de libre flotación sin cerradura, porque no creo que la lectura necesita una cerradura.

Esta suposición es incorrecta. Como dice http://pybsddb.sourceforge.net/ref/lock/page.html, BerkeleyDB tiene que emitir bloqueos de lectura internamente porque de lo contrario podría obtener un comportamiento indefinido si un lector intentara leer los datos que estaban siendo cambiados desde debajo de él. Por lo tanto, las lecturas pueden ser fácilmente parte de una situación de estancamiento.

Esto es particularmente cierto en presencia de cursores. Los cursores de lectura mantienen bloqueos en todo lo que se ha leído hasta que se cierra el cursor. Consulte http://pybsddb.sourceforge.net/ref/lock/am_conv.html para obtener más información sobre cómo puede llegar al punto muerto (de hecho, puede incluso entablar un bloqueo).

Cuestiones relacionadas