使用Perl访问BerkeleyDB的正确方法是什么?

时间:2011-04-22 06:22:57

标签: database perl deadlock berkeley-db strawberry-perl

我在使用BerkeleyDB时遇到了一些问题。我有多个相同代码的实例指向一个DB文件的存储库,一切运行正常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'),然后将值设置为busy'1',然后对其执行某些操作,然后将DB条目完全移动到另一个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,它就会被锁定,如下所示:

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 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 个答案:

答案 0 :(得分:4)

但是,我的db_get命令是自由浮动的,没有锁定,因为我不认为读取需要锁定。

这个假设是错误的。正如http://pybsddb.sourceforge.net/ref/lock/page.html所说,BerkeleyDB必须在内部发出读锁定,否则如果读者试图读取从其下面更改的数据,则可能会出现未定义的行为。因此,读取很容易成为陷入僵局的一部分。

在游标存在的情况下尤其如此。读取游标会在光标关闭之前对已读取的所有内容进行锁定。有关详细信息,请参阅http://pybsddb.sourceforge.net/ref/lock/am_conv.html以了解陷入僵局的方法(实际上您甚至可以自行解锁)。

答案 1 :(得分:3)

简而言之,您需要进行死锁检测。我可以看到两种可能性。首先,您可以使用db_deadlock utility。其次,也许更方便的是,你可以在打开你的环境时指定-LockDetect标志,这个标志在Perl docs for BerkeleyDB.pm中没有详细解释。

在版本4.5.20中,这两种方式似乎都适用于我。 (那么你的version是什么?)

现在详细说明。

指定-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死锁检测接口,只要有多个线程或进程访问数据库并且至少有一个正在修改它。 / p>

我得出的结论是,通过重复执行两个编写器和一个读取器的测试,两种方式都能正常工作,这会在数据库中快速连续(每秒100个)放入新条目时会死锁几次,或者去通过数据库中所有键的光标。

标记方法似乎很快就能解决死锁问题,但在我的测试中并没有引起注意。

另一方面,使用脚本在并行的详细输出中运行db_deadlock实用程序是有益的,因为您可以看到它们如何阻塞,然后在锁定器中止后继续,尤其是当与{{3 }}:

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

我缺乏解释所有细节的专业知识,但你可以看到,在被阻止的情况下,某些条目存在,而在其他情况下却没有。另请参阅db_stat utility中标题为Berkeley DB Concurrent Data Store locking conventionsIWRITE是什么?)的部分。

你在问这些死锁是如何发生的。不能确切地说,但我确实看到他们 发生并发访问。你也问他们是如何解决的。我不知道。在我的测试场景中,被阻止的脚本将会挂起。也许在你的场景中有人在你不知道的情况下运行了死锁检测?

为了完整性,您的应用程序可能只是挂起,因为线程在退出之前没有关闭资源。如果您只是按Ctrl-C进程并且没有用于关闭资源的清理处理程序,则可能会发生。但这似乎不是你的问题。

如果它确实成为您的问题,您应该查看参考指南中Berkeley DB Programmer's Reference Guide上的部分。

CDS和DS没有恢复的概念。由于CDS和DS不支持事务并且不维护恢复日志,因此它们无法运行恢复。如果数据库在DS或CDS中损坏,则只能将其删除并重新创建。 (从Handling failure in Data Store and Concurrent Data Store applications开始逐字逐句。)

最后,Oracle网站上有视频教程,包括Berkeley DB Book by Himanshu Yadava

答案 2 :(得分:1)

虽然不是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();
}