一般从概念上说,队列只是一个存放消息的目的地,队列的使用者是消息的生产者和消息的发送者。但在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;
}));
}
}
}
任务分发后,业务层的代码基本就完成了。我们可以发现,整个过程基本是在定义任务类及任务类的属性。这个过程好比是定义队列系统中的消息。
任务的存储是怎么执行的呢?