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

PHP创建Cron执行脚本的正确方式及现有问题解决

PHP定时任务(Cron)的正确实现方案

针对你遇到的文件锁卡住、脚本超时,以及双重Cron触发的问题,以下是具体的修复和优化方案:

一、文件锁问题的修复

当前代码的锁处理存在漏洞,容易导致锁无法释放:

  1. 文件打开模式错误:使用r+模式需要文件提前存在,改为c+模式可自动创建文件,避免找不到文件的错误;
  2. 锁释放不严谨:缺少异常捕获,脚本如果意外退出(比如致命错误)会导致锁一直持有;另外flock($fp, LOCK_UN)末尾少了分号,语法错误会导致锁无法释放;
  3. 无意义等待:sleep(2)完全没必要,只会增加脚本执行时间。

改进后的锁处理逻辑:

$lockFile = __DIR__ . '/perform_cronjobs_lock.txt';
$fp = fopen($lockFile, 'c+');
if (!$fp) {
    die("无法打开锁文件");
}

// 使用try-finally确保锁一定会释放
try {
    if (!flock($fp, LOCK_EX | LOCK_NB, $blocked)) {
        if ($blocked) {
            die("定时任务正在执行中");
        } else {
            die("获取锁失败");
        }
    }

    // 写入当前PID和时间,方便排查卡住的情况
    ftruncate($fp, 0);
    fwrite($fp, "PID: " . getmypid() . " | 执行时间: " . date('Y-m-d H:i:s'));
    fflush($fp);

    // 这里放任务执行逻辑
} finally {
    flock($fp, LOCK_UN);
    fclose($fp);
}

二、超时问题的解决

  1. 避免依赖set_time_limit:如果是通过CLI执行PHP脚本(本地Cron推荐方式),set_time_limit默认是无限制的;如果是外部Cron通过HTTP访问,Web服务器(比如Nginx/Apache)也会有自己的超时设置,需要同时调整;
  2. 任务拆分:不要单次执行过多任务,当前的$limit -= 0.1逻辑很混乱,改为按固定数量拆分,比如每次最多处理10个普通任务、50个RSS任务,剩下的留给下一次Cron执行;
  3. 优化任务逻辑:检查perform_cronjob方法里的数据库操作,给查询字段加索引,避免全表扫描;减少不必要的IO操作,比如合并数据库请求。

三、Cron管理规范

  1. 停止重复触发源:立即停用外部Cron或本地系统Cron中的一个,避免两个源同时触发导致锁冲突;
  2. 优先使用CLI模式执行:本地Cron直接调用PHP CLI执行脚本,比如:
    * * * * * /usr/bin/php /path/to/perform_cronjobs.php >> /path/to/cron_logs.log 2>&1
    
    这种方式比HTTP访问更稳定,没有Web服务器的超时限制,也能直接输出日志;
  3. 添加日志记录:在脚本中记录每个任务的执行情况,方便排查问题:
    $logFile = __DIR__ . '/cron_logs.log';
    function writeLog($msg) {
        global $logFile;
        file_put_contents($logFile, date('Y-m-d H:i:s') . " - " . $msg . "\n", FILE_APPEND);
    }
    
    // 任务执行后记录
    writeLog("执行了RSS读取任务,剩余限制: {$limit}");
    

四、优化后的完整代码示例

<?php
// CLI模式下无需设置时间限制,网页模式可根据需要调整
if (php_sapi_name() !== 'cli') {
    set_time_limit(120);
}

$lockFile = __DIR__ . '/perform_cronjobs_lock.txt';
$logFile = __DIR__ . '/cron_logs.log';
$fp = fopen($lockFile, 'c+');
if (!$fp) {
    writeLog("无法打开锁文件");
    die("无法打开锁文件");
}

function writeLog($msg) {
    global $logFile;
    file_put_contents($logFile, date('Y-m-d H:i:s') . " - " . $msg . "\n", FILE_APPEND);
}

try {
    if (!flock($fp, LOCK_EX | LOCK_NB, $blocked)) {
        if ($blocked) {
            writeLog("任务已在执行,跳过本次触发");
            die("任务已在执行");
        } else {
            writeLog("获取锁失败");
            die("获取锁失败");
        }
    }

    ftruncate($fp, 0);
    fwrite($fp, "PID: " . getmypid() . " | 执行时间: " . date('Y-m-d H:i:s'));
    fflush($fp);

    writeLog("开始执行定时任务");

    $pageid = "perform_cronjobs.php";
    include_once(__DIR__ . "/backend/include.php");

    $currentMinute = (int)date("i");
    if ($currentMinute === 30) {
        // 每小时执行的任务
        writeLog("执行每小时任务");
    } else {
        // 每分钟执行的任务
        $list = cronjob::select_all_past_cronjob_classes();
        $normalLimit = 10; // 普通任务每次最多执行10个
        $rssLimit = 50; // RSS任务每次最多执行50个

        foreach($list as $cronjob){
            if ($cronjob->name === "manual_action") {
                continue;
            }

            if ($cronjob->name === "read_rss") {
                if ($rssLimit <= 0) {
                    continue;
                }
                $cronjob->perform_cronjob();
                $rssLimit--;
                writeLog("执行RSS读取任务,剩余配额: {$rssLimit}");
            } else {
                if ($normalLimit <= 0) {
                    continue;
                }
                $cronjob->perform_cronjob();
                $normalLimit--;
                writeLog("执行任务: {$cronjob->name},剩余配额: {$normalLimit}");
            }
        }
    }

    end_of_file($pageid);
    writeLog("定时任务执行完成");

} catch (Exception $e) {
    writeLog("任务执行出错: " . $e->getMessage());
    throw $e;
} finally {
    flock($fp, LOCK_UN);
    fclose($fp);
}

function end_of_file($pageid) {
    // 你的数据库操作逻辑
}
?>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 02:15:42