PHP无原生定时器,定时执行本质依赖外部调度(如crontab)或轮询模拟;直接sleep()会导致请求阻塞、超时,不可用于Web场景;最稳方案是Linux crontab配合绝对路径与环境变量配置。

PHP里没有真正的“定时器”,只有任务调度的模拟
PHP本身是无状态的脚本语言,不支持长期驻留内存的定时器(比如 setTimeout 那种)。所谓“定时执行”,本质是靠外部调度触发 PHP 脚本运行,或靠 PHP 主动轮询+睡眠来模拟。直接在 Web 请求中写 sleep(60) 等待再执行,会导致请求卡死、超时、资源占用,绝对不可行。
常见错误现象:max_execution_time 超时、Apache/Nginx 连接中断、Cron 日志里反复报 PHP Warning: Cannot modify header information(因 CLI 和 Web SAPI 行为混用)。
- Web 场景下想“每5秒查一次数据库”?别用
while(true) { sleep(5); ... }—— 这会阻塞整个请求,且一旦页面关闭,进程大概率被杀 - CLI 场景下写个常驻脚本?必须配合信号处理(
pcntl_signal)、避免内存泄漏(gc_collect_cycles()),否则跑几天就 OOM - 想用
setitimer或pcntl_alarm?它们只发信号,不能直接调函数,且 Windows 不支持
Cron 是最稳的调度入口,但要注意 PATH 和环境变量
Linux/macOS 的 cron 是事实标准,但 PHP 脚本在 Cron 下常因环境差异失败:Web 环境能跑的脚本,Cron 里报 Command not found 或连不上数据库。
关键原因:Cron 使用极简 shell(通常是 /bin/sh),$PATH 与用户登录态不同,~/.bashrc 不加载,PHP 扩展(如 pdo_mysql)可能未启用。
- 在 Cron 中始终用绝对路径:写
/usr/bin/php /var/www/app/artisan schedule:run,而不是php artisan schedule:run - 显式设置环境变量:
*/5 * * * * PATH=/usr/local/bin:/usr/bin:/bin HOME=/var/www APP_ENV=production /usr/bin/php /var/www/artisan schedule:run >> /var/log/laravel-schedule.log 2>&1 - Laravel 用户注意:
scheduler:run只是“检查该不该跑”,真正执行靠 Cron 每分钟调一次;Lumen 或原生项目得自己写判断逻辑(比如读取配置里的next_run_at时间戳)
PHP-FPM + Redis 实现轻量级延迟队列
当需要“30秒后发短信”“2小时后清理缓存”,又不想引入 RabbitMQ 或 Beanstalkd,可用 Redis 的 ZSET + PHP-FPM 定期扫描模拟延迟队列。
原理:把任务序列化后存入 ZSET,score 设为 UNIX 时间戳;另起一个简单 CLI 脚本(由 Cron 每10秒跑一次),用 ZRANGEBYSCORE 拿出 score ≤ now 的任务执行,并用 ZREM 删除。
容易踩的坑:ZRANGEBYSCORE 默认包含边界,得用 (now(左开)避免重复执行;并发时多个进程可能同时捞到同一任务,需加 SETNX 锁或用 Lua 原子操作。
redis-cli ZADD delayed_jobs 1717023600 "{\"job\":\"send_sms\",\"to\":\"138****1234\"}"
执行扫描脚本前,先确认 Redis 连接是 Persistent 模式(new Redis(); $redis->pconnect()),否则每秒新建连接会打爆 fd 限制。
ReactPHP 或 Swoole 是唯一可行的“真定时器”方案
如果硬要 PHP 进程内维持毫秒级精度、可取消、可重复的定时器,只能脱离传统 FPM 模型,改用事件驱动运行时。ReactPHP 提供 Loop::addPeriodicTimer,Swoole 有 Swoole\Timer::tick 和 ::after。
但代价明显:不能跑在 Apache/Nginx 下,必须用 php server.php 启动独立服务;Swoole 需编译安装扩展,ReactPHP 依赖大量 Composer 包,调试难度陡增。
- ReactPHP 示例中,
$loop->addPeriodicTimer(5.0, fn() => echo "tick\n");的 5.0 是秒,不是毫秒;回调里不能阻塞(如file_get_contents),得换React\Http\Browser - Swoole 的
tick回调里可以安全调用 MySQLi 或 PDO,但注意:每个 tick 回调都在同一个进程内执行,全局变量会累积,记得手动unset大数组 - 线上部署时,必须用
supervisord或systemd管理进程生命周期,否则崩溃即失联
多数业务根本不需要毫秒级精度——Cron 误差几十秒完全可接受,强行上 ReactPHP 反而增加运维复杂度。真要定时,先问清楚:这个“定时”是给用户看的提示,还是金融级对账的硬性要求?后者才值得动用 Swoole。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











