Laravel + Workerman 实现多进程定时器任务

前言

由于本人个人原因不太喜欢使用Linux的Crontab或者使用Supervisor去执行php的一些异步进程。
但是有了workerman的加持,我们可以使用workerman的一些方法特性去实现纯php版的异步功能。
因为workerman这个框架自身也是纯PHP实现的高性能异步PHP socket即时通讯框架,php加持php岂不美哉?
(PS:workerman死忠粉)

准备工作

Laravel版本:6.0
Workerman版本:4.0

workerman的运行环境需要安装pcntl和posix扩展。
我这里使用的是laravel的集成docker环境 laradock是可以直接使用的
Workerman环境具体可以访问:http://doc.workerman.net/install/install.h...
Laradock环境https://laradock.io/

具体实现

一、安装workerman

composer require workerman/workerman

二、编写Artisan控制台命令方便调用workerman

php artisan make:command WorkermanTimerCommand

可以看到在laravel的app\Console\Commands目录下生产了一个WorkermanTimerCommand.php文件。

三、接着我们开始编写workerman的控制台命令支持

<?php

namespace App\Console\Commands;

use App\Listens\WorkermanTimers;
use Illuminate\Console\Command;
use Workerman\Worker;

class WorkermanTimerCommand extends Command
{
    /**
     * 控制台命令的名称和参数.
     *
     * @var string
     */
    protected $signature = 'workerman {action} {--d}';

    /**
     * 控制台命令描述.
     *
     * @var string
     */
    protected $description = 'workerman的多进程定时任务';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        global $argv;
        $action = $this->argument('action');
        $argv[0] = 'wk';
        $argv[1] = $action;
        $argv[2] = $this->option('d') ? '-d' : '';
        $this->startServer();
    }

    /**
     * 启动workerman服务
     */
    public function startServer()
    {
        $worker = new Worker();
        // 服务名称.
        $worker->name = 'laravel timer';
        // 启动多少个进程数量,这里大家灵活配置,可以参考workerman的文档.
        $worker->count = 4;
        // 当workerman的进程启动时的回调方法.
        $worker->onWorkerStart = [WorkermanTimers::class, 'onWorkerStart'];
        // 当workerman的进程关闭时的回调方法.
        $worker->onClose = [WorkermanTimers::class, 'onClose'];
        Worker::runAll();
    }
}

可以看到在上面的startServer方法里,我们为onWorkerStartonClose分别注册回调到了WorkermanTimers类里面的onWorkerStartonWorkerStart方法。
这里的意思是当workerman里面的进程启动的时候,会调用WorkermanTimers类里面对应的方法!

那我们来看看这个WorkermanTimers类究竟需要干一些什么!

四、编写定时器任务分发类(WorkermanTimers)

我们现在app目录下新建一个Listens目录,并创建WorkermanTimers

WorkermanTimers类具体代码:


namespace App\Listens;

use Workerman\Lib\Timer;

class WorkermanTimers
{

    /**
     * 服务进程启动时
     * @param $businessWorker
     */
    public static function onWorkerStart($businessWorker)
    {
        // 拿到当前进程的id编号.
        $workid = $businessWorker->id;
        // 获取所有定时器任务配置.
        $timedTask = config('timers');
        if (is_array($timedTask)) {
            // 循环检测任务绑定.
            foreach ($timedTask as $key => $value) {
                // 绑定任务进程.
                if ($value['worker_id'] == $workid) {
                    Timer::add($value['time'], $value['func']);
                }
            }
        }

    }

    /**
     * 服务进程结束时
     * @param $client_id
     */
    public static function onClose($client_id)
    {
    }

}

解释:
以上onWorkerStart方法就是workerman每个进程刚启动的时候会回调过来当前进程的属性$businessWorker

我们在WorkermanTimerCommand里面定义的进程数count=4,就是4个进程。也就是说onWorkerStart方法会被调用四次,因为多进程里每个进程相互隔离,所以每次我们拿到的$businessWorker->id都会不一样。

worker进程的id编号,范围为0到$worker->count-1,所以每次$businessWorker->id编号应该是0,1,2,3

所以我们循环定义的timers配置,判断每个配置绑定的进程编号如果和当前的进程编号一致,我们就为当前进程增加一个定时器!

可能各位看官有疑问了,timers的配置在哪呢?
我们接着往下走

五、在config/目录下新增一个timers.php

具体配置如下:

<?php
return [
     // 定时任务名称.
    'say' => [
        'worker_id' => 1, // 需要绑定的进程id.
        'time' => 5, // 时间间隔 秒为单位.
        'func' => 'Facades\App\Services\TestService::testSay', // 定时执行的方法.
    ],
/*    'eat' => [
        'worker_id' => 2, // 需要绑定的进程id.
        'time' => 10, // 时间间隔 秒为单位.
        'func' => 'Facades\App\Services\TestService::testEat', // 定时执行的方法.
    ]*/
];

timers.php返回的是一个多维数据结构的配置,
数组一级元素代表这个定时任务的名称(为了区分和理解),
数组二级元素worker_id代表我们需要绑定到哪个进程
数组二级元素time表示时间间隔,多少秒执行一次
数组二级元素func 代表要执行哪个方法

在结合上面的WorkermanTimers类去看,就可以理解为:

任务:say绑定在进程编号为1的worker上面,每5秒就会执行一次\App\Services\TestService类里面的testSay方法

在laravel里面,命名空间前面加上Facades\就可以静态调用该类的方法,这部分的概念大家可以去看看laravel文档

六、编写定时任务类

我们在app目录下新建一个Services文件夹,并且新增一个TestService

TestService类具体代码:

<?php

namespace App\Services;

class TestService
{

    public function testSay()
    {
        var_dump("Hello Laravel and Workerman");
    }

}

七、测试

输入命令php artison workerman start 启动workerman

看看有没有任务执行:

成功

命令:

启动:

php artisan workerman start

启动常驻内存:

php artisan workerman start --d

其他命令:

php artisan workerman reload //重新加载配置
php artisan workerman reload //重启workerman
php artisan workerman stop //停止workerman

总结

流程:

1.当workerman的每个进程启动后,会回调给WorkermanTimers类的onWorkerStart方法
2.onWorkerStart会加载所有定时任务的配置,然后去循环绑定任务类
3.添加workerman定时间并绑定任务类方法!
4.每个进程绑定一个定时任务,当然也可以一个进程绑定多个定时器任务,具体取决于你怎么在timers.php里面配置

参考文档

Workerman
Laravel中文文档

其他

workerman是一款底层设计非常优秀的框架,纯php实现高性能常驻内存特性。
workerman能做的事情也远远不止于此,还有很多例如:IM、游戏、物联网等等功能等你去探索!
当优雅的Laravel 遇上 性能强悍得 Workerman 会发生点什么呢?

如果大家有兴趣的话后面我可以在写一些Laravel结合Workerman开发及时通信的例子,比如聊天室、弹幕什么的!

原文地址:https://utf8.hk/archives/workerman_laravel...

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 9

与laravel自带的队列和任务调度相比有何优势?

4年前 评论
assimon (楼主) 4年前

php 加持 php 岂不美哉

4年前 评论

global $argv; $argv[0] = 'wk'; $argv[1] = $action; $argv[2] = $this->option('d') ? '-d' : ''; 这个在哪里有用到?

4年前 评论

@Echoiii 全局变量,可以看一下symfony的console组件,调用workerman脚本的时候传递给workerman

4年前 评论
Echoiii 4年前

不是还得使用Supervisor守护进程 守护workerman的进程。。。

3年前 评论

这个自带crontab格式解析的: github.com/walkor/crontab

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!