2010-03-02 9 views
12

Tengo la necesidad de actualizar una secuencia de comandos Perl CGI donde los usuarios deben completar 3 pasos. Después de que terminen cada paso, el script está registrando qué paso finalizó el usuario. Tener un registro de esto es importante para que podamos demostrarle al usuario que solo terminaron el primer paso y que no completaron los tres pasos, por ejemplo.Concurrente se agrega al mismo archivo con Perl

En este momento, el script está creando 1 archivo de registro para cada instancia del script CGI. Entonces, si UserA hace el paso 1, entonces el usuario B hace el paso 1, luego el paso 2, luego el paso 3, y luego el usuario A termina el paso 2 y el paso 3, el orden de los archivos de registro sería.

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

Los archivos de registro se denominan con la fecha y hora actual, un número aleatorio, y el proceso PID.

Esto funciona bien para evitar que el mismo archivo se escriba más de una vez, pero el directorio obtiene rápidamente miles de archivos (cada archivo contiene solo unos pocos bytes). Hay un proceso para rotar y comprimir estos registros, pero me ha tocado hacerlo para que el script inicie sesión en un solo archivo por día para reducir la cantidad de archivos de registro que se crean.

Básicamente, el archivo de registro tendrá la fecha actual en el nombre del archivo, y cada vez que el script CGI necesite escribir en el registro, se agregará al único archivo de registro para ese día, independientemente del usuario o qué paso están encendidos

No será necesario leer el archivo de registro; lo único que le sucederá será un apéndice del script CGI. La rotación del registro se ejecutará en archivos de registro de 7 días o más.

Mi pregunta es, ¿cuál es la mejor manera de manejar los adjuntos concurrentes a este archivo de registro? ¿Debo cerrarlo antes de agregar? Encontré this page en Perl Monks que parece indicar que "cuando varios procesos escriben en el mismo archivo, y todos tienen el archivo abierto para anexar, los datos no se sobrescribirán".

He aprendido que el hecho de que se pueda hacer no significa que deba hacerlo, pero en este caso, ¿cuál es la forma más segura y práctica de hacerlo?

Resumen:

  • concurrente anexa al mismo archivo
  • Cada final del archivo es sólo una línea, a menos de 50 caracteres
  • orden no importa

Gracias!

Respuesta

13

Sí, use flock.

un programa de ejemplo está abajo, comenzando con la típica cuestión frontal:

#! /usr/bin/perl 

use warnings; 
use strict; 

use Fcntl qw/ :flock /; 

Luego especifique la ruta de acceso al registro y el número de clientes que se ejecutará:

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

Para registrar una mensaje, abra el archivo en el modo de agregar para que todas las escrituras automáticamente lleguen al final. Luego, llame al flock para esperar nuestro turno para tener acceso exclusivo al registro. Una vez que estemos arriba, escriba el mensaje y close el identificador, que libera automáticamente el bloqueo.

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: $!"; 
} 

ahora $clientsfork fuera de los procesos del niño a ir a través de los tres pasos con intervalos aleatorios entre:

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; 
} 

No olvide que esperar en todos los niños para salir:

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`; 

Muestra de salida:

[...] 
./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

Tenga en cuenta que el orden será diferente de ejecutar para ejecutar.

+3

gbacon hace este derecho, sino algo importante para recordar la hora de adaptar su código: usted * No * desbloqueo ('LOCK_UN') el archivo - que lo cierre. Eso asegurará que los datos se descarguen y * luego * lo desbloqueen. – hobbs

+0

Gracias gbacon. El orden no es importante, entonces eso no es un problema. No estoy del todo seguro de si necesito bifurcar en mi caso. Como se trata de una secuencia de comandos CGI (no una CGI rápida, no permanece activa), el usuario solo podrá realizar 1 paso durante la vida del script: una vez que completa un paso, la secuencia de comandos se cerrará.Luego, en la web, está trabajando en el paso 2, hits submit, el paso 2 se registrará y el script se cerrará. – BrianH

+0

@BrianH No estaba claro: los niños bifurcados simulan múltiples clientes concurrentes. En su programa CGI, llame a un sub similar a 'log_step' de mi respuesta para registrar la finalización de un paso por un usuario real. –

2

"cuando varios procesos escriben en el mismo archivo, y todos ellos tienen el archivo abierto para anexar, los datos no se sobrescribirán" puede ser cierto, pero eso no significa que sus datos no puedan salir destrozados (una entrada dentro de otra). No es muy probable que suceda para pequeñas cantidades de datos, pero podría serlo.

flock es una solución confiable y razonablemente simple para ese problema. Te aconsejo que simplemente uses eso.

0

Puede intentar jugar con el bloqueo de archivos, pero esto lo llevará a la tierra del daño muy rápidamente. La forma más fácil sería tener un pequeño proceso persistente o un trabajo cron que escanee su directorio de archivos de registro y anexe los eventos al archivo de registro una vez.

Para mayor seguridad, puede hacer que sus scripts de registro creen un nuevo archivo de registro cada período de tiempo (digamos 5 minutos) y hacer que su daemon ignore los archivos que tienen menos de cinco minutos.

0

Creo que ejecutaría un proceso por separado, p. usando Net :: Daemon o similar, que maneja la escritura de entradas de registro de una manera central. Las instancias de script CGI pasarán las cadenas de registro a este daemon a través de un socket.

0

Usted tiene algunas opciones, en orden creciente de complejidad:

1) Sólo hora y la marca de fecha cada línea. Cuando necesite examinar el archivo fusionado, intercalará todos los archivos de entrada.

2) Escriba una secuencia de comandos que se ejecute todo el tiempo que mantenga todos los manejadores de archivos abiertos y, usando select() busca archivos con datos nuevos y los tira a la salida en el orden en que los recibió. Este método podría convertirse en una fuente de recursos, ya que llamaría constantemente a seleccionar, luego buscaría nuevos archivos, luego abriría los nuevos archivos y luego volvería a llamar.

3) Escriba una secuencia de comandos que acepte conexiones TCP. Si alguna vez termina en una situación donde los registradores pueden tener más archivos de registro abiertos de los que un proceso en su sistema operativo puede soportar a la vez, está de vuelta a la solución número 1. Honestamente, vaya al número 1.

1

Insto Entrar :: Log4perl

+2

Elabore su sugerencia –

Cuestiones relacionadas