Webman 的 Timer 在命令行脚本中必然失效,因其依赖 Workerman 事件循环,而 CLI 脚本无此循环;Timer 适用于毫秒级内部轮询,Crontab 才适合业务级定时任务。

Webman 的 Timer 在命令行脚本里必然失效,不是 bug,是设计如此——它依赖 Workerman 的事件循环,而 CLI 脚本没有。
为什么 Timer 在 php webman xxx 中报 “can only be used in workerman running environment”
这个错误不是配置问题,而是运行时环境缺失。Webman 的 Timer(底层是 Workerman\Timer)必须注册在 Workerman 的主事件循环中才能触发;命令行执行的 PHP 脚本是短生命周期、无事件循环的独立进程,Timer::add() 一调就直接抛异常。
常见误用场景:
- 在
command/xxx.php或任意php xxx.php脚本里调用Timer::add()或依赖它的组件(如ClientRedisQueue::send()) - 以为“只要用了 Webman 就能用 Timer”,忽略了进程模型差异
- 把本该放进程文件(
process/Task.php)里的定时逻辑,硬塞进命令行入口
Timer 和 Crontab 不是一个东西,别混用
Timer 是毫秒级单次/周期回调,适合内部状态轮询、连接保活、延迟任务;Crontab 是基于 cron 表达式的调度器,用于业务级定时任务(如每 5 秒拉一次 Redis 队列)。两者共用同一事件循环,但用途和生命周期完全不同。
关键区别:
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
-
Timer::add(1000, fn() => ...):每 1000ms 执行一次,不校验时间精度,只管“间隔” -
new Crontab('*/5 * * * * *', fn() => ...):严格按系统时钟对齐,每 5 秒整点触发(0s、5s、10s…) -
Timer回调若阻塞(如sleep(3)),会拖慢整个事件循环;Crontab同样串行,但表达式驱动,更易排查延迟来源
长连接场景下 Timer 失效的真凶:进程被卡死
WebSocket 或 TCP 长连接服务里,常有人用 Timer::add(30000, [$connection, 'ping']) 做心跳。但一旦某个连接的 onMessage 处理耗时过长(比如同步调用外部 HTTP 接口超时未设 default_socket_timeout),整个 Worker 进程的事件循环就被堵住,Timer 自然无法按时触发——这不是 Timer 失效,是它根本没机会跑。
排查信号:
- 日志里看到多个心跳时间戳严重偏移(如应 14:00:00 发,实际 14:00:28 才发)
-
top显示某个php start.php start -d进程 CPU 持续 100% - HTTP 接口响应变慢,甚至超时,说明 Worker 进程资源被占满
解决思路不是换 Timer,而是隔离耗时操作:把可能阻塞的逻辑扔进 Worker::runParallel(),或改用异步 HTTP 客户端(如 swoole_http_client 或 amphp/http-client)。
真正该用 Crontab 的地方,别手痒写 Timer
业务层定时任务(如“每 10 分钟统计在线用户数并写入 Redis”)必须用 Crontab,而不是靠 Timer::add(600000, ...) 模拟。原因很实在:
-
Timer依赖进程 uptime,重启/热更新后计时重置,容易漏执行 -
Crontab表达式自带对齐语义,配合date('U')可做幂等判断(如只处理next_run_at 的任务) -
Crontab实例可被php start.php reload重建,Timer不行——reload 后旧 Timer 全丢,新 Timer 得手动重新 add - 所有
Crontab任务共享一个调度上下文,方便统一加 try/catch + 日志埋点;Timer散落在各处,出错难归因
最易被忽略的一点:Crontab 不支持分布式锁,多机部署时必须自己加 Redis 锁或用外部调度器;而 Timer 根本没这问题——它压根跑不起来。

















