Каков правильный способ доступа к BerkeleyDB с Perl?

у меня были некоторые проблемы с использованием BerkeleyDB. У меня несколько экземпляров одного и того же кода указали на один репозиторий файлов БД, и все работает нормально в течение 5-32 часов, а затем внезапно возникает тупик. Командные подсказки останавливаются непосредственно перед выполнением вызова db_get, db_put или cursor creation. Поэтому я просто спрашиваю, Как правильно отвечать на эти звонки. Вот мой общий план:

это как окружающая среда и DBs создано:

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

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

один экземпляр этого кода запускается, переходит на сайт и сохраняет URL-адреса для анализа другим экземпляром (у меня установлен флаг, чтобы каждая БД была заблокирована, когда она заблокирована):

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

он периодически проверяет, находится ли определенное количество элементов в Несортированном:

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

перед добавлением любого элемента в любую БД он сначала проверяет все БД, чтобы увидеть, присутствует ли этот элемент:

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

следующий код приходит после, и многие его экземпляры запускаются параллельно. Сначала он получает следующий элемент в unsorted (который не имеет значения busy '1'), затем устанавливает значение busy '1', затем делает что-то с ним, затем полностью перемещает запись DB в другую DB (она удаляется из unsorted и хранится в другой 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;

и в любом другом месте, если я вызываю db_put или db_del на любом DB, он обертывается блокировкой так:

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

однако мои команды db_get свободно плавают с никакого замка, потому что я не думаю, что чтение нуждается в замке.

Я просмотрел этот код миллион раз, и алгоритм герметичен. Поэтому мне просто интересно, реализую ли я какую-либо часть этого неправильно, неправильно использую замки и т. д. Или, если есть лучший способ предотвратить блокировку (или даже диагностировать блокировку) с помощью BerkeleyDB и Strawberry Perl?

обновление: чтобы быть более конкретным, проблема возникает на сервере Windows 2003 (1.5 GB RAM, не уверен, что это важно). Я могу запустить всю эту установку на моей машине Windows 7 (4GB RAM). Я также начал печатать статистику блокировки, используя следующее:

добавление этого флага в создание среды:

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

и затем вызывая это каждые 60 секунд:

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

статус всегда возвращается как 0, что является успехом. Вот последний отчет о статистике:

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

которого я опасаюсь это:

8   Number of deadlocks

как возникли эти тупики и как они были разрешены? (все части кода все еще работают). Что такое тупик в данном случае?

3 ответов


короче говоря, вам нужно сделать обнаружение взаимоблокировки. Я вижу две возможности для этого. Во-первых, вы можете использовать db_deadlock утилиты. Во-вторых, и, возможно, более удобно, вы можете указать -LockDetect флаг при открытии вашей среды, флаг, который точно не объясняется подробно в Perl docs для BerkeleyDB.pm.

оба способа, похоже, отлично работают для меня в версии 4.5.20. (Каков ваш версия, у способ?)

теперь о деталях.

задание -LockDetect флаг-это действительно так. Есть несколько ценностей на выбор. Я выбрал DB_LOCK_DEFAULT и он, казалось, работал просто отлично. С большим количеством подсказок о том, что происходит, вы, безусловно, можете получить больше фантазии.

под управлением db_deadlock утилиты можно сделать так:

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

вот цитата из db_deadlock руководство:

эта утилита должна запускаться как фоновый демон или базовые интерфейсы обнаружения взаимоблокировок Berkeley DB должны вызываться каким-либо другим способом, когда есть несколько потоков или процессов, обращающихся к базе данных, и по крайней мере один из них изменяет ее.

я пришел к выводу, что оба способа работают нормально, неоднократно выполняя тест с двумя писателями и одним читателем, который будет тупик пару раз, помещая новые записи в базу данных в быстрой последовательности (100 в секунду), или просматриваем курсором все ключи в базе данных.

метод флага, похоже, очень быстро справляется с тупиками, они не стали заметными в моих тестах.

С другой стороны, работает db_deadlock утилита с подробным выводом параллельно со сценариями поучительна тем, что вы видите, как они блокируют, а затем продолжают после того, как локеры были прерваны, особенно в сочетании с db_stat утилиты:

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

мне не хватает опыт, чтобы объяснить все детали, но вы можете видеть, что в заблокированных ситуациях есть определенные записи, а в других нет. Также смотрите раздел под названием соглашения о блокировке параллельного хранилища данных Berkeley DB(что составляет IWRITE?) в справочник программиста Berkeley DB.

вы спрашиваете, как эти тупики случаются. Не могу сказать точно, но я вижу, что они are происходит с параллельным доступом. Вы также спрашиваете, как они были разрешены. Я понятия не имею. В моих тестовых сценариях заблокированные сценарии просто зависают. Может быть, в вашем сценарии кто-то запустил обнаружение тупика без вашего ведома?

для полноты, ваше приложение может просто зависание потому что поток не закрыл ресурсы перед выходом. Может произойти, если вы просто Ctrl-C процесс и нет обработчика очистки на месте, чтобы закрыть ресурсы. Но это не ваш проблема.

если это станет вашей проблемой, вы должны просмотреть раздел на обработка сбоев в хранилище данных и параллельных приложениях хранилища данных в справочнике.

компакт-диски и DS не имеют понятия восстановления. Поскольку CDS и DS не поддерживают транзакции и не поддерживают журнал восстановления, они не могут запускать восстановление. Если база данных повреждена в DS или компакт-дисках, ее можно только удалить и воссоздать. (Взято moreless дословно с Беркли DB книга Химаншу Ядава.)

наконец, на сайте Oracle есть видеоуроки, в том числе один на использование компакт-дисков Марго Зельцер.


однако мои команды db_get свободно плавают без блокировки, потому что я не думаю, что чтение нуждается в блокировке.

это предположение неверно. Как http://pybsddb.sourceforge.net/ref/lock/page.html говорит, BerkeleyDB должен выдавать блокировки чтения внутри, потому что в противном случае вы можете получить неопределенное поведение, если читатель попытается прочитать данные, которые были изменены из-под него. Поэтому чтение может легко быть частью в тупике ситуация.

Это особенно верно в присутствии курсоры. Курсоры чтения поддерживают блокировку всего, что было прочитано, до тех пор, пока курсор не будет закрыт. См.http://pybsddb.sourceforge.net/ref/lock/am_conv.html для получения более подробной информации о способах, которыми вы можете попасть в тупик (на самом деле вы даже можете заблокировать себя).


хотя это не решение BerkeleyDB, вы можете использовать альтернативную блокировку, хотя Win32::Mutex, который использует базовые мьютексы Windows. Очень простой пример ниже:

#!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();
}