Concurrent使用Perl附加到同一文件

时间:2010-03-02 18:28:44

标签: perl concurrency locking

我需要升级Perl CGI脚本,用户必须完成3个步骤。完成每个步骤后,脚本将记录用户完成的步骤。记录这一点很重要,因此我们可以向用户证明他们只完成了第一步,并没有完成所有三个步骤,例如。

现在,该脚本正在为CGI脚本的每个实例创建1个日志文件。因此,如果UserA执行步骤1,则UserB执行步骤1,然后执行步骤2,然后执行步骤3 - 然后UserA完成步骤2和步骤3,即日志文件的顺序。

LogFile.UserA.Step1
LogFile.UserB.Step1
LogFile.UserB.Step2
LogFile.UserB.Step3
LogFile.UserA.Step2
LogFile.UserA.Step3

日志文件以当前时间戳,随机数和进程PID命名。

这样可以防止同一个文件被多次写入,但是该目录可以快速获取数千个文件(每个文件只包含几个字节)。有一个循环和压缩这些日志的过程,但它已经落到我身上,因此脚本每天只记录一个文件,以减少正在创建的日志文件的数量。

基本上,日志文件将在文件名中包含当前日期,并且只要CGI脚本需要写入日志,它就会附加到当天的一个日志文件中,无论用户或步骤是什么是的。

没有什么需要读取日志文件 - 唯一会发生的事情是CGI脚本的追加。日志轮换将在7天或更早的日志文件上运行。

我的问题是,处理并发附加到此日志文件的最佳方法是什么?在追加之前我需要锁定它吗?我发现Perl Monks上的this page似乎表明“当多个进程正在写入同一个文件,而且所有进程都打开了附加文件时,数据不会被覆盖。”

我已经了解到,仅仅因为它可以完成并不意味着我应该这样做,但在这种情况下,最安全,最佳实践的方法是什么?

要点:

  • Concurrent追加到同一个文件
  • 每个附加到文件的行只有一行,少于50个字符
  • 订单无关紧要

谢谢!

6 个答案:

答案 0 :(得分:13)

是的,请使用flock

下面是一个示例程序,从典型的前端问题开始:

#! /usr/bin/perl

use warnings;
use strict;

use Fcntl qw/ :flock /;

然后我们指定日志的路径和将运行的客户端数量:

my $log = "/tmp/my.log";
my $clients = 10;

要记录消息,请以附加模式打开文件,以便所有写入自动结束。然后调用flock等待我们开启对日志的独占访问权限。一旦我们启动,请编写消息并close句柄,它会自动释放锁定。

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关闭$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

请记住,订单与运行不同。

答案 1 :(得分:2)

“当多个进程写入同一个文件,并且所有进程都打开了要附加的文件时,数据不会被覆盖”可能是真的,但这并不意味着你的数据不会被破坏(一个条目在另一个内部)。对于少量数据来说,它不太可能发生,但它可能会发生。

flock是解决该问题的可靠且相当简单的解决方案。我建议你简单地使用它。

答案 2 :(得分:1)

我会敦促Log :: Log4Perl

答案 3 :(得分:0)

您可以尝试使用文件锁定,但这会让您很快受到伤害。更简单的方法是使用一个小的持久进程或一个cron作业来扫描日志文件目录并将事件一次附加到日志文件中。

为了更加安全,您可以让您的日志记录脚本在每个时间段(例如5分钟)创建新的日志文件,并使您的守护程序忽略不到五分钟的文件。

答案 4 :(得分:0)

我认为我会运行一个单独的流程,例如使用Net :: Daemon或类似的,以集中方式处理日志条目的写入。 CGI脚本实例会通过套接字将日志字符串传递给此守护程序。

答案 5 :(得分:0)

你有几个选择,按复杂程度增长:

1)每一行都有时间和日期戳。当您需要检查合并文件时,会交错所有输入文件。

2)编写一个始终保持运行的脚本,使所有文件句柄保持打开状态,并使用select()查找包含新数据的文件,并按照接收顺序将其转储到输出中。这种方法可能会成为一种资源,因为它会不断地调用select,然后查找新文件,然后打开新文件,然后再次调用select。

3)编写一个接受TCP连接的脚本。如果您最终遇到记录器可以打开的日志文件多于操作系统中的进程可以支持的情况,那么您将返回解决方案编号1.老实说,请转到1号。