PHP:如何防止代码的多次执行(如果它已在进行中)

时间:2015-05-30 19:48:16

标签: php mysql cron

解释

API调用(到另一个服务),通常需要10-20秒才能响应,存储在数据库中,

存储后,系统会立即尝试使用API​​向用户显示结果,但可能会失败(并显示失败但我们会自动重试),因此还有{{1}设置为每30秒运行一次并再次尝试(失败)查询。

如果API返回成功(无论是即时使用还是使用Cron Job),该标志在数据库中更改为成功,并且不会再次运行。

问题

我的问题是在Cron Job到API的过程中,Instant Call也可能会尝试另一个调用,因为它尚未标记为成功,

同样在极少数情况下,当前一个Cron作业正在进行时,下一个Cron作业可能会再次运行该代码。

我已经尝试过阻止此问题

我尝试将Cron Job API调用存储在带有In Process的数据库表中,并在API调用成功时将其删除,或者如果失败则将状态设置为0,

Status=1
  

但如果 if ($status === 0) { // Set Status to 1 in Database First (or die() if database update failed) // Then Call The API // If Failed Set Status to 0 so Cron Job can try again // If Successful Change Flag to success and remove from queue } Instant Call恰好同时发生怎么办?它们都检查状态是否为0,然后将状态设置为1并执行API调用...

问题

  1. 我尝试过正确的方法来处理这个问题吗?

  2. 如果有很多电话(有时+ 500 /秒),我应该担心它们会在确切的时间发生(我在上面的黄色报价中解释过的问题)

  3. Bounty之前更新

    在PHP方面是不是真的有一种简单的方法来处理这种情况?如果没有,专家们认为哪种方式更好?下面是一些方法,但没有一个方法足够详细,没有任何一个有任何Downvotes / Upvotes。

    P.S。对数据库有很多更新/插入,我不认为锁定是一个有效的想法,我不确定其余的想法。

8 个答案:

答案 0 :(得分:13)

这正是Semaphore创建的原因。

在php中,它可以通过以下方式使用: 在PHP中使用信号量实际上非常简单。只有4个信号量函数:

sem_acquire() – Attempt to acquire control of a semaphore.
sem_get() – Creates (or gets if already present) a semaphore.
sem_release() – Releases the a semaphore if it is already acquired.
sem_remove() – Removes (deletes) a semaphore.

那么它们如何一起工作?首先,调用sem_get()来获取信号量的标识符。之后,您的一个进程将调用sem_acquire()来尝试获取信号量。如果它当前不可用,sem_acquire()将阻塞,直到信号量被另一个进程释放。获取信号量后,您可以访问您使用它控制的资源。完成资源后,调用sem_release()以便另一个进程可以获取信号量。完成所有操作后,您已确保所有进程都不再需要信号量,您可以调用sem_remove()来完全删除信号量。

您可以在this article

中找到有关此内容的更多信息和示例

答案 1 :(得分:4)

我在脚本中做的是 (伪代码)

SCRIPT START
LOCK FILE 'MYPROCESSFILE.LOCK'
DO SOMETHING I WANT
UNLOCK FILE 'MYPROCESSFILE.LOCK'
SCRIPT END

因此,如果文件被锁定,则第二个(重复的)进程将不会运行(将锁定/暂停/等待)UNTIL文件被原始进程解锁。

使用WORKING PHP代码更新了EDIT

<?php

    class Locker {

        public $filename;
        private $_lock;

        public function __construct($filename) {
            $this->filename = $filename;
        }

        /**
         * locks relevant file
         */
        public function lock() {
                touch($this->filename);
                $this->_lock = fopen($this->filename, 'r');
                flock($this->_lock, LOCK_EX);
        }

        /**
         * unlock above file
         */
        public function unlock() {
                flock($this->_lock, LOCK_UN);
        }

    }

    $locker = new Locker('locker.lock');
    echo "Waiting\n";
    $locker->lock();
    echo "Sleeping\n";
    sleep(30);
    echo "Done\n";
    $locker->unlock();

?>

答案 2 :(得分:2)

这里需要一个正确的排队解决方案。您可以使用队列表和表锁自己实现它,以避免不同的进程获取相同的作业。

所以你可以从队列表中选择这样的任务:

LOCK TABLES table WRITE;
SELECT * FORM table WHERE status = 0 LIMIT 1;
set status = 1 for the selected row
UNLOCK TABLES;

锁定表格可确保其他进程不会执行SELECT,也不会从表格中选取相同的行。

将作业插入队列就像这样简单:

INSERT INTO table (job_id, status) VALUES(NULL, status);

处理完成后删除作业:

DELETE FROM table WHERE job_id = 12345;

答案 3 :(得分:1)

在每个cron作业开始检查锁文件是否存在,如果在api进程完成后取消该文件,如果不是某个临时目录中的crate lock文件则退出退出。

答案 4 :(得分:1)

既然你应该知道cron运行的时间(比如说每5分钟一次),那么对于你的用户请求的函数,你可以检查一下系统时间是否恰好是应该运行cron的时间吗?这样做可以防止它们在同一时间运行。

答案 5 :(得分:1)

我在Linux上使用它来查看当需要避免多次执行时脚本是否正在运行:

$output = array();
exec('pgrep -fl the_script.php', $output);

然后浏览$output并确定它是否已在进行中。

例如,这里是现有代码的复制/粘贴:

$exec_output = array();
exec('pgrep -fl archiver.php', $exec_output);
$pid_count = 0;
foreach ($exec_output as $line) {
    $parts = explode(' ', $line);
    if (basename($parts[2]) == 'archiver.php') $pid_count++;
}

然后根据$pid_count做事。 basename()检查是为了确保我不会捕捉到special_archiver.php或其他任何可能存在的内容。您也可以检查完整路径。

答案 6 :(得分:0)

Semaphores可以安装在php中,对于内核级别的信号控制,它将以原子方式控制进程锁定。 Unix旨在将此机制与其他方法(如signals)一起用于进程间通信。不确定你是否需要那么复杂。

它可能会查看ps -ef的输出,但可能会受系统负载和进程优先级的影响。您可能会发现它使用数据库标志,但为什么增加开销?数据库可能会很忙。

我想如果你想每秒进行500次检查,一个简单的文件可能不太容易出现延迟问题。

e.g。如果cron脚本以

开头
if ( ! -f otherprocessisrunning)
then
   // create/open the file
   > cronprocessisrunning

   // when cron process finishes
   // it removes the cronprocessisrunning file
   rm -f cronprocessisrunning
else 
   sleep for 2 minutes
   call this function
fi

和另一个脚本在php中具有相同的行为

if (! file_exist(cronprocessisrunning))
    > otherprocessisrunning
    start the other process
    when it is finished, remove otherprocessisrunning
endif

它应该足够快,因为创建文件句柄(没有内容)转换为简单的系统调用。如果不是,请在bash shell中尝试。

答案 7 :(得分:0)

我不知道这是否是一个好方法:

git push otherproject mybranch:master

Cron职位:

脚本启动

temp_queue Table
-----------------------
id --> Int, Index, Autoincrement
query_id --> Int (your query ID or something to identificate a specific query)
in_use_by --> varchar (cron or api)

然后检查最后的SELECT结果

SELECT in_use_by FROM temp_queue ORDER_BY id ASC LIMIT 1;

if results != 0 return;

INSERT INTO temp_queue SET query_id=SOME_ID, in_use_by = 'cron';
SELECT in_use_by FROM temp_queue ORDER_BY id ASC LIMIT 1;

执行结束时:

if in_use_by == 'cron' continue
else return

API工作:

脚本启动

DELETE FROM temp_queue WHERE query_id=SOME_ID

然后检查最后的SELECT结果

SELECT in_use_by FROM temp_queue ORDER_BY id ASC LIMIT 1;

if results != 0 return;

INSERT INTO temp_queue SET query_id=SOME_ID, in_use_by = 'api';
SELECT in_use_by FROM temp_queue ORDER_BY id ASC LIMIT 1;

执行结束时:

if in_use_by == 'api' continue
else return

如果Cron Job&amp; API尝试在同一时间调用查询?他们都将使用query_id = SOME_ID检查第一个写入的行,因此只有1个继续。

是的,很多选择,插入和删除。但它确实有效。

你们对此有何看法?