Подтвердить что ты не робот

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

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

В настоящее время script создает 1 файл журнала для каждого экземпляра CGI script. Поэтому, если 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

Файлы журнала называются с текущей меткой времени, случайным числом и идентификатором процесса.

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

В основном, файл журнала будет иметь текущую дату в имени файла, и в любое время, когда CGI script должен записываться в журнал, он будет прикрепляться к одному файлу журнала за этот день, независимо от пользователя или чего они включены.

Ничего не нужно читать файл журнала - единственное, что произойдет с ним, - это добавить CGI script. Вращение журнала будет выполняться в файлах журнала, которые составляют 7 дней или старше.

Мой вопрос: какой лучший способ обработать параллельные приложения к этому файлу журнала? Нужно ли блокировать его перед добавлением? Я нашел эту страницу в Perl Monks, которая, как представляется, указывает, что "когда несколько процессов записываются в один файл, и все они имеют открытый файл для добавления данные не должны быть перезаписаны."

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

Резюме:

  • Параллельно добавляет к одному файлу
  • Каждое добавление к файлу - это только одна строка, менее 50 символов
  • Заказ не имеет значения

Спасибо!

4b9b3361

Ответ 1

Да, используйте 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 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

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

Ответ 2

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

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

Ответ 3

Я бы призвал Log:: Log4Perl

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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

2) Запишите script, который продолжает работать все время, когда все дескрипторы файлов открыты, и, используя select(), находит файлы с новыми данными и выдает их на вывод в том порядке, в котором он был получен. Этот метод может стать ресурсом hog, поскольку он будет постоянно вызывать select, затем искать новые файлы, затем открывать новые файлы, а затем снова вызывать select.

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