You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP cURL定时执行冲突问题:如何避免多实例重叠运行

解决PHP脚本单实例高频执行问题(避免重叠)

你需要的是让scriptA.php在每分钟内尽可能多次运行,但绝对不能并发执行——核心是进程互斥锁,再配合Cron触发的scriptB.php来实现分钟内的循环执行。下面是几种可靠的实现方案:

方案一:PID文件锁(最常用、轻量)

让scriptA.php自己管理运行状态,不管通过什么方式触发,都能保证同一时间只有一个实例在跑。

修改scriptA.php,添加锁逻辑

在脚本最开头加入以下代码:

<?php
// 定义PID文件路径,选一个脚本有读写权限的目录(比如/tmp)
$pidFile = '/tmp/scriptA.pid';

// 检查是否已有运行的实例
if (file_exists($pidFile)) {
    $runningPid = trim(file_get_contents($pidFile));
    
    // 验证PID对应的进程是否真的存在(仅Linux/macOS可用,Windows需调整)
    if (function_exists('posix_kill') && posix_kill($runningPid, 0)) {
        // 进程仍在运行,直接退出
        error_log("scriptA is already running with PID: $runningPid");
        exit(1);
    } else {
        // PID文件残留但进程已死,清理旧文件
        unlink($pidFile);
    }
}

// 写入当前进程PID到文件
file_put_contents($pidFile, getmypid());

// 注册脚本退出时的清理函数(不管正常结束还是异常崩溃)
register_shutdown_function(function() use ($pidFile) {
    if (file_exists($pidFile) && trim(file_get_contents($pidFile)) == getmypid()) {
        unlink($pidFile);
    }
});

// 下面是你原来的查询API、写入数据库的逻辑
// ...

修改scriptB.php,实现分钟内循环执行

让scriptB.php在每分钟内重复调用scriptA.php,每次等待上一次执行完成后再启动下一次:

<?php
// 记录当前分钟的起始时间
$minuteStart = time();

// 循环执行,直到当前时间超过起始时间60秒
while (time() < $minuteStart + 60) {
    // 执行scriptA,等待它完成后再继续
    $output = shell_exec('php /绝对路径/scriptA.php');
    
    // 可选:如果scriptA执行极快,添加短暂延迟避免资源耗尽(比如0.1秒)
    usleep(100000);
    
    // 可选:记录执行日志
    error_log("scriptA executed at " . date('Y-m-d H:i:s') . " | Output: $output");
}

配置Cron

设置Cron每分钟运行一次scriptB.php:

* * * * * php /绝对路径/scriptB.php >> /var/log/scriptB.log 2>&1

方案二:MySQL数据库锁(适合已有数据库的场景)

如果你的脚本已经连接MySQL,可以用数据库的GET_LOCK机制实现互斥,不需要额外管理文件:

修改scriptA.php的锁逻辑

<?php
// 先建立MySQL连接(替换成你的数据库信息)
$db = new mysqli('localhost', 'username', 'password', 'database');
if ($db->connect_error) {
    die("数据库连接失败: " . $db->connect_error);
}

// 定义唯一的锁名称
$lockKey = 'scriptA_exclusive_lock';

// 尝试获取锁,超时时间设为0(立即返回,不等待)
$result = $db->query("SELECT GET_LOCK('$lockKey', 0) AS lock_acquired");
$row = $result->fetch_assoc();

if (!$row['lock_acquired']) {
    // 无法获取锁,说明已有实例在运行
    error_log("scriptA is already running - couldn't acquire lock");
    exit(1);
}

// 注册退出函数,释放锁
register_shutdown_function(function() use ($db, $lockKey) {
    $db->query("SELECT RELEASE_LOCK('$lockKey')");
    $db->close();
});

// 你的业务逻辑
// ...

关键注意事项

  • 权限问题:如果用PID文件,确保脚本对文件所在目录有读写权限(/tmp通常默认开放)。
  • 异常处理:register_shutdown_function能处理大多数退出场景,但如果服务器强制kill进程(比如kill -9),PID文件可能残留——不过我们已经在脚本开头做了进程存在性检查,会自动清理残留文件。
  • 资源监控:如果scriptA执行非常快,每分钟多次运行可能会占用较多系统资源,建议监控CPU/内存负载,必要时调整循环延迟。

内容的提问来源于stack exchange,提问作者John Chase

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:17:50