Параллельный добавляет в тот же файл с помощью Perl

У меня есть потребность обновить сценарий CGI Perl, где пользователи должны завершить 3 шага. После того, как они закончат каждый шаг, сценарий регистрируется который шаг завершенный пользователь. Наличие записи этого важно, таким образом, мы можем доказать пользователю, что они только закончили шаг один и не завершали все три шага, например.

Прямо сейчас сценарий создает 1 файл журнала для каждого экземпляра сценария CGI. Таким образом, если Усера делает шаг 1, то UserB делает шаг 1, то шаг 2, то шаг 3 - и затем шаг 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, которая, кажется, указывает, что, "когда несколько процессов пишут в тот же файл, и всем им открыли файл для добавления, данные не должны быть перезаписаны".

Я узнал, что просто, потому что это может быть сделано, не означает, что я должен, но в этом случае, какова самая безопасная, лучшая практика способ сделать это?

Сводка:

  • Параллельный добавляет в тот же файл
  • Каждый добавляет в файл, всего одна строка, меньше чем 50 символов
  • Порядок не имеет значения

Спасибо!

12
задан BrianH 2 March 2010 в 18:28
поделиться

6 ответов

Да, используйте стадо .

Ниже приведен пример программы, начиная с типичного вступительного сообщения:

#! /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

Имейте в виду, что порядок выполнения будет отличаться от цикла к запуску.

13
ответ дан 2 December 2019 в 20:17
поделиться

"когда несколько процессов пишут в один и тот же файл, и у всех из них файл открыт для добавления, данные не должны перезаписываться" может быть правдой, но это не значит, что ваши данные не могут выйти искаженными (одна запись внутри другой). Это маловероятно для небольших объемов данных, но может произойти.

flock - надежное и достаточно простое решение этой проблемы. Я бы посоветовал вам просто использовать его.

2
ответ дан 2 December 2019 в 20:17
поделиться

Вы можете попробовать поиграть с блокировкой файлов, но это очень быстро приведет вас в страну боли. Более простой способ - создать небольшой постоянный процесс или задание cron, которое будет сканировать каталог с файлом журнала и добавлять события в файл журнала по одному.

Для дополнительной безопасности вы можете заставить свои сценарии ведения журнала создавать новый файл журнала каждый период времени (скажем, 5 минут) и заставить ваш демон игнорировать файлы, возраст которых меньше пяти минут.

0
ответ дан 2 December 2019 в 20:17
поделиться

Я бы посоветовал Log :: Log4Perl

1
ответ дан 2 December 2019 в 20:17
поделиться

Думаю, я бы запустил отдельный процесс, например используя Net :: Daemon или аналогичный, который централизованно обрабатывает записи журнала. Экземпляры сценария CGI будут передавать строки журнала этому демону через сокет.

0
ответ дан 2 December 2019 в 20:17
поделиться

У вас есть несколько вариантов, в порядке возрастания сложности:

1) Просто проставлять временные и датированные метки в каждой строке. Когда вам нужно изучить объединенный файл, вы перемежаете все входные файлы.

2) Написать постоянно выполняющийся сценарий, который держит открытыми все файловые каналы и, используя select(), находит файлы с новыми данными и сбрасывает их на выход в порядке их получения. Этот метод может стать ресурсозатратным, поскольку будет постоянно вызывать select, затем искать новые файлы, затем открывать новые файлы, затем снова вызывать select.

3) Напишите сценарий, принимающий TCP-соединения. Если вы когда-нибудь окажетесь в ситуации, когда у регистраторов может быть открыто больше файлов журнала, чем процесс в вашей операционной системе может поддерживать одновременно, вы вернетесь к решению номер 1. Честно говоря, выбирайте номер 1.

0
ответ дан 2 December 2019 в 20:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: