2010-03-02 57 views
12

我需要升級一個Perl CGI腳本,用戶必須完成3個步驟。完成每個步驟後,腳本將記錄用戶完成的步驟。記錄這一點很重要,因此我們可以向用戶證明他們只完成第一步,並且沒有完成所有三個步驟。併發使用Perl追加到同一個文件

現在,腳本正在爲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,它似乎表明「當多個進程正在寫入同一個文件,並且所有文件都打開了要添加的文件時,數據不會被覆蓋。」

我已經瞭解到,僅僅因爲它可以完成並不意味着我應該這樣做,但在這種情況下,最安全的最佳實踐方法是什麼?

摘要:

  • 併發追加到同一個文件
  • 每個追加到該文件僅僅是一個線,不到50個字符
  • 順序並不重要

謝謝!

回答

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

請記住,訂單將從運行到運行不同。

+3

gbacon做這項權利,但一些重要的調整自己的代碼時,要記住:你*不*解鎖('LOCK_UN')的文件 - 你將其關閉。這將確保數據被刷新,然後*解鎖它。 – hobbs 2010-03-03 05:00:42

+0

謝謝gbacon。訂單不重要,所以這不是問題。我不完全確定是否需要分叉我的案件。由於這是一個CGI腳本(不是快速的CGI - 它不會保持活躍狀態​​),用戶將只能夠在腳本生命週期內執行1個步驟 - 一旦他完成一個步驟,腳本將退出。然後,在網絡上他正在執行第2步,點擊提交,第2步將被記錄,腳本退出。 – BrianH 2010-03-03 14:31:08

+0

@BrianH我不清楚:分叉的孩子模擬多個併發客戶端。在你的CGI程序中,從我的答案中調用一個類似於'log_step'的子實體,以記錄真實用戶完成的步驟。 – 2010-03-03 16:25:57

2

「當多個進程正在寫入同一個文件,並且所有文件都打開了要添加的文件時,數據不會被覆蓋」可能是正確的,但這並不意味着您的數據不會出現損壞(一個入口在另一個入口內)。少量數據發生的可能性不大,但可能會發生。

flock是一個可靠的,合理簡單的解決方案,解決這個問題。我會建議你簡單地使用它。

0

你可以嘗試玩文件鎖定,但這會讓你很快受到傷害。更簡單的方法是創建一個小型持久性進程或cron作業,它將掃描您的日誌文件目錄並將這些事件附加到日誌文件中。

爲了更加安全,您可以讓日誌記錄腳本在每個時間段(例如5分鐘)內創建新的日誌文件,並使守護程序忽略小於5分鐘的文件。

0

我想我會運行一個單獨的過程,例如,使用Net :: Daemon或類似的,它可以處理以中心方式寫入日誌條目。 CGI腳本實例將通過套接字將日誌字符串傳遞給此守護程序。

0

您有幾種選擇,在複雜的成長順序:

1)只是時間和日期戳每一行。當您需要檢查合併文件時,您將交織所有輸入文件。 2)編寫一個腳本,該腳本始終保持運行,並保持所有文件句柄處於打開狀態,並使用select()查找具有新數據的文件,並按照接收順序將其轉儲到輸出。這種方法可能會變成資源浪費,因爲它會不斷調用select,然後查找新文件,然後打開新文件,然後再次調用select。

3)編寫接受TCP連接的腳本。如果您最終遇到記錄器可以打開的日誌文件比您的操作系統中可以同時支持的進程更多的情況,那麼您將回到解決方案編號1.老實說,編號爲1.

1

我要敦促登錄:: Log4Perl

+2

詳細說明您的建議 – 2015-02-14 17:54:30