如何优化cron调度的PHP脚本防止重复执行的实现方案
现有方案合理性评估
你当前的核心实现逻辑是合理的,基于flock的非阻塞独占锁机制本身可靠性很高,操作系统会在进程异常退出时自动回收其持有的锁,不会出现永久死锁的问题,常规场景下可以正常满足「单实例运行、已有实例运行时终止新进程」的需求。
现存可优化问题
- 两个不同的cron任务使用了同一个
cron.lock锁文件,会导致cron1和cron2两个任务互相抢占锁,无法同时运行,不符合两个独立任务的调度需求。 - 异常场景下资源处理不规范:业务代码如果抛出致命错误或被强制中断,可能不会执行到
flock($fp, LOCK_UN)和fclose($fp)逻辑;锁抢占失败的else分支直接exit,没有关闭已打开的文件句柄,虽然PHP会在脚本结束后自动回收资源,但不符合最佳实践。 - 锁文件路径未明确,cron执行时的工作目录可能和手动执行不一致,会导致锁文件被生成到随机目录,锁机制失效。
- 没有锁文件的权限校验,如果cron运行用户和锁文件所属用户不一致,会出现fopen失败或无法加锁的问题。
优化方案建议
- 给两个cron脚本分配独立的锁文件,比如cron1用
cron1.lock、cron2用cron2.lock,避免不同任务互相干扰。 - 把锁文件放在系统临时目录(通过
sys_get_temp_dir()获取)下,避免权限和工作目录的问题。 - 增加try-finally结构,保证无论业务逻辑是否正常执行,都能正确释放锁、关闭文件句柄。
- 可在拿到锁后写入当前进程PID到锁文件,方便后续排查异常残留的进程。
优化后的代码示例:
// cron1.php就用cron1.lock,cron2.php就替换成cron2.lock $lockFile = sys_get_temp_dir() . '/cron1.lock'; $fp = fopen($lockFile, 'a+'); if (!$fp) { echo "锁文件打开失败,请检查权限\n"; exit(1); } // 尝试加非阻塞独占锁 if (flock($fp, LOCK_EX | LOCK_NB)) { echo "任务开始执行\n"; // 写入当前进程PID方便排查 ftruncate($fp, 0); fwrite($fp, getmypid()); fflush($fp); try { // 此处替换为实际长耗时业务代码 sleep(2); } finally { // 无论业务是否报错都释放锁、清理资源 flock($fp, LOCK_UN); fclose($fp); // 可选:执行完成后删除锁文件避免残留 @unlink($lockFile); } } else { echo "已有任务实例在运行,退出本次执行\n"; fclose($fp); exit(0); }
内容的提问来源于stack exchange,提问作者Hid Dencum
相关产品推荐
相关产品推荐

