如何寫一個任務佇列

wilson_yang發表於2018-04-02

概述

任務佇列通常被我們用來處理一些非同步的耗時任務,在laravel中具體的用法可以看文件,那麼任務佇列是如何工作的呢?簡而言之,它也是一個生產者消費者模型,佇列處理器充當消費者不斷消費任務,任務生產者不斷向佇列中塞任務。基於這種思想,我們也可以自己嘗試著寫一個。

消費者

既然如此,我們首先需要一個處理(消費)任務的消費者。核心程式碼如下:

    public function run()
    { 
        while(true){
            $taskList = $this->getTaskList();
            if(empty($taskList)){
                $this->stop();
            }
            $this->consume($taskList);
        }
    }

    protected function consume($taskList)
    {
        while(!empty($taskList)){
            $job = array_pop($taskList);
            $job = unserialize($job);
            $job->handle();
        }
    }

    protected function stop()
    {
        posix_kill($this->pid,SIGSTOP);
    }

消費者說明
利用一個死迴圈,在任務佇列不為空的時候不斷消耗佇列,為空的時候則停止任務處理器程式,防止CPU空轉浪費資源。在laravel的佇列程式碼中使用的是sleep來間歇性停止程式,而不是採用上述程式碼中的這種SIGSTOP方式。

生產者

接下來我們就繼續寫一個生產者,例子中儲存任務使用的是redis,核心程式碼如下:


    public function pushTask()
    {
        //store job in the redis or other database
        $this->storeTask();
        $this->wakeupWorker();
    }

    protected function storeTask()
    {
        //store job in the redis or other database like this
        $redis = $this->getTaskContainer();
        $redis->lpush("task_list",serialize($this->job));
        //job must implements interface hanler
    }

    protected function wakeupWorker()
    {
        posix_kill($this->workerPid,SIGCONT);
    }

生產者說明
當有任務到來的時候,把任務持久化到某個任務容器中,然後喚醒消費者程式,使其消費任務。

結語

具體的程式碼示例,見 程式碼示例,測試用例只能執行在linux環境中(因為藉助了linux訊號機制),測試用例經過測試全部可用,測試用例中使用了redis儲存任務,可根據自己需要使用檔案或者mysql資料庫等其他方式儲存。
laravel中的消費者使用了sleep的方式間歇性喚醒自己去檢查佇列是否有可消費的物件,而不用藉助於生產者訊號喚醒,一定程度上降低了耦合性,生產者只需向任務佇列中塞任務即可,但是也會帶來一個問題就是如果長久沒有任務到來,生產者依然還是不斷地要喚醒自己,再掛起自己,這種也是某種形式的浪費資源。但是laravel的例子在任何平臺都是可以用的。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

每天進步一點點

相關文章