laravel綜合話題:隊列——異步消息的定義


一般從概念上說,隊列只是一個存放消息的目的地,隊列的使用者是消息的生產者和消息的發送者。但在php-fpm模式中,我們自己的程序通常不會常駐內存,在本篇文章中,將依照laravel中文文檔及源碼探索laravel隊列的實現方式。

簡介

Laravel 隊列爲不同的後臺隊列服務提供統一的 API,例如 Beanstalk,Amazon SQS,Redis,甚至其他基於關係型數據庫的隊列。隊列的目的是將耗時的任務延時處理,比如發送郵件,從而大幅度縮短 Web 請求和響應的時間。

laravel隊列中的服務像Beanstalk、Amazon SQS,Redis、關係數據庫,只提供數據存儲服務,異步消息的分發由laravel控制。

鏈接 Vs. 隊列

在開始使用 Laravel 隊列前,弄明白 「連接」 和 「隊列」 的區別是很重要的。在你的 config/queue.php 配置文件裏,有一個 connections 配置選項。這個選項給 Amazon SQS,Beanstalk,或者 Redis 這樣的後端服務定義了一個特有的連接。不管是哪一種,一個給定的連接可能會有多個 「隊列」,而 「隊列」 可以被認爲是不同的棧或者大量的隊列任務。

隊列與連接的概念可以類比數據庫,在數據庫配置中,可以有多個連接,每個連接可以指定一個驅動,一個鏈接雖然指定了一個數據庫,但一個連接可以有多個數據表,數據表纔是最後存儲數據地方;在隊列配置中,不同的是,一個隊列與一個數據表的概念更吻合。

要注意的是,queue 配置文件中每個連接的配置示例中都包含一個 queue 屬性。這是默認隊列任務被髮給指定連接的時候會被分發到這個隊列中。

隊列的名稱類似於數據庫中的表名,但是在後面我們可以發現,隊列驅動使用Redis服務時,隊列的名稱由Redis的key標識,隊列的內容是List類型。

創建任務

生成任務類

在你的應用程序中,隊列的任務類都默認放在 app/Jobs 目錄下。如果這個目錄不存在,那當你運行 make:job Artisan 命令時目錄就會被自動創建。你可以用以下的 Artisan 命令來生成一個新的隊列任務:

php artisan make:job SendReminderEmail

生成的類實現了 Illuminate\Contracts\Queue\ShouldQueue 接口,這意味着這個任務將會被推送到隊列中,而不是同步執行。

ShouldQueue接口只是一個標識,因爲在laravel任務調度中同樣使用了任務,ShouldQueue只是說明是否要放入隊列中執行。

<?php

namespace Illuminate\Contracts\Queue;

interface ShouldQueue
{
    //
}

任務類結構

任務類的結構很簡單,一般來說只會包含一個讓隊列用來調用此任務的 handle 方法。

<?php

namespace App\Jobs;

use App\Podcast;
use App\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;

class ProcessPodcast implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $podcast;

    /**
     * 創建一個新的任務實例。
     *
     * @param  Podcast  $podcast
     * @return void
     */
    public function __construct(Podcast $podcast)
    {
        $this->podcast = $podcast;
    }

    /**
     * 運行任務。
     *
     * @param  AudioProcessor  $processor
     * @return void
     */
    public function handle(AudioProcessor $processor)
    {
        // Process uploaded podcast...
    }
}

分發任務

你寫好任務類後,就能通過 dispatch 輔助函數來分發它了。

ProcessPodcast::dispatch($podcast);

dispatch方法來自於trait Dispatchable

<?php

namespace Illuminate\Foundation\Bus;

trait Dispatchable
{
    /**
     * Dispatch the job with the given arguments.
     *
     * @return \Illuminate\Foundation\Bus\PendingDispatch
     */
    public static function dispatch()
    {
        return new PendingDispatch(new static(...func_get_args()));
    }

    /**
     * Set the jobs that should run if this job is successful.
     *
     * @param  array  $chain
     * @return \Illuminate\Foundation\Bus\PendingChain
     */
    public static function withChain($chain)
    {
        return new PendingChain(get_called_class(), $chain);
    }
}

我們在Dispatchable源碼中可以看到,dispatch方法返回一個PdendingDispatch類的實例,原來Job對象將作爲PendingDispatch構造方法的參數,存儲於PendingDispatch實例中,對Job類之後的操作將作用於PendingDispatch類的實例上。我們查看PendingDispatch類的源碼可以發現,PendingDispatch類並沒有複雜的結構,只是作爲我們定義任務類的委託。

<?php

namespace Illuminate\Foundation\Bus;

use Illuminate\Contracts\Bus\Dispatcher;

class PendingDispatch
{
    /**
     * The job.
     *
     * @var mixed
     */
    protected $job;

    /**
     * Create a new pending job dispatch.
     *
     * @param  mixed  $job
     * @return void
     */
    public function __construct($job)
    {
        $this->job = $job;
    }

    /**
     * Set the desired connection for the job.
     *
     * @param  string|null  $connection
     * @return $this
     */
    public function onConnection($connection)
    {
        $this->job->onConnection($connection);

        return $this;
    }

    /**
     * Set the desired queue for the job.
     *
     * @param  string|null  $queue
     * @return $this
     */
    public function onQueue($queue)
    {
        $this->job->onQueue($queue);

        return $this;
    }

    /**
     * Set the desired connection for the chain.
     *
     * @param  string|null  $connection
     * @return $this
     */
    public function allOnConnection($connection)
    {
        $this->job->allOnConnection($connection);

        return $this;
    }

    /**
     * Set the desired queue for the chain.
     *
     * @param  string|null  $queue
     * @return $this
     */
    public function allOnQueue($queue)
    {
        $this->job->allOnQueue($queue);

        return $this;
    }

    /**
     * Set the desired delay for the job.
     *
     * @param  \DateTime|int|null  $delay
     * @return $this
     */
    public function delay($delay)
    {
        $this->job->delay($delay);

        return $this;
    }

    /**
     * Set the jobs that should run if this job is successful.
     *
     * @param  array  $chain
     * @return $this
     */
    public function chain($chain)
    {
        $this->job->chain($chain);

        return $this;
    }

    /**
     * Handle the object's destruction.
     *
     * @return void
     */
    public function __destruct()
    {
        app(Dispatcher::class)->dispatch($this->job);
    }
}

延遲分發、分發到指定隊列、分發到指定連接

如果你想延遲執行一個隊列中的任務,你可以用任務實例的 delay 方法。
要指定隊列的話,就調用任務實例的 onQueue 方法。
如果你使用了多個隊列連接,你可以將任務推到指定連接。要指定連接的話,你可以在分發任務的時候使用 onConnection 方法

在任務類之後的進一步操作都是通過委託類PendingDispatch類直接作用任務類對象上的。delay、onQueue、onConnection方法在trait Queueable類中。

<?php

namespace Illuminate\Bus;

trait Queueable
{
    ...

    /**
     * Set the desired connection for the job.
     *
     * @param  string|null  $connection
     * @return $this
     */
    public function onConnection($connection)
    {
        $this->connection = $connection;

        return $this;
    }

    /**
     * Set the desired queue for the job.
     *
     * @param  string|null  $queue
     * @return $this
     */
    public function onQueue($queue)
    {
        $this->queue = $queue;

        return $this;
    }

    /**
     * Set the desired connection for the chain.
     *
     * @param  string|null  $connection
     * @return $this
     */
    public function allOnConnection($connection)
    {
        $this->chainConnection = $connection;
        $this->connection = $connection;

        return $this;
    }

    /**
     * Set the desired queue for the chain.
     *
     * @param  string|null  $queue
     * @return $this
     */
    public function allOnQueue($queue)
    {
        $this->chainQueue = $queue;
        $this->queue = $queue;

        return $this;
    }

    /**
     * Set the desired delay for the job.
     *
     * @param  \DateTimeInterface|\DateInterval|int|null  $delay
     * @return $this
     */
    public function delay($delay)
    {
        $this->delay = $delay;

        return $this;
    }

    /**
     * Set the jobs that should run if this job is successful.
     *
     * @param  array  $chain
     * @return $this
     */
    public function chain($chain)
    {
        $this->chained = collect($chain)->map(function ($job) {
            return serialize($job);
        })->all();

        return $this;
    }

    /**
     * Dispatch the next job on the chain.
     *
     * @return void
     */
    public function dispatchNextJobInChain()
    {
        if (! empty($this->chained)) {
            dispatch(tap(unserialize(array_shift($this->chained)), function ($next) {
                $next->chained = $this->chained;

                $next->onConnection($next->connection ?: $this->chainConnection);
                $next->onQueue($next->queue ?: $this->chainQueue);

                $next->chainConnection = $this->chainConnection;
                $next->chainQueue = $this->chainQueue;
            }));
        }
    }
}

任務分發後,業務層的代碼基本就完成了。我們可以發現,整個過程基本是在定義任務類及任務類的屬性。這個過程好比是定義隊列系統中的消息。

任務的存儲是怎麼執行的呢?

參見:laravel綜合話題:隊列——異步消息的分發_php,laravel_szuaudi的博客-CSDN博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章