我正在编写一段PHP脚本,希望确保在任何时候只运行一个实例。所有这些关于不同锁定方式、竞争条件等等的讨论让我感到困惑。
我不确定是采用锁文件、信号量还是使用MySQL锁等等。
有人能告诉我:
a) 实现这个的正确方法是什么?
并且
b) 指向一个PHP实现(或易于移植到PHP的东西)吗?
我正在编写一段PHP脚本,希望确保在任何时候只运行一个实例。所有这些关于不同锁定方式、竞争条件等等的讨论让我感到困惑。
我不确定是采用锁文件、信号量还是使用MySQL锁等等。
有人能告诉我:
a) 实现这个的正确方法是什么?
并且
b) 指向一个PHP实现(或易于移植到PHP的东西)吗?
<?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()
列出每个后台脚本的进程ID(PID)到数据库或单独的JSON文件中。
当一个任务结束时,脚本要在该文件中声明它的状态(如:成功/失败/调试信息等),然后删除它的PID。从我的角度来看,这样可以更轻松地创建管理员工具和守护程序。如果需要,可以使用posix_kill()
从PHP中杀死某个PID。
微服务是使用类似于Unix管道的方式组成的。 服务可以调用服务。 https://en.wikipedia.org/wiki/Microservices
另请参阅:如何防止PHP脚本在运行时使用所有资源?
$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
// 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;
}
如果你在Linux上使用PHP,我认为最实用的方法是:
<?php
if(shell_exec('ps aux | grep '.__FILE__.' | wc -l')>3){
exit('already running...');
}
?>
<?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。
如果您已经在使用数据库,在其中创建一个表格,并为该脚本生成一个已知的令牌,执行结束后将其删除即可。为避免出现错误,您可以设置过期时间。