一般從概念上說,隊列只是一個存放消息的目的地,隊列的使用者是消息的生產者和消息的發送者。但在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;
}));
}
}
}
任務分發後,業務層的代碼基本就完成了。我們可以發現,整個過程基本是在定義任務類及任務類的屬性。這個過程好比是定義隊列系統中的消息。
任務的存儲是怎麼執行的呢?