[英]PHP locking / making sure a given script is only running once at any given time
我正在嘗試編寫一個 PHP 腳本,我想確保它在任何給定時間只運行一個實例。 所有這些關於不同鎖定方式、競爭條件等的討論都給了我很大的幫助。
我對鎖文件是要走的路,還是信號量,或使用 MySQL 鎖,等等等等感到困惑。
誰能告訴我:
a) 實現這一點的正確方法是什么?
和
b) 指向我的 PHP 實現(或易於移植到 PHP 的東西?)
一種方法是將 php 函數flock與一個虛擬文件一起使用,它將充當看門狗。
在我們的工作開始時,如果文件引發了LOCK_EX標志,則可以退出或等待。
PHP 羊群文檔: http : //php.net/manual/en/function.flock.php
對於此示例,必須首先創建名為lock.txt的文件。
示例 1 ,如果另一個孿生進程正在運行,它將正確退出,無需重試,並給出狀態消息。
如果文件lock.txt不可訪問,它將拋出錯誤狀態。
<?php
$fp = fopen("lock.txt", "r+");
if (!flock($fp, LOCK_EX|LOCK_NB, $blocked)) {
if ($blocked) {
// another process holds the lock
echo "Couldn't get the lock! Other script in run!\n";
}
else {
// couldn't lock for another reason, e.g. no such file
echo "Error! Nothing done.";
}
}
else {
// lock obtained
ftruncate($fp, 0); // truncate file
// Your job here
echo "Job running!\n";
// Leave a breathe
sleep(3);
fflush($fp); // flush output before releasing the lock
flock($fp, LOCK_UN); // release the lock
}
fclose($fp); // Empty memory
示例 2 , FIFO (先進先出):我們希望進程在隊列之后等待執行,如果有的話:
<?php
$fp = fopen("lock.txt", "r+");
if (flock($fp, LOCK_EX)) { // acquire an exclusive lock
ftruncate($fp, 0); // truncate file
// Your job here
echo "Job running!\n";
// Leave a breathe
sleep(3);
fflush($fp); // flush output before releasing the lock
flock($fp, LOCK_UN); // release the lock
}
fclose($fp);
通過在腳本結束時創建和刪除文件,它也可以通過fopen進入x模式。
創建和開放僅用於寫作; 將文件指針放在文件的開頭。 如果文件已經存在, fopen() 調用將失敗,返回 FALSE
但是,在Unix 環境中,為了進行微調,我發現使用getmypid()
將每個后台腳本的PID
列出到數據庫或單獨的 JSON 文件中更容易。
當一個任務結束時,腳本負責在這個文件中聲明他的狀態(例如:成功/失敗/調試信息等),然后刪除他的PID
。 在我看來,這允許以更簡單的方式創建管理員工具和守護進程。 並在必要時使用posix_kill()
從 PHP 中posix_kill()
PID。
微服務是使用類 Unix 管道組成的。 服務可以調用服務。 https://en.wikipedia.org/wiki/Microservices
另請參閱:防止 PHP 腳本在運行時耗盡所有資源?
// borrow from 2 anwsers on stackoverflow
function IsProcessRunning($pid) {
return shell_exec("ps aux | grep " . $pid . " | wc -l") > 2;
}
function AmIRunning($process_file) {
// Check I am running from the command line
if (PHP_SAPI != 'cli') {
error('Run me from the command line');
exit;
}
// Check if I'm already running and kill myself off if I am
$pid_running = false;
$pid = 0;
if (file_exists($process_file)) {
$data = file($process_file);
foreach ($data as $pid) {
$pid = (int)$pid;
if ($pid > 0 && IsProcessRunning($pid)) {
$pid_running = $pid;
break;
}
}
}
if ($pid_running && $pid_running != getmypid()) {
if (file_exists($process_file)) {
file_put_contents($process_file, $pid);
}
info('I am already running as pid ' . $pid . ' so stopping now');
return true;
} else {
// Make sure file has just me in it
file_put_contents($process_file, getmypid());
info('Written pid with id '.getmypid());
return false;
}
}
/*
* Make sure there is only one instance running at a time
*/
$lockdir = '/data/lock';
$script_name = basename(__FILE__, '.php');
// The file to store our process file
$process_file = $lockdir . DS . $script_name . '.pid';
$am_i_running = AmIRunning($process_file);
if ($am_i_running) {
exit;
}
使用信號量:
$key = 156478953; //this should be unique for each script
$maxAcquire = 1;
$permissions =0666;
$autoRelease = 1; //releases semaphore when request is shut down (you dont have to worry about die(), exit() or return
$non_blocking = false; //if true, fails instantly if semaphore is not free
$semaphore = sem_get($key, $maxAcquire, $permissions, $autoRelease);
if (sem_acquire($semaphore, $non_blocking )) //blocking (prevent simultaneous multiple executions)
{
processLongCalculation();
}
sem_release($semaphore);
看:
https://www.php.net/manual/en/function.sem-get.php
如果您在 linux 上使用 php,我認為最實用的方法是:
<?php
if(shell_exec('ps aux | grep '.__FILE__.' | wc -l')>3){
exit('already running...');
}
?>
另一種方法是使用文件標志和退出回調,退出回調將確保在 php 執行結束的任何情況下文件標志都將重置為 0 也是致命錯誤。
<?php
function exitProcess(){
if(file_get_contents('inprocess.txt')!='0'){
file_put_contents('inprocess.txt','0');
}
}
if(file_get_contents('inprocess.txt')=='1'){
exit();
}
file_put_contents('inprocess.txt','1');
register_shutdown_function('exitProcess');
/**
execute stuff
**/
?>
您可以選擇最適合您的項目的解決方案,實現這兩種簡單方法是文件鎖定或數據庫鎖定。
有關文件鎖定的實現,請查看http://us2.php.net/flock
如果您已經在使用數據庫,請創建一個表,為該腳本生成已知標記,將其放在那里,然后在腳本結束后將其刪除。 為避免錯誤問題,您可以使用到期時間。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.