每个进程都独立拥有自己的资源,包括内存页、文件指针等等,同时,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) |