比如我有很多定时器,有指定时间执行的比如每天0点28分执行【28 0 】,有固定间隔时间段执行的比如每五分钟执行异常【0 /1 】,还可以动态的添加管理这些定时器
有参考过插件市场的yzh52521/webman-task,启动两个服务,一个提供http请求处理添加删除操作,一个启动自定义进程来实现定时任务的执行,在内部http和自定义进程通讯来进行数据交互
但是原生的Crontab定时任务是阻塞执行的,如果有一个执行很久会导致剩下的不能再对应时间执行,阻塞期间http服务与自定义进程之间的通讯还会挂起
然后试过使用swoole做事件循环,在new Crontab的callback中使用go(function(){}),这样确实可以实现不阻塞的执行定时程序,也不会阻塞自定义进程的通讯服务,但是随着定时程序的数量、耗时增多,后面查看日志会发现时不时的exit with status 11、exit with status 65280报错,然后进程自动重启,在runtimes下面也看不到具体的报错日志
后面又试过直接使用swoole开启tcp服务,在workerStart里面初始化定时器,在receive里面接受http的请求来管理定时器执行,因为swoole不支持linux的crontab表达式,webman的实现是生成一个回调函数,拿到下一分钟要执行的定时任务依次Timer::add生成定时器,我把Timer::add替换成\Swoole\Timer::after就可以在swoole中使用crontab表达式,执行的时候也是Co::create()执行的,然后发现定时器可以正常按时间执行,但是定时器执行后tcp服务就被挂起了,无法和http交互
请问各位大佬有类似实现过对应功能吗,怎么解决支持秒级的定时器执行,并且可以进行通讯、定时任务执行期间不阻塞服务的
架构一般是一个定时进程,一堆http业务进程。
定时进程负责定时触发任务,使用workerman/http-client异步调用,这样定时进程不会有任何阻塞操作。
http业务进程负责处理具体的定时任务,任务本身是否阻塞没有太大影响,只要http进程足够就行。
可以考虑使用队列进行业务逻辑处理,定时器只用于发送队列消息,这样就不需要考虑定时器的阻塞问题了
使用php协程,参考文档
https://www.workerman.net/a/1723
https://ripple.cloudtay.com/docs/basic/defer/
在定时器里面用协程执行任务,定时器会立即返回,开始执行下一个定时器,并不会阻塞,这样解决了多个定时器互相阻塞的问题
协程还是当前进程执行,定时任务里如果是阻塞调用,整体进程还是阻塞的。这样就协程没有作用
Fiber/Revolt
那一套遇到死循环确实只能傻眼,但是Swoole
有抢占式调度。后续还是用swoole实现了,swoole开启的tcp服务设置运行协程和一键协程化之后,tcp的通讯服务和定时任务就互不阻塞了。其中最主要的问题还是用swoole作为webman的eventloop之后,看起来是互相冲突导致webman子进程重启,没这个问题的话两个一起用还是挺方便的
$server = new Server($host, (int)$port, SWOOLE_PROCESS);
// 设置服务器为异步非阻塞模式
$server->set([
'worker_num' => 1,
'enable_coroutine' => true,
'max_coroutine' => 3000,
'daemonize' => true,
'log_file' => runtime_path() . '/logs/swoole.log',
'log_date_format' => '%Y-%m-%d %H:%M:%S',
'log_rotation' => SWOOLE_LOG_ROTATION_DAILY,
'display_errors' => true,
'hook_flags' => SWOOLE_HOOK_ALL,
]);
// 当有客户端数据到达时
$server->on('receive', function (Server $server, $fd, $reactor_id, $data) {
Co::create(function () use ($server, $fd, $data) {
// echo "Received data from client {$fd}: {$data}\n";
try {
$data = json_decode($data, true);
$method = $data['method']?:'';
$args = $data['args']?:[];
$res = $this->onReceive($method,$args);
$server->send($fd,$res);
}catch (\Exception $exception){
$server->send($fd,json_encode(['code' => 0, 'msg' => $exception->getMessage() ]));
}
});
});
插件市场:https://www.workerman.net/app/view/cron
最关键的逻辑是使用composer require symfony/process
这是直接一个定时器跑了一个进程吗?
执行的时候使用proc_open 执行一个命令,并且打开用来输入/输出的文件指针。
你应该已经完成。看到这个想到上家公司自己做的定时调度,感觉也挺好。用ai把我记忆中上家公司的实现流程总结出来了。供参考:
你的方案是可行的,尤其是结合了多进程、定时调度、任务状态管理以及异步执行的设计,符合大多数任务调度系统的最佳实践。以下是对你的方案的总结和优化建议:
方案总结
1. 任务调度表设计
2. 多进程调度
symfony/process
启动多个子进程,让每个任务在独立的进程中执行,避免任务之间的相互阻塞或影响。3. Crontab 表达式解析
composer
包(例如mtdowling/cron-expression
)来解析任务的 crontab 表达式,从而决定任务的下次执行时间。这可以保证灵活的任务调度。4. 协程与异步执行
swoole
提供的协程来进一步优化任务的异步执行,尤其是涉及网络请求、数据库 I/O 等操作时,协程能有效提升任务的执行效率,避免阻塞。RabbitMQ
、Redis
)进一步解耦任务调度和执行,确保任务的异步处理和高效并发。5. 任务锁与并发控制
SETNX
来加锁任务,确保每个任务在同一时刻只会被一个进程处理。任务执行完毕后,释放 Redis 锁。SELECT ... FOR UPDATE
)或文件锁(flock
)来加锁任务。6. 任务超时与状态管理
symfony/process
或swoole
的超时机制确保任务不会长时间占用资源,任务执行时间超过设定的超时时间后自动终止,并标记为“超时”状态。7. 任务日志记录
优化建议
任务锁定的策略:
负载均衡与扩展:
监控与告警:
Prometheus
、Grafana
)来监控任务的执行状态与性能,尤其在任务超时、失败等异常情况下,触发告警通知以便及时处理。总结
总体来说,你的方案充分考虑了定时调度、任务并发、异步执行、任务状态管理和日志记录,具备较强的扩展性和鲁棒性。通过适当的锁机制、进程管理与协程结合,能够实现一个高效、稳定的任务调度系统。