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博客

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