webman的crontab不支持毫秒级调度,因其基于秒级tick且表达式不解析毫秒;必须改用workerman\coroutine\timer::tick()配合swoole/swow事件循环,并隔离单进程、协程i/o与try/catch异常捕获。

Webman 的 Crontab 本身不支持毫秒级调度,所谓“高精度”必须绕过它,直接用协程原生定时器;否则无论怎么调表达式、改配置,最小粒度卡死在 1 秒,且容易被阻塞拖垮整个进程。
为什么 new Crontab() 永远达不到毫秒级
workerman/crontab 是基于字符串解析 + 秒级对齐的 PHP 实现,它的底层触发依赖 Worker 进程的主循环 tick(默认每秒检查一次),不感知毫秒。即使你写 '*/500 * * * * *',它也只会按秒判断——500 毫秒这个值根本不会被识别,更不会注册。
- 表达式始终按「秒分时日月周」6 位解析,但第 1 位(秒)只接受整数,不支持小数或子秒单位
- 所有任务共享同一个事件循环,一个
sleep(2)或慢 SQL 就会让后续所有定时器延迟执行 - 没有运行时重调度能力,无法动态补偿因阻塞丢失的时间点
改用 Workerman\Coroutine\Timer::tick() 才真能控毫秒
要实现 500ms、120ms 这类调度,必须放弃 Crontab 类,改用协程驱动下的原生定时器接口。前提是已启用 Swoole 或 Swow 事件循环(不能用默认 Select 驱动)。
- 在进程文件里
use Workerman\Coroutine\Timer; - 在
onWorkerStart()中调用Timer::tick(500, fn() => { ... });—— 第一个参数是毫秒数,支持整数和浮点(如120.5) - 绝对不要混用
Timer::add():它是非协程安全的旧接口,在 Swoole 下会被降级为 100ms 级别 - 回调内禁止同步 I/O(如
file_get_contents、sleep),否则会阻塞整个协程调度器
必须隔离进程 + 强制单 Worker 实例
毫秒级任务对 CPU 和事件循环极其敏感,哪怕和其他普通定时任务共用一个进程,都可能因资源争抢导致漂移。不能靠“加个 count => 2”来横向扩展,反而会加剧不一致。
- 在
config/process.php中单独声明一个进程,例如'micro_task' => ['handler' => app\process\MicroTask::class, 'count' => 1] -
count必须设为1:多实例会导致多个定时器同时触发,时间戳不同步,还可能重复执行 - 该进程配置中必须显式指定
eventLoop => Workerman\Events\Swoole::class(或Swow::class),否则 fallback 到低精度驱动 - 验证是否生效:在
onWorkerStart()里var_dump(get_class(Workerman\Events\EventInterface::getEvent()));,输出应为Swoole或Swow
回调里不加 try/catch 就等于裸奔
协程定时器一旦在回调中抛出未捕获异常(比如数据库连接失败、Redis 超时),整个 Timer::tick() 实例会静默退出,后续再也不会触发——没有任何错误日志,进程也不报错,就像任务“消失”了一样。
- 每个定时器回调必须包裹
try/catch,至少记录到文件或 syslog - 避免在回调里做耗时操作;真要查库,用
Co\Mysql或Co\Redis协程客户端 - 如果需要严格单调时间校准(比如防重复、对账),得手动调用
Co::gettimeofday(true)获取微秒级时间戳,别信time()或date()
真正难的不是写对那行 Timer::tick(),而是确保整个链路——驱动、进程、I/O、错误处理——全部协同在协程语义下运转。少一环,毫秒级就退化成秒级,甚至彻底失效。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











