使用Perl将并发附加到同一文件
我需要升级Perl CGI脚本,其中用户必须完成3个步骤。完成每个步骤后,脚本将记录用户完成的步骤。记录这一点很重要,因此我们可以向用户证明,例如,他们只完成了第一步,没有完成所有三个步骤 现在,脚本正在为CGI脚本的每个实例创建一个日志文件。因此,如果UserA执行步骤1,那么UserB执行步骤1,然后执行步骤2,然后执行步骤3-然后UserA完成步骤2和步骤3,那么日志文件的顺序将是使用Perl将并发附加到同一文件,perl,concurrency,locking,Perl,Concurrency,Locking,我需要升级Perl CGI脚本,其中用户必须完成3个步骤。完成每个步骤后,脚本将记录用户完成的步骤。记录这一点很重要,因此我们可以向用户证明,例如,他们只完成了第一步,没有完成所有三个步骤 现在,脚本正在为CGI脚本的每个实例创建一个日志文件。因此,如果UserA执行步骤1,那么UserB执行步骤1,然后执行步骤2,然后执行步骤3-然后UserA完成步骤2和步骤3,那么日志文件的顺序将是 LogFile.UserA.Step1 LogFile.UserB.Step1 LogFile.UserB.
LogFile.UserA.Step1
LogFile.UserB.Step1
LogFile.UserB.Step2
LogFile.UserB.Step3
LogFile.UserA.Step2
LogFile.UserA.Step3
日志文件以当前时间戳、随机数和进程PID命名
这可以很好地防止同一个文件被多次写入,但目录很快就会得到数千个文件(每个文件中只包含几个字节)。有一个循环和压缩这些日志的过程,但我必须这样做,以便脚本每天只记录一个文件,以减少正在创建的日志文件的数量
基本上,日志文件的文件名中将包含当前日期,只要CGI脚本需要写入日志,它就会附加到当天的一个日志文件中,而不管用户或他们在执行什么步骤
不需要读取日志文件-唯一会发生在日志文件上的事情是CGI脚本的附加。日志循环将在7天或更早的日志文件上运行
我的问题是,处理此日志文件的并发附件的最佳方法是什么?附加之前是否需要锁定它?我在Perl Monks上发现,这似乎表明“当多个进程写入同一个文件,并且所有进程都打开该文件进行附加时,数据不会被覆盖。”
我明白了,仅仅因为它可以做到并不意味着我应该这样做,但在这种情况下,什么是最安全的,最好的做法是这样做的
总结:
- 并发附加到同一文件
- 每个附加到文件的内容只有一行,少于50个字符
- 秩序无关紧要
flock
是解决该问题的可靠且相当简单的解决方案。我建议你简单地使用它。你可以尝试使用文件锁定,但这会很快将你带到受伤的地方。更简单的方法是使用一个小的持久化进程或cron作业来扫描日志文件目录,并将事件一次一个地附加到日志文件中
为了更安全,您可以让日志脚本每隔一段时间(比如5分钟)创建一个新的日志文件,并让守护进程忽略小于5分钟的文件。我想我应该运行一个单独的进程,例如使用Net::daemon或类似的程序,以集中的方式处理日志条目的写入。CGI脚本实例将通过套接字将日志字符串传递给此守护进程。您有几个选项,按复杂性的增长顺序排列: 1) 每一行都有时间和邮戳。当需要检查合并的文件时,可以交错所有输入文件 2) 编写一个始终运行的脚本,使所有文件句柄保持打开状态,并使用select()查找包含新数据的文件,并按接收顺序将其转储到输出。这个方法可能会成为资源占用者,因为它会不断地调用select,然后查找新文件,然后打开新文件,然后再次调用select
3) 编写一个接受TCP连接的脚本。如果您最终遇到这样一种情况,即记录器每次打开的日志文件比操作系统中的进程所能支持的要多,那么您就回到了解决方案1。老实说,选择1号。是的,使用
flock
下面是一个示例程序,从典型的前端开始:
#! /usr/bin/perl
use warnings;
use strict;
use Fcntl qw/ :flock /;
然后,我们指定日志路径和将运行的客户端数:
my $log = "/tmp/my.log";
my $clients = 10;
要记录消息,请在追加模式下打开文件,以便所有写入操作都自动结束。然后调用flock
等待轮到我们以独占方式访问日志。我们起床后,写下消息并关闭把手,它会自动释放锁
sub log_step {
my($msg) = @_;
open my $fh, ">>", $log or die "$0 [$$]: open: $!";
flock $fh, LOCK_EX or die "$0 [$$]: flock: $!";
print $fh "$msg\n" or die "$0 [$$]: write: $!";
close $fh or warn "$0 [$$]: close: $!";
}
现在fork
off$clients
子进程以随机间隔完成以下三个步骤:
my %kids;
my $id = "A";
for (1 .. $clients) {
my $pid = fork;
die "$0: fork: $!" unless defined $pid;
if ($pid) {
++$kids{$pid};
print "$0: forked $pid\n";
}
else {
my $user = "User" . $id;
log_step "$user: Step 1";
sleep rand 3;
log_step "$user: Step 2";
sleep rand 3;
log_step "$user: Step 3";
exit 0;
}
++$id;
}
不要忘记等待所有孩子退出:
print "$0: reaping children...\n";
while (keys %kids) {
my $pid = waitpid -1, 0;
last if $pid == -1;
warn "$0: unexpected kid $pid" unless $kids{$pid};
delete $kids{$pid};
}
warn "$0: still running: ", join(", " => keys %kids), "\n"
if keys %kids;
print "$0: done!\n", `cat $log`;
样本输出:
[...]
./prog.pl: reaping children...
./prog.pl: done!
UserA: Step 1
UserB: Step 1
UserC: Step 1
UserC: Step 2
UserC: Step 3
UserD: Step 1
UserE: Step 1
UserF: Step 1
UserG: Step 1
UserH: Step 1
UserI: Step 1
UserJ: Step 1
UserD: Step 2
UserD: Step 3
UserF: Step 2
UserG: Step 2
UserH: Step 2
UserI: Step 2
UserI: Step 3
UserB: Step 2
UserA: Step 2
UserA: Step 3
UserE: Step 2
UserF: Step 3
UserG: Step 3
UserJ: Step 2
UserJ: Step 3
UserE: Step 3
UserH: Step 3
UserB: Step 3
[...]
/prog.pl:收获孩子。。。
/prog.pl:完成!
UserA:步骤1
用户B:步骤1
UserC:步骤1
用户C:步骤2
用户C:步骤3
UserD:步骤1
用户E:步骤1
UserF:步骤1
UserG:步骤1
UserH:步骤1
UserI:步骤1
UserJ:步骤1
用户D:步骤2
用户D:步骤3
UserF:步骤2
UserG:步骤2
UserH:步骤2
UserI:步骤2
UserI:步骤3
用户B:步骤2
UserA:第2步
UserA:步骤3
用户E:步骤2
UserF:步骤3
UserG:步骤3
UserJ:步骤2
UserJ:步骤3
用户E:步骤3
UserH:步骤3
用户B:步骤3
请记住,每次运行的顺序都不同。我敦促Log::Log4Perlgbacon做到这一点是正确的,但在修改代码时要记住一点:您不解锁(
锁定_UN
)文件,而是关闭它。这将确保数据被刷新,然后解锁。谢谢。秩序并不重要,所以这不是问题。我不完全确定我是否需要插手我的案子。由于这是一个CGI脚本(不是快速CGI-它不会保持活动状态),用户在脚本生命周期内只能执行一个步骤-一旦完成一个步骤,脚本将退出。然后,在web上,他正在处理第2步,点击提交,第2步将被记录,脚本将退出。@BrianH