Как правильно получить доступ к BerkeleyDB с помощью Perl? - PullRequest
6 голосов
/ 22 апреля 2011

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

Вот как создаются среда и БД:

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";

Один экземпляр этого кода запускается, переходит на сайт и сохраняет 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";
....

Этот следующий код идет после, и многие его экземпляры выполняются параллельно. Во-первых, он получает следующий элемент в несортированном виде (у которого нет занятого значения '1'), затем устанавливает значение в занятое '1', затем что-то делает с этим, затем полностью перемещает запись БД в другую БД (это удалено из несортированного и сохранено в другой БД):

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 для ЛЮБОЙ БД, он будет заблокирован следующим образом:

print "\n\nBad.\n\n";
        $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 ГБ ОЗУ, не уверен, если это важно). Я могу запустить всю эту настройку на моем компьютере с Windows 7 (4 ГБ ОЗУ). Я также начал распечатывать статистику блокировки, используя следующее:

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

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

4 голосов
/ 02 мая 2011

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

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

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

3 голосов
/ 04 мая 2011

Короче говоря, вам нужно сделать обнаружение тупика. Я вижу две возможности сделать это. Во-первых, вы можете использовать утилиту db_deadlock . Во-вторых, и, возможно, более удобно, вы можете указать флаг -LockDetect при открытии вашей среды, флаг, который не совсем подробно описан в документах Perl для для 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 в секунду) или продолжались. через курсор всех ключей в базе данных.

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

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

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 .

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

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

Если это действительно становится вашей проблемой, вам следует ознакомиться с разделом Обработка ошибок в приложениях Data Store и Concurrent Data Store в Справочном руководстве.

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

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

1 голос
/ 02 мая 2011

Хотя это не решение 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();
}
...