Laravel + Workerman 實現多程式定時器任務

assimon發表於2020-03-31

前言

由於本人個人原因不太喜歡使用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 協議》,轉載必須註明作者和本文連結

相關文章