symfony scheduler 不支持秒级调度,因其最小轮询间隔硬编码为60秒且任务时间对齐分钟边界;可行方案是用supervisor管理常驻php脚本,每秒轮询并自行处理信号、并发与超时。

Symfony Scheduler 本身不支持秒级调度,它底层依赖 PHP 的 sleep() 和事件循环,但官方明确不保证亚分钟级精度,实际运行中最小粒度接近 60 秒 —— 想靠它跑 * * * * * * 这种秒级 cron 是行不通的。
为什么 Symfony Scheduler 不能做秒级任务
Symfony Scheduler 是为“分钟级及以上”场景设计的:它启动后以固定间隔(默认 60 秒)轮询 schedule:run 命令,检查是否有到期任务;所有任务触发时间都对齐到分钟边界(例如 10:00、10:01),不会在 10:00:05 这样的时刻执行。即使你写 ->everySecond(),框架也会静默降级为每分钟一次。
- 源码里
Scheduler::tick()的最小间隔硬编码为60秒 - CLI 命令
scheduler:run默认只响应 SIGTERM,无法被秒级信号唤醒 - 没有内置心跳机制或异步计时器支持
替代方案:用 Supervisor + 原生 PHP 脚本实现秒级轮询
真正可行的做法是绕过 Scheduler,用轻量脚本 + 进程管理器控制生命周期。核心思路:一个常驻 PHP 进程,每秒检查一次时间并触发对应逻辑,由 Supervisor 确保崩溃后自动重启。
- 写一个
src/Command/SecondlyCronCommand.php,继承Command,execute()方法里用while (true)+sleep(1)循环 - 每次循环内用
date('s')或microtime(true)判断是否满足秒级条件(比如每 5 秒执行一次就检查$now % 5 === 0) - 在
supervisord.conf中配置:command=php bin/console app:secondly-cron,autostart=true,autorestart=true - 务必加
pcntl_signal(SIGTERM, fn() => exit(0));处理优雅退出,否则 Supervisor kill 时进程可能僵死
注意信号与并发安全问题
秒级脚本一旦常驻,就会面临真实生产环境中的典型陷阱:
- 多个实例同时运行:Supervisor 配置了
numprocs=2却没加文件锁,导致同一任务被执行两次 —— 必须用flock()或数据库行锁控制临界区 - 上一轮执行未结束,下一轮已触发:比如任务耗时 1.2 秒,而循环间隔是 1 秒,必须在
execute()开头加if (file_exists('/tmp/secondly_cron.lock')) { return Command::FAILURE; }类似的互斥判断 -
sleep(1)实际误差可能达 100ms,若需严格对齐整秒(如 10:00:00),得用$next = ceil(microtime(true)); sleep($next - microtime(true));补偿
真正要跑秒级任务,就得接受它不是“配置一下 Scheduler 就完事”的事情——得自己管进程、管信号、管并发、管超时。Symfony Scheduler 的价值在于简化分钟级以上任务的声明式定义,而不是替代系统 cron 或自研调度器。











