PHP:如何防止代码的多次执行(如果已经在执行中) 解释
数据库中存储了一个API调用(对另一个服务的调用),通常需要10-20秒才能响应 存储后,系统将立即尝试使用API向用户显示结果,但可能会失败(并显示失败,但我们将自动重试),因此还有一个PHP:如何防止代码的多次执行(如果已经在执行中) 解释,php,mysql,cron,Php,Mysql,Cron,数据库中存储了一个API调用(对另一个服务的调用),通常需要10-20秒才能响应 存储后,系统将立即尝试使用API向用户显示结果,但可能会失败(并显示失败,但我们将自动重试),因此还有一个Cron作业设置为每30秒运行一次,并再次尝试(失败的)查询 如果API返回success(无论是即时使用还是使用Cron作业),则该标志在数据库中更改为success,并且不会再次运行 问题 我的问题是,当对API的即时调用正在进行时,Cron作业可能还会尝试另一个调用,因为它尚未标记为成功 同样在极少数情况
Cron作业
设置为每30秒运行一次,并再次尝试(失败的)查询
如果API返回success(无论是即时使用还是使用Cron作业),则该标志在数据库中更改为success,并且不会再次运行
问题
我的问题是,当对API的即时调用
正在进行时,Cron作业
可能还会尝试另一个调用,因为它尚未标记为成功
同样在极少数情况下,当前一个Cron作业正在进行时,下一个Cron作业可能会再次运行代码
我已经试图阻止这个问题
我尝试将进程中的API调用存储在状态=1
的数据库表中,并在API调用成功时将其删除,或者在调用失败时将状态设置为0
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
}
但是如果即时调用
和Cron作业调用
同时发生会怎样?他们都检查状态是否为0,然后都将状态设置为1并执行API调用
问题
我所尝试的是正确的处理方法吗
如果有很多电话(有时+500/秒),我是否应该担心电话发生在准确的时间(我在上面的黄色引号中解释的问题)
悬赏前更新
在PHP端是否真的有一种简单的方法来处理此类情况?如果没有,专家认为哪种方式更好?以下是一些方法,但没有一种方法足够详细,也没有一种方法有任何反对票/赞成票
另外,数据库有许多更新/插入,我不认为锁定是一个有效的想法,我不确定其余的想法。在每个cron作业开始时,检查锁定文件是否存在,如果退出,如果不退出,请在api进程完成后将锁定文件装入某个临时目录中,取消链接该文件。因为您应该知道cron将运行的时间(比如每5分钟一次)那么,对于用户请求的函数,您是否可以检查系统时间是否正好是cron应该运行的时间?这至少会阻止他们在同一时间跑步 我在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\u count
进行操作。basename()
检查是为了确保我没有捕捉到像special\u archiver.php
或任何可能存在的东西。您还可以检查完整路径。可以安装在php中,对于内核级别的信号控制,它将自动控制进程锁定。Unix被设计为将此机制与其他方法(如进程间通信)一起使用。我不确定你是否需要那么老练
它可以通过查看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
它应该足够快,因为创建文件句柄(没有内容)转化为一个简单的系统调用。如果不是,请在bashshell中尝试。这正是为创建的原因
在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\u get()
获取信号量的标识符
之后,您的一个进程将调用sem\u acquire()
,尝试获取信号量。如果当前不可用,sem_acquire()
将阻塞,直到另一个进程释放信号量
一旦信号量被获取,您就可以访问您用它控制的资源
使用完资源后,调用sem\u release()
,以便另一个进程可以获取信号量
当所有这些都说了又做了,并且您已经确保您的进程不再需要该信号量时,您可以调用sem\u remove()
来完全删除该信号量
您可以在中找到有关此问题的更多信息和示例。您需要一个合适的排队解决方案。您可以使用队列表和表锁自己实现它,以避免不同的进程拾取相同的作业
因此,您可以从队列表中拾取任务,如下所示:
LOCK TABLES table WRITE;
SELECT * FORM table WHERE status = 0 LIMIT 1;
set status = 1 for the selected row
UNLOCK TABLES;
锁定表将确保其他进程不执行选择,也不从表中拾取同一行
将作业插入队列非常简单,如下所示:
INSERT INTO table (job_id, status) VALUES(NULL, status);
处理完成后删除作业:
DELETE FROM table WHERE job_id = 12345;
我不知道这是否是个好办法:
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)
Cron作业:
脚本开始
然后检查最后的选择结果
if in_use_by == 'cron' continue
else return
if in_use_by == 'api' continue
else return
执行结束时:
DELETE FROM temp_queue WHERE query_id=SOME_ID
DELETE FROM temp_queue WHERE query_id=SOME_ID
API作业:
脚本开始
然后检查最后的选择结果
if in_use_by == 'cron' continue
else return
if in_use_by == 'api' continue
else return
执行结束时:
DELETE FROM temp_queue WHERE query_id=SOME_ID
DELETE FROM temp_queue WHERE query_id=SOME_ID
如果Cron作业和API试图同时调用查询,会发生什么情况?它们都将检查query\u id=SOME\u id的第一个写入行,因此只有1个具有continue
是的,有很多选择、插入和删除。但它是有效的
你们怎么看?我在脚本中做的是
(伪代码)