PHP:如何防止多次执行代码(如果代码已经在处理中)

2022-08-31 00:47:44

解释

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

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

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

问题

我的问题是,当 to API 正在处理时,可能还会尝试另一个调用,因为它尚未标记为成功,Instant CallCron Job

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

我已经尝试过防止问题的原因

我尝试将API调用存储在数据库表中,并在API调用成功时将其删除,或者如果失败,则将状态设置为0,In ProcessStatus=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

 }
     

但是,如果和同时发生呢?它们都检查状态是否为0,然后都将状态设置为1并执行API调用...Instant CallCron Job Call

问题

  1. 我尝试过的正确处理方法是什么?

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

赏金前更新

在PHP方面,真的没有一种简单的方法来处理这种情况吗?如果不是,在专家看来哪种方式更好?以下是一些方法,但没有一个足够详细,也没有一个有任何下议/赞成票。

附言:数据库有很多更新/插入,我不认为锁定是一个有效的想法,我不确定其他想法。


答案 1

这正是创建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.

那么它们如何协同工作呢?

  1. 首先,调用以获取信号量的标识符。sem_get()
  2. 之后,您的一个进程将调用以尝试获取信号量。如果它当前不可用, 将阻塞,直到另一个进程释放信号量。sem_acquire()sem_acquire()
  3. 获取信号量后,您可以访问使用它控制的资源。
  4. 使用完资源后,调用,以便另一个进程可以获取信号量。sem_release()
  5. 当一切都说完了,并且您已经确保没有一个进程不再需要信号量时,您可以调用以完全删除信号量。sem_remove()

您可以在本文中找到有关此内容的更多信息和示例。


答案 2

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

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

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

编辑更新了工作 PHP 代码

<?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();

?>

推荐