缅怀革命先烈 致敬人民英雄|每年9月30日为烈士纪念日,缅怀革命先烈,致敬人民英雄。山河已无恙,吾辈当自强!

PHP 命令行脚本开发:CLI 模式与定时任务最佳实践

pantao
pantao 正式会员正式会员认证极客认证极客
发布于 2026-09-30 13:29 ·3 浏览 ·2 回复
本文转载自 Clara轻量论坛系统 - 轻量级 PHP 论坛系统,原文地址:https://www.leleweb.cn/thread-652.html
转载请注明出处,版权归原作者所有。
他们都看过 1 人浏览过
ipzh

全部回复 2

ipzh
ipzh 正式会员正式会员认证极客认证极客 恐龙宝宝 Lv3 1楼 2026-09-30 13:35

文件锁这块别用 PID 文件那套,直接在入口用 `flock` 的 `LOCK_EX | LOCK_NB` 最省事,非阻塞拿不到锁就静默退出,不算失败。

接着你断掉的那句「上一次跑了 8 分钟」往下说——5 分钟一次的任务重叠了,处理方式就是:

$lock = fopen(__DIR__ . '/.clean.lock', 'c');
if (!flock($lock, LOCK_EX | LOCK_NB)) {
    fwrite(STDERR, "上一次任务还在跑,跳过\n");
    exit(0);   // 注意是 0,不是 1,这不是错误
}
// ... 干活 ...
flock($lock, LOCK_UN);

几个配套细节:锁文件放 `sys_get_temp_dir()` 或脚本同目录都行,别放会被清理的目录;`exit(0)` 很重要,否则 crontab 每隔几分钟给你发一封告警邮件。另外建议在任务开头和结尾各打一行带时间戳的日志(写文件就行),排查「到底跑没跑」比看 crontab 邮件靠谱得多。

如果你是在 Clara 这套系统里做定时任务,其实不用自己挂 crontab——插件里用 `Cron::register` 注册的是懒触发机制,靠用户访问顺手触发,零配置。但缺点也明显:站点没流量时任务不跑。所以真正对时间敏感的任务(比如每日结算),还是 CLI + crontab 更稳,两者按场景选。

最后补个宝塔上的坑:crontab 的执行用户和 PATH 跟你在 SSH 里登录的完全不是一回事,脚本里凡是调用外部命令(mysqldump、zip 之类)一律写绝对路径,PHP 也建议直接用 `/www/server/php/74/bin/php` 全路径,别指望 `php` 能在 cron 的 PATH 里找到。

itjianghu
itjianghu 正式会员正式会员认证极客认证极客 #265 2楼 2026-09-30 13:43
ipzh:文件锁这块别用 PID 文件那套,直接在入口用 `flock` 的 `LOCK_EX | LOCK_NB` 最省事,非阻塞拿不到锁就静默退出,不算失败。 接着…

flock 这套我完全同意,`LOCK_NB` 抢不到就 `exit(0)` 是标准做法——尤其它比 PID 文件强的地方在于:脚本被 `kill -9` 时内核会自动释放锁,而 PID 文件会残留一个已经不存在的进程号,让你后面每一次都被误判成「还在跑」,这才是最坑的。

顺着你的话补两个边界,都是真踩过的:

一是锁不会自己过期。 flock 只在进程退出时释放,如果任务卡在慢查询或外部命令上,锁就被无限持有,之后每 5 分钟静默跳过,你只看到「任务不跑了」却不知道原因。建议在拿到锁后往锁文件里 `ftruncate` + 写入 PID 和时间戳,抢不到锁时读出来判断:超过阈值(比如 3 倍周期)就打一行 WARN,别让它无声无息。或者给任务本身加 `--timeout`,用 `pcntl_alarm` 兜底。

二是 flock 在网络文件系统上不可靠。 如果宝塔挂的是 NFS / 容器共享卷,`flock()` 可能退化成 POSIX 锁甚至直接失效,两实例同时跑。锁文件务必落在本地盘,`sys_get_temp_dir()`(通常是 /tmp)一般没问题,但注意 systemd-tmpfiles 默认会清 /tmp 里长期未访问的文件——低频任务(比如每月跑的)才有这个风险,高频任务不用担心。

另外一个偷懒但很好用的法子:锁干脆别写在 PHP 里,crontab 直接包一层——`/5 * flock -n /tmp/clean.lock /www/server/php/74/bin/php /path/run.php --task=clean`,现成脚本零改动就获得防重叠能力。

Clara 那边你说的懒触发没错,我补一句折中方案:时间敏感的任务用 crontab 去 `curl` 一个带 token 的内部触发地址,既走 Web 上下文(配置、框架、Session 都在),又由 crontab 保证准时,比纯 CLI 重写一套 bootstrap 省事。不过要记得给这个地址加白名单校验,别让它变成任意人可打的入口。

最后同感宝塔那个 PATH 坑——顺便把 crontab 里的 `MAILTO=""` 也加上,不然每次 `exit(1)` 系统都往 `/var/spool/mail` 塞信,时间久了能把磁盘啃掉一块。