Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5379805
  • 博文数量: 1144
  • 博客积分: 11974
  • 博客等级: 上将
  • 技术积分: 12312
  • 用 户 组: 普通用户
  • 注册时间: 2005-04-13 20:06
文章存档

2017年(2)

2016年(14)

2015年(10)

2014年(28)

2013年(23)

2012年(29)

2011年(53)

2010年(86)

2009年(83)

2008年(43)

2007年(153)

2006年(575)

2005年(45)

分类: LINUX

2009-08-30 16:49:26

   每个进程都独立拥有自己的资源,包括内存页、文件指针等等,同时,linux系统提供了多种进程之间的交互方式,比较简单的方式就是写到文件里,DB_File就是一种解决方案。
 
#!/usr/bin/perl
# test create of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-13
 
use strict;
use warnings;
use DB_File;
 
my %hash;
my $file_name = "test_btree";
unlink $file_name;
 
tie(%hash, 'DB_File', $file_name, O_CREAT|O_RDWR, 0666, $DB_BTREE)
    || die "Cannot open $file_name: $!\n";
 
## == Add some info ==
$hash{"me"} = "lianming";
$hash{"else"} = "nothing";
$hash{"job"} = "monitor";
 
print "Print as hash:\n";
foreach my $key (keys (%hash)) {
        print "$key->$hash{$key}\n";
}
print "End\n";
 
untie %hash;
 
    执行结果为:
 
Print as hash:
else->nothing
job->monitor
me->lianming
End
 
    tie命令,本来是用于变量绑定,在DB_File中,是将一个哈希变量,和一个文件绑在了一起,简单的来讲,我们可以认为变量'%hash',是存在这个文件中的,所以我们对这个哈希变量进行的所有修改,它都会在文件中做相应的操作。
    tie的参数,第一个为要绑定的变量
        第二个为要绑定的文件
        第三个为flag(是否创建,只读,只写,可读写)
        第四个为权限(和linux文件的权限类似,不过没有执行,最大是6)
        第五个为存储引擎,有三种,hash、btree、recno,一般来讲,hash和btree是存储key/value类型数据的,而recno是顺序存储,相当于数组。使用上的区别,请看下文。
 
    在DB_File中添加信息,只要直接给哈希变量添加信息即可,和一般的赋值是一样的。
    DB_File也提供了类似BerkeleyDB的方法来添加对象,要将tie的结果返回给一个值,例如$db,然后利用$db->put($key, $value)的方式来添加,这种办法我不是很习惯用。
 
    同理,在DB_File中读取信息,就直接从哈希变量中读取就ok了。
 
    这样,我们就已经把信息存在了文件中,下次,如果另外一个进程要来修改这个文件,直接打开即可。
 
#!/usr/bin/perl
# test read/write of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-10
 
use strict;
use warnings;
use DB_File;
 
my %hash;
my $file_name = "test_btree";
 
tie(%hash, 'DB_File', $file_name, O_WRONLY, 0666, $DB_BTREE)
    || die "Cannot open $file_name: $!\n";
 
## == Add some info ==
for (my $i = 0; $i < 10; $i ++) {
        $hash{$i} = $i."new";
}
 
print "Print as hash:\n";
foreach my $key (keys (%hash)) {
        print "$key->$hash{$key}\n";
}
print "End\n";
 
untie %hash;
 
    执行结果如下:
 
Print as hash:
0->0new
1->1new
2->2new
3->3new
4->4new
5->5new
6->6new
7->7new
8->8new
9->9new
else->nothing
job->monitor
me->lianming
End
 
    只要记住,打开文件的时候要和创建的时候采用同样的存储算法,否则会报无法打开文件的错。
 
    一般来讲,哈希是不允许存储重复键的,就是说,对同一个key,赋两次value,那么后一次赋值会将前一次赋的值给覆盖掉,但是btree是可以存储这样的信息的。
 
#!/usr/bin/perl
# test duplicate keys of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-10
 
use strict;
use warnings;
use DB_File;
 
my %hash;
my $file_name = "test_btree";
unlink $file_name;
 
# == enable duplicate records
$DB_BTREE->{'flags'} = R_DUP;
 
tie(%hash, 'DB_File', $file_name, O_CREAT|O_WRONLY, 0666, $DB_BTREE)
    || die "Cannot open $file_name: $!\n";
 
## == Add some info ==
$hash{'me'} = "Sangyb";
$hash{'me'} = "Lianming";
$hash{'me'} = "Here";
 
print "Print as hash:\n";
foreach my $key (keys (%hash)) {
        print "$key->$hash{$key}\n";
}
print "End\n";
 
untie %hash;
 
    执行结果如下:
 
Print as hash:
me->Sangyb
me->Sangyb
me->Sangyb
End
 
    问题出现了……虽然它存储了3个相同的key,但是我明明存储的是不同的value,打出来的却是相同的value。
    原因是一个叫做联想数组的东西,当你请求同样的key的时候,只会返回第一个value。所以,我们的数据其实已经存储好了,只是在读出来的时候,由于某些原因,出现了这样的问题。
   
    这个时候,就必须利用api来进行操作了。
    它提供了一个seq的函数,用来对btree中的对象进行指向:
 
#!/usr/bin/perl
# test duplicate keys of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-10
 
use strict;
use warnings;
use DB_File;
 
my %hash;
my $file_name = "test_btree";
unlink $file_name;
 
# == enable duplicate records
$DB_BTREE->{'flags'} = R_DUP;
 
my $db = tie(%hash, 'DB_File', $file_name, O_CREAT|O_WRONLY, 0666, $DB_BTREE)
    || die "Cannot open $file_name: $!\n";
 
my ($k, $v, $status);
 
## == Add some info ==
$hash{'me'} = "Sangyb";
$hash{'me'} = "Lianming";
$hash{'me'} = "Here";
 
print "Print as api:\n";
 
## == use api: seq ==
$k = $v = 0;
for ($status = $db->seq($k, $v, R_FIRST);
        $status == 0;
        $status = $db->seq($k, $v, R_NEXT)) {
        print "$k->$v\n";
}
 
print "End\n";
 
undef $db;
untie %hash;
 
    执行结果如下:
 
Print as api:
me->Sangyb
me->Lianming
me->Here
End
 
    在读取btree文件的时候,会有一个指针指向一个特定的key/value对,然后读取,写入的操作,就对这个指针指向的pair进行操作。seq函数就是控制这个指针的移动,它有三个参数:
    第一个,指针指向的pair的key
    第二个,指针指向的pair的value
    第三个,flag,调用函数时,指针做的操作:
        R_CURSOR:当前
        R_FIRST:最前面的一个pair
        R_LAST:最后的一个pair
        R_NEXT:下一个pair
        R_PREV:前一个pair
    如果调用失败了,就会返回0。
 
    针对duplicate key,还有别的几个实用的函数,例如get_dup,del_dup等。
    get_dup可以获取重复键的一些信息,del_dup可以删除特定的pair。
 
    当多个进程同时对一个文件进行操作的时候,必然要涉及到一个锁的问题,多个进程如果同时对一个文件进行写,那么肯定会挂掉,我们可以做一些测试。
 
    1、两个进程同时进行读操作
 
#!/usr/bin/perl
# test multi read of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-10
 
use strict;
use warnings;
use DB_File;
 
my %hash;
my $file_name = "test_btree";
unlink $file_name;
 
my $db = tie(%hash, 'DB_File', $file_name, O_CREAT|O_WRONLY, 0666, $DB_BTREE)
    || die "Cannot open $file_name: $!\n";
 
## == Add some info ==
for (my $i = 0; $i < 20; $i ++) {
        $hash{$i} = $i."test";
}
 
## == multi read ==
 
my $pid = fork();
 
if (!defined($pid)) {
        print "Fork error: $!\n";
        exit 1;
}
 
if ($pid == 0) {
        ## == child ==
        my $key;
 
        for (my $i = 0; $i < 1000; $i ++) {
                $key = int(rand()*20);
                print "Child: $key->$hash{$key}\n";
        }
        undef $db;
        untie %hash;
        exit 0;
} else {
        ## == parent ==
        my $key;
 
        for (my $i = 0; $i < 1000; $i ++) {
                $key = int(rand()*20);
                print "Parent: $key->$hash{$key}\n";
        }
}
 
undef $db;
untie %hash;
 
    执行结果是正常的,child和parent会交替出现,打出正确的读取结果。
 
...
Parent: 15->15test
Child: 1->1test
Parent: 5->5test
Child: 10->10test
Parent: 3->3test
Child: 5->5test
Parent: 4->4test
Child: 9->9test
Parent: 17->17test
Child: 17->17test
Parent: 9->9test
Child: 7->7test
Parent: 15->15test
Child: 2->2test
Parent: 15->15test
...
 
    2、两个进程同时写
 
#!/usr/bin/perl
# test multi write of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-10
 
use strict;
use warnings;
use DB_File;
 
my %hash;
my $file_name = "test_btree";
unlink $file_name;
 
my $db = tie(%hash, 'DB_File', $file_name, O_CREAT|O_WRONLY, 0666, $DB_BTREE)
    || die "Cannot open $file_name: $!\n";
 
## == multi read ==
 
my $pid_1 = fork();
 
if (!defined($pid_1)) {
        print "Fork error: $!\n";
        exit 1;
}
 
if ($pid_1 == 0) {
        ## == child ==
        my $key;
 
        for (my $i = 0; $i < 1000; $i ++) {
                $key = int(rand()*10);
                $hash{$key} = $key."abc";
        }
        undef $db;
        untie %hash;
        exit 0;
}
 
my $pid_2 = fork();
 
if (!defined($pid_2)) {
        print "Fork error: $!\n";
        exit 1;
}
 
if ($pid_2 == 0) {
        ## == child ==
        my $key;
 
        for (my $i = 0; $i < 1000; $i ++) {
                $key = int(rand()*10)+10;
                $hash{$key} = $key."abc";
        }
        undef $db;
        untie %hash;
        exit 0;
}
 
wait();
 
## == parent ==
my $cnt = 0;
 
foreach my $key (keys(%hash)) {
        print "$key->$hash{$key}\n";
        $cnt ++;
}
print "\ncount is $cnt\n";
 
undef $db;
untie %hash;
 
    执行结果如下:
 
10->10abc
11->11abc
12->12abc
13->13abc
14->14abc
15->15abc
16->16abc
17->17abc
18->18abc
19->19abc
 
count is 10
 
    我们本来是想让他有20个值的,结果只有第二个子进程的修改生效了,第一个子进程的修改并没有生效。
 
    我们可以得到一个结论,那就是DB_File可以支持多个进程同时读,但是无法同时写。
    具体原因,我只能猜测一下了,那就是tie,在文件开始的tie,是将文件内容和哈希变量绑定,我们可以认为,其实就是一个指针,指到了文件的开始位置,读的话,是互不干涉的,所以可以多个进程同时读。但是写的时候,也许写的操作并不是直接写文件,而是修改哈希的值,最后在untie,或者需要的时候,才会把对哈希的操作刷到文件中(也可以用$db->sync来实现),所以,虽然两个进程都对文件进行了写的操作,但是只有最后untie的一个才会生效。
    如果要测试的话,可以在第二个子进程undef之前,sleep几秒钟,就可以看到,生效的就是第一个进程的写入了。
    当然,这只是最简单的情况,我测试过多个进程同时对它进行大量的写操作,那最后文件就会乱七八糟,甚至有可能没法打开。
 
    这种情况下,是需要对文件上锁的,在锁住文件之后,才对它进行tie,在解锁之前,要进行untie。有两个给DB_File上锁的包,就拿DB_File::Lock来做例子:
 
#!/usr/bin/perl
# test lock of DB_File
# create by lianming: 2009-08-10
# last modify by lianming: 2009-08-10
 
use strict;
use warnings;
use DB_File;
use DB_File::Lock;
use Fcntl qw(:flock O_RDWR O_CREAT);
 
unlink $file_name;
 
my $pid_1 = fork();
 
if (!defined($pid_1)) {
        print "Fork error: $!\n";
        exit 1;
}
 
if ($pid_1 == 0) {
        ## == child ==
        my $key;
 
        for (my $i = 0; $i < 1000; $i ++) {
                $key = int(rand()*12)+122;
 
                ## == lock and tie ==
                tie(%hash, 'DB_File::Lock', $file_name, O_CREAT|O_WRONLY, 0666, $DB_BTREE, "write") || die "Cannot open $file_name: $!\n";
                $hash{$key} = $key."abc";
                untie %hash;
        }
        exit 0;
}
 
my $pid_2 = fork();
 
if (!defined($pid_2)) {
        print "Fork error: $!\n";
        exit 1;
}
 
if ($pid_2 == 0) {
        ## == child ==
        my $key;
 
        for (my $i = 0; $i < 1000; $i ++) {
                $key = int(rand()*10)+10;
 
                ## == lock and tie ==
                tie(%hash, 'DB_File::Lock', $file_name, O_CREAT|O_WRONLY, 0666, $DB_BTREE, "write") || die "Cannot open $file_name: $!\n";
                $hash{$key} = $key."abc";
                untie %hash;
        }
        exit 0;
}
 
wait();
 
## == parent ==
 
tie(%hash, 'DB_File::Lock', $file_name, O_CREAT|O_RDONLY, 0666, $DB_BTREE, "read") || die "Cannot open $file_name: $!\n";
 
my $cnt = 0;
 
foreach my $key (keys(%hash)) {
        print "$key->$hash{$key}\n";
        $cnt ++;
}
print "\ncount is $cnt\n";
 
untie %hash;
 
    执行结果为:
 
10->10abc
11->11abc
12->12abc
122->122abc
123->123abc
124->124abc
125->125abc
126->126abc
127->127abc
128->128abc
129->129abc
13->13abc
130->130abc
131->131abc
132->132abc
133->133abc
14->14abc
15->15abc
16->16abc
17->17abc
18->18abc
19->19abc
 
count is 22
 
    这就是我们想要的结果。在tie的时候,同时上锁,在untie的时候解锁。和DB_File的tie参数一样,最后加一个read或者write就ok
    锁有两种,read和write,read锁可以多个进程同时持有,但是write锁只能一个进程持有。
 
    如果说btree和hash的存储算法,是相当于把哈希变量存在了文件中,那recno就是把数组变量存在了文件中,recno因为用的不多,也没有具体看过。
    对DB_File的性能做一些测试,具体测试代码和结果就不写了,如果大家有兴趣可以自己去搞一下,只写一下测试的结果。
    1、测试随机的写性能:分别用两种引擎,单进程对一个文件随机写入100w条数据,测试结果如下:
        HASH:耗时53s,文件大小为83M
        BTREE:耗时12s,文件大小为120M
    可见写的话,btree性能稍好,但是占用空间较大。
    2、测试随机读的性能:分别用两种引擎,单进程对一个文件随机写入100w条数据,测试结果如下:
        HASH:耗时28s
        BTREE:耗时28s
    读性能两个引擎差不多。
    3、并发性能:
    a、用BTREE引擎,开启10个进程,每个进程对一个文件写入1w条数据,写入每条数据之前加锁,写完后解锁。测试结果耗时1000多s,可见它的并发性是何等的差劲……
    b、用BTREE引擎,单进程,对一个文件写入10w条数据,写入每条数据前加锁,写完后解锁。测试结果是2分钟。
 
    可见,单进程的读写,还是很快的,但是涉及到加锁后就很慢很慢,慢的主要是tie和untie的过程,这部分的过程涉及到文件的读写,没有任何并发的机制,所以只有等每次锁了之后才可以tie和untie,这部分的时间消耗是很可观的。并发的问题,其实有另外的办法可以解决,下一篇文档就讨论一种并发性很高的文件存储策略。
阅读(2188) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~