6

我在使用 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 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

这些死锁是如何发生的,又是如何解决的?(代码的所有部分仍在运行)。在这种情况下,究竟什么是死锁?

4

3 回答 3

4

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

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

在存在游标的情况下尤其如此。读取游标会锁定已读取的所有内容,直到游标关闭。有关更多详细信息,请参阅http://pybsddb.sourceforge.net/ref/lock/am_conv.html进入死锁的方法(实际上你甚至可以自己死锁)。

于 2011-05-02T05:30:34.283 回答
3

简而言之,您需要进行死锁检测。我可以看到两种可能性。首先,您可以使用该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与脚本并行运行具有详细输出的实用程序具有指导意义,因为您可以看到它们如何阻塞,然后在储物柜被中止后继续,尤其是与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 Programmer's Reference Guide中标题为Berkeley DB 并发数据存储锁定约定(什么是IWRITE?)的部分。

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

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

如果它确实成为您的问题,您应该查看参考指南中的处理数据存储和并发数据存储应用程序中的故障部分。

CDS 和 DS 没有恢复的概念。由于 CDS 和 DS 不支持事务并且不维护恢复日志,因此它们无法运行恢复。如果数据库在 DS 或 CDS 中损坏,您只能将其删除并重新创建。(从Himanshu Yadava 的 Berkeley DB Book 中逐字记录。)

最后,Oracle 网站上还有视频教程,包括Margo Seltzer 的关于使用 CDS 的视频教程。

于 2011-05-03T22:47:27.203 回答
1

虽然不是 BerkeleyDB 解决方案,但您可以通过使用底层 Windows 互斥锁的 Win32::Mutex 使用替代锁定。下面是一个非常简单的例子:

#!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();
}
于 2011-05-01T23:53:48.327 回答