2010-11-10 37 views
8

Tôi có một tập lệnh cron thực thi một tập lệnh PHP cứ 10 phút một lần. Kịch bản lệnh kiểm tra hàng đợi và xử lý dữ liệu trong hàng đợi. Đôi khi hàng đợi có đủ dữ liệu để kéo dài hơn 10 phút xử lý, tạo ra tiềm năng của hai tập lệnh cố gắng truy cập cùng một dữ liệu. Tôi muốn có thể phát hiện xem tập lệnh đã chạy chưa để khởi chạy nhiều bản sao của tập lệnh. Tôi nghĩ về việc tạo ra một lá cờ cơ sở dữ liệu cho biết rằng một tập lệnh đang xử lý, nhưng nếu kịch bản đã từng sụp đổ, nó sẽ để nó ở trạng thái tích cực. Có cách nào dễ dàng để biết liệu kịch bản PHP có đang chạy từ việc sử dụng một script PHP hay shell không?Làm cách nào để phát hiện xem tập lệnh PHP đã chạy chưa?

+2

có thể có nội dung nào đó có tệp '.pid'? – drudge

+0

có, thêm vào error_handler để xóa pid, và sau khi thực hiện công việc xóa pid – ajreal

+0

Sử dụng bất cứ điều gì bạn muốn, IMHO không có giải pháp không gặp sự cố. Sử dụng semaphores? tệp .pid? Tất cả chúng sẽ bị bỏ lại trong trạng thái "tích cực", nếu xảy ra sự cố xấu. Nhưng một số người trong số họ mạnh mẽ hơn những người khác. –

Trả lời

5

Nếu bạn cần nó hoàn toàn chống va chạm, bạn nên sử dụng semaphores, được phát hành tự động khi php kết thúc xử lý yêu cầu cụ thể.

Cách tiếp cận đơn giản hơn là tạo bản ghi DB hoặc tệp lúc bắt đầu thực hiện và xóa tệp ở cuối. Bạn luôn có thể kiểm tra "tuổi" của bản ghi/tệp đó và nếu nó lớn hơn 3 lần so với thực thi tập lệnh thông thường, giả sử nó đã bị lỗi và xóa nó.

Không có "viên đạn bạc", nó chỉ phụ thuộc vào nhu cầu của bạn.

+0

... Tôi không nghĩ rằng phần mở rộng semaphore có sẵn cho Win – Milan

3

Cách phổ biến cho các trình tiện ích * nix (mặc dù không nhất thiết là các tập lệnh PHP, nhưng nó sẽ hoạt động) là sử dụng tệp .pid.

Khi tập lệnh bắt đầu kiểm tra sự tồn tại của tệp .pid được đặt tên cho tập lệnh (thường được lưu trữ trong/var/run /). Nếu nó không tồn tại, tạo ra nó thiết lập nội dung của nó đến PID của quá trình chạy kịch bản (sử dụng getmypid) sau đó tiếp tục thực hiện bình thường. Nếu nó tồn tại, hãy đọc PID từ nó và xem liệu quá trình đó vẫn đang chạy, có thể bằng cách chạy ps $pid. Nếu nó đang chạy, thoát. Nếu không, ghi đè nội dung của nó bằng PID của bạn (như trên) và tiếp tục thực hiện bình thường.

Khi thực hiện xong, hãy xóa tệp.

+2

Tôi đoán điều này có thể sẽ hoạt động tốt trên hệ điều hành * nix điển hình miễn là khoảng cách thời gian giữa các lần chạy daemon của bạn là khiêm tốn, nhưng hãy cẩn thận rằng [PIDs được tái chế ] (http://unix.stackexchange.com/questions/26677/will-process-ids-be-recycled-what-if-you-reach-the-maximal-id) và vì vậy bạn có thể không muốn sử dụng chúng như một phần của một hệ thống khóa. Có ít nhất một chế độ thất bại về mặt lý thuyết ở đây, nơi daemon của bạn bị treo, một số quá trình chạy dài không liên quan sẽ mất cùng một PID, và các lần khởi chạy sau đó của daemon của bạn đều tin rằng nó đã chạy, khiến nó chết vĩnh viễn. –

3

Tôi biết đây là câu hỏi cũ nhưng trong trường hợp ai đó đang xem ở đây, tôi sẽ đăng một số mã. Đây là những gì tôi đã làm gần đây trong một tình huống tương tự và nó hoạt động tốt. Đặt mã này ở đầu tệp của bạn và nếu cùng một tập lệnh đã chạy, nó sẽ để lại và kết thúc tệp mới.

Tôi sử dụng nó để giữ cho một hệ thống giám sát luôn hoạt động. Một công việc cron bắt đầu kịch bản mỗi 5 phút nhưng trừ khi người kia đã dừng lại từ một số lý do (thường là nếu nó đã bị rơi, rất hiếm!) Cái mới sẽ tự thoát ra.

// The file to store our process file 
define('PROCESS_FILE', 'process.pid'); 

// Check I am running from the command line 
if (PHP_SAPI != 'cli') { 
    log_message('Run me from the command line'); 
    exit; 
} 

// Check if I'm already running and kill myself off if I am 
$pid_running = false; 
if (file_exists(PROCESS_FILE)) { 
    $data = file(PROCESS_FILE); 
    foreach ($data as $pid) { 
     $pid = (int)$pid; 
     if ($pid > 0 && file_exists('/proc/' . $pid)) { 
      $pid_running = $pid; 
      break; 
     } 
    } 
} 
if ($pid_running && $pid_running != getmypid()) { 
    if (file_exists(PROCESS_FILE)) { 
     file_put_contents(PROCESS_FILE, $pid); 
    } 
    log_message('I am already running as pid ' . $pid . ' so stopping now'); 
    exit; 
} else { 
    // Make sure file has just me in it 
    file_put_contents(PROCESS_FILE, getmypid()); 
    log_message('Written pid with id '.getmypid()); 
} 

Nó sẽ KHÔNG hoạt động mà không sửa đổi trên Windows, nhưng sẽ ổn trong các hệ thống dựa trên Unix.

+0

-1 vì lý do tương tự như câu trả lời của Brenton ở trên; bởi vì hầu hết các Unix đều tái chế PID, thực tế là có một tiến trình đang chạy mà PID khớp với một tệp được ghi vào tệp PID của bạn bởi một hóa thân trước của tập lệnh của bạn KHÔNG có nghĩa là nó thực sự là tập lệnh của bạn vẫn đang chạy. Nếu kịch bản của bạn bị treo, và sau đó một số quá trình chạy dài không liên quan sẽ sinh ra và lấy PID mà tập lệnh của bạn đang sử dụng trước khi xảy ra sự cố, khi kịch bản tiếp theo chạy nó sẽ tự tắt chính nó; kịch bản của bạn có thể vẫn chết mãi mãi! Sử dụng 'flock()' thay vào đó để bảo vệ chính bạn khỏi trường hợp cạnh này. –

+0

Điều này cũng hoạt động trên các cửa sổ, bạn phải xóa '/ proc /' - kiểm tra. – Radon8472

0

Điều này phù hợp với tôi. Đặt bản ghi cơ sở dữ liệu bằng cờ khóa và dấu thời gian. Kịch bản của tôi phải hoàn thành tốt trong vòng 15 phút để nói thêm rằng như một feild khóa cuối cùng để kiểm tra:

 $lockresult = mysql_query(" 
     SELECT * 
     FROM queue_locks 
     WHERE `lastlocked` > DATE_SUB(NOW() , INTERVAL 15 MINUTE)  
     AND `locked` = 'yes' 
     AND `queid` = '1' 
     LIMIT 1 
     "); 
$LockedRowCount = mysql_num_rows($lockresult); 

if($LockedRowCount>0){ 
    echo "this script is locked, try again later"; 
    exit; 
}else{ 
//Set the DB record to locked and carry on son 
$result = mysql_query(" 
      UPDATE `queue_locks` SET `locked` = 'yes', `lastlocked` = CURRENT_TIMESTAMP WHERE `queid` = 1; 
      "); 

} 

Sau đó mở khóa nó ở phần cuối của kịch bản:

$result = mysql_query("UPDATE `queue_locks` SET `locked` = 'no' WHERE `queid` = 1;"); 
24

Bạn chỉ có thể sử dụng một tập tin khóa. Chức năng flock() của PHP cung cấp một trình bao bọc đơn giản cho chức năng flock của Unix, cung cấp các khóa cố định trên các tệp.

Nếu bạn không phát hành chúng một cách rõ ràng, hệ điều hành sẽ tự động giải phóng các khóa này cho bạn khi quá trình giữ chúng chấm dứt, ngay cả khi nó chấm dứt bất thường.

Bạn cũng có thể thực hiện theo quy ước Unix làm cho tệp khóa của bạn thành 'tệp PID' - tức là, khi lấy khóa trên tệp, hãy viết tập lệnh PID của bạn vào đó. Thậm chí nếu bạn không bao giờ đọc điều này từ bên trong kịch bản của bạn, nó sẽ thuận tiện cho bạn nếu kịch bản của bạn bị treo hoặc phát điên và bạn muốn tìm PID của nó để tự tay giết nó.

Dưới đây là một sao chép/dán sẵn sàng thực hiện:

#!/usr/bin/php 
<?php 

$lock_file = fopen('path/to/yourlock.pid', 'c'); 
$got_lock = flock($lock_file, LOCK_EX | LOCK_NB, $wouldblock); 
if ($lock_file === false || (!$got_lock && !$wouldblock)) { 
    throw new Exception(
     "Unexpected error opening or locking lock file. Perhaps you " . 
     "don't have permission to write to the lock file or its " . 
     "containing directory?" 
    ); 
} 
else if (!$got_lock && $wouldblock) { 
    exit("Another instance is already running; terminating.\n"); 
} 

// Lock acquired; let's write our PID to the lock file for the convenience 
// of humans who may wish to terminate the script. 
ftruncate($lock_file, 0); 
fwrite($lock_file, getmypid() . "\n"); 

/* 
    The main body of your script goes here. 
*/ 
echo "Hello, world!"; 

// All done; we blank the PID file and explicitly release the lock 
// (although this should be unnecessary) before terminating. 
ftruncate($lock_file, 0); 
flock($lock_file, LOCK_UN); 

Chỉ cần thiết lập đường dẫn của tập tin khóa của bạn đến bất cứ nơi bạn thích và bạn thiết lập.

+1

Đây phải là câu trả lời hay nhất. Nó chỉ đơn giản là có ý nghĩa. [Symfony 2.6 LockHandler] (http://symfony.com/blog/new-in-symfony-2-6-lockhandler) cũng đang thực hiện khóa bằng phương tiện khóa tập tin. –

+0

Mỗi tài liệu: Trên các phiên bản của PHP trước 5.3.2, khóa cũng được phát hành bởi fclose() (cũng được gọi tự động khi tập lệnh kết thúc). 5.3.2 \t Mở khóa tự động khi xử lý tài nguyên của tệp bị đóng đã bị xóa. Mở khóa bây giờ luôn luôn phải được thực hiện bằng tay. –

+1

@JustinMcAleer PHP có thể không tự động giải phóng khóa cho bạn sau khi đóng một tập tin xử lý, nhưng hệ điều hành vẫn sẽ (ít nhất là trên các hệ điều hành Unix); việc chuyển hướng thủ công về cơ bản là không chính xác. Và ngay cả khi không, bất kỳ hệ điều hành nào cũng hy vọng sẽ giải phóng khóa khi quá trình của bạn kết thúc; không làm như vậy sẽ bị phá vỡ. –

3

Bạn có thể sử dụng mới Symfony 2.6 LockHandler

$lock = new LockHandler('update:contents'); 
if (!$lock->lock()) { 
    echo 'The command is already running in another process.'; 
} 
+0

Bạn có thể gửi một liên kết mà tôi có thể tải về lớp LockHandler chỉ mà không có phần còn lại của Synfony? – Radon8472

+1

https://github.com/symfony/filesystem/blob/master/LockHandler.php – Nicklasos

-1

Tôi biết đây là một câu hỏi cũ, nhưng có một cách tiếp cận mà chưa được đề cập trước đây mà tôi nghĩ là đáng xem xét.

Một trong những vấn đề với giải pháp khóa cơ sở dữ liệu hoặc khóa cơ sở dữ liệu, như đã đề cập, là nếu tập lệnh không thành công vì lý do nào đó ngoài hoàn thành bình thường, nó sẽ không giải phóng khóa. Và do đó trường hợp tiếp theo sẽ không bắt đầu cho đến khi khóa được xóa bằng tay hoặc xóa bằng chức năng dọn dẹp.

Nếu bạn chắc chắn rằng tập lệnh chỉ nên chạy một lần, thì việc kiểm tra từ bên trong tập lệnh sẽ tương đối dễ dàng dù tập lệnh đã chạy khi bạn khởi động nó. Dưới đây là một số mã:

function checkrun() { 
    exec("ps auxww",$ps); 
    $r = 0; 
    foreach ($ps as $p) { 
     if (strpos($p,basename(__FILE__))) { 
      $r++; 
      if ($r > 1) { 
       echo "too many instances, exiting\n"; 
       exit(); 
      } 
     } 
    } 
} 

Đơn giản chỉ cần gọi hàm này vào lúc bắt đầu của kịch bản, trước khi bạn làm bất cứ điều gì khác (chẳng hạn như một handler cơ sở dữ liệu mở hoặc xử lý một tập tin nhập khẩu), và nếu kịch bản tương tự cũng đã chạy rồi nó sẽ xuất hiện hai lần trong danh sách quy trình - một lần cho trường hợp trước đó và một lần cho trường hợp này. Vì vậy, nếu nó xuất hiện nhiều lần, chỉ cần thoát.

Một hình ảnh xác thực tại đây: Tôi giả định rằng bạn sẽ không bao giờ có hai tập lệnh có cùng tên cơ sở có thể chạy đồng thời một cách hợp pháp (ví dụ: cùng một tập lệnh chạy dưới hai người dùng khác nhau). Nếu đó là một khả năng, thì bạn cần phải mở rộng việc kiểm tra đến một cái gì đó tinh vi hơn một chuỗi con đơn giản trên tên tệp của tệp. Nhưng điều này hoạt động tốt nếu bạn có tên tập tin duy nhất cho kịch bản của bạn.

+0

-1 không phải cho cách tiếp cận bạn đề xuất ở đây mà đúng hơn là với tuyên bố rằng khóa tệp Unix sẽ không được giải phóng nếu quá trình chấm dứt bất thường. Điều này là không đúng sự thật. Xem http://stackoverflow.com/questions/12651068/release-of-flock-in-case-of-errors để minh họa điều này. –

4

Nếu bạn đang chạy Linux, điều này sẽ làm việc ở phía trên cùng của kịch bản của bạn:

$running = exec("ps aux|grep ". basename(__FILE__) ."|grep -v grep|wc -l"); 
if($running > 1) { 
    exit; 
} 
+1

Tôi thích sự đơn giản của giải pháp này. Có bất kỳ hạn chế nào không? Ngoài ra, trong trường hợp bất kỳ ai khác muốn sử dụng nó - nếu bạn sử dụng nó trong crontab, sau đó kiểm tra xem số đó có lớn hơn 2 thay vì 1. – LauriK

+0

@LauriK, theo câu hỏi của bạn hay không. Kịch bản lệnh đầu tiên có thể đã dừng trong nội bộ mà không rõ ràng từ tập lệnh thứ hai khi thấy PID vẫn đang chạy. Sử dụng một cái gì đó khác có thể giúp kịch bản thứ hai biết để giết đầu tiên và bắt đầu lại. – Xeoncross

-1

Chỉ cần thêm sau trên đỉnh của kịch bản của bạn.

<?php 
    // Ensures single instance of script run at a time. 
    $fileName = basename(__FILE__); 
    $output = shell_exec("ps -ef | grep -v grep | grep $fileName | wc -l"); 
    //echo $output; 
    if ($output > 2) 
    { 
     echo "Already running - $fileName\n"; 
     exit; 
    } 

    // Your php script code. 
?> 
+0

-1; điều này về cơ bản là giải pháp tương tự như http://stackoverflow.com/a/33045344/1709587, nhưng xấu hơn (tại sao nhận xét ra echo? Ngoài ra, đóng '?> 'thẻ vi phạm PSR-2) và, theo như tôi có thể lý do, bị hỏng; tại sao bạn kiểm tra '$ output> 2' thay vì' $ output> 1' nếu bạn đã lọc ra các tiến trình grep của mình? Các dòng duy nhất nhận được thông qua để 'wc' ở đây nên được thực tế trường hợp của kịch bản của bạn, do đó, kiểm tra nên được'> 1', không phải '> 2'. (Phải thừa nhận rằng, tôi chưa thử nghiệm để xác nhận điều này.) –

0

Tôi đã thử cách này. Hoạt động tốt trong Windows.

$status_file=__DIR__.'/mail_process.txt'; 
    if(file_exists($status_file) && file_get_contents($status_file)=='running'){ 
       echo 'Program running'; 
      exit; 
    } 
    file_put_contents($status_file,'running'); 

     // Code block 
     //........... 

    file_put_contents($status_file,'finished'); 

Nhờ @martinodf cho ý tưởng của mình.

Các vấn đề liên quan