PHP无内置可靠定时机制,必须依赖cron或Windows任务计划程序触发CLI脚本;禁用sleep()或pcntl_fork()实现常驻定时器;标准做法是cron调用PHP CLI脚本并规范日志、时区与错误处理。

PHP 本身没有内置的、可靠的长期运行定时任务机制,所有“PHP 定时任务”本质上都依赖外部调度器触发 PHP 脚本执行。主流架构下,必须用 cron(Linux)或 Task Scheduler(Windows)作为底层驱动,PHP 只负责单次、短时、无状态的任务逻辑。
为什么不能用 PHP 的 sleep() 或 pcntl_fork() 做常驻定时器
常见误区是写个死循环 + sleep(60) 来模拟定时,这在生产环境极不可靠:
-
sleep()会阻塞整个进程,无法响应信号、超时控制难,且一旦脚本异常退出,任务就永久中断 -
pcntl_fork()在 Web SAPI(如 Apache、FPM)下基本不可用,fork后子进程常被 SAPI 自动回收或权限限制 - FPM 模式下,每个请求独立生命周期,不支持跨请求维持状态或计时器
- 内存泄漏、句柄未释放等问题会在长周期运行中指数级放大
标准做法:用 cron 调用 CLI 模式 PHP 脚本
这是最稳定、可监控、易部署的方式。关键点在于确保脚本能在 CLI 环境下独立运行,且不依赖 Web 上下文(如 $_SERVER、Session、Cookie)。
- 脚本开头加
#!/usr/bin/env php并设可执行权限(chmod +x task.php),可直接./task.php运行 - 使用
php -f /path/to/task.php方式调用更通用,避免 shebang 兼容问题 - 务必在脚本内显式设置时区:
date_default_timezone_set('Asia/Shanghai');,否则 cron 使用系统时区,可能和 PHP-FPM 不一致 - 输出日志建议重定向到文件,例如:
*/5 * * * * /usr/bin/php /var/www/app/artisan schedule:run >> /var/log/laravel-cron.log 2>&1
Laravel 的 schedule:run 是怎么工作的
它不是替代 cron,而是把多个任务的定义集中管理,并由单个 cron 条目统一触发。本质仍是“每分钟拉起一次 PHP CLI 进程,检查当前是否该执行某任务”。
立即学习“PHP免费学习笔记(深入)”;
protected function schedule(Schedule $schedule)
{
$schedule->command('backup:database')->dailyAt('02:00');
$schedule->command('notify:pending')->everyFiveMinutes();
}对应 cron 条目只需一行:
* * * * * cd /var/www/myapp && php artisan schedule:run >> /dev/null 2>&1
- 每次执行
schedule:run都会遍历所有注册任务,比对时间表达式(dailyAt、everyFiveMinutes等),只真正调用符合条件的命令 - 所有命令必须是
Command类,且实现handle()方法,不能直接写裸逻辑 - 如果任务耗时超过 1 分钟,下次
cron触发时可能并发执行——需自行加锁(如用cache()->lock())
非 Laravel 项目如何轻量实现任务调度
不需要引入框架也能结构化管理定时任务。核心是抽象出“任务注册 + 时间判断 + 执行分发”三层:
- 用一个配置数组定义任务:
['backup' => ['interval' => '0 2 * * *', 'script' => '/path/to/backup.php']] - 主调度脚本读取配置,解析 crontab 格式(可用
crunz或cron-expression包),判断是否命中 - 用
proc_open()或shell_exec()异步调用目标脚本,避免阻塞主流程 - 务必记录执行结果与耗时,比如写入
syslog或简单文本日志,否则故障时完全无迹可查
真正容易被忽略的是错误隔离:一个任务崩溃不应影响其他任务执行,CLI 脚本必须用 try/catch 包裹顶层逻辑,并确保 exit(0) 正常退出——cron 把非零退出码视为失败,可能触发告警。











