Laravel 文件系统及队列处理

队列

Laravel 队列为不同的后台队列服务提供统一的 API,这些队列介质可以是 Beanstalk、Redis,也关系型数据库。使用队列的主要目的是将耗时的任务延时处理,比如发送邮件、发送短信,从而大幅度缩短 Web 请求和响应的时间。

队列特点:

  • 异步(延时);
  • 削峰(并发);
  • 重试(失败);

队列配置文件存放在 config/queue.php 文件中,其中包含了同步(本地使用)驱动和 null 队列驱动,null 队列主要用于那些放弃队列的任务。

驱动介质

Database

// 创建队列数据库迁移表
php artisan queue:table
// 创建队列失败的迁移表
php artisan queue:failed-table
// 运行迁移命令
php artisan migrate

Redis

如果你的 Redis 队列驱动使用了 Redis 集群,你的队列名必须包含一个 key hash tag 。这是为了确保所有的 Redis 键对于一个队列都被放在同一哈希中。

'redis' => [
    'driver' => 'redis',
    'connection' => 'default',
    'queue' => '{default}',
    'retry_after' => 90,
]

相关概念

在开始使用 Laravel 队列前,弄明白 「连接」 和 「队列」 的区别是很重要的。

  • 连接:config/queue.php 文件中 connections 配置选项;
  • 队列:可以被认为是不同的栈或者大量的队列任务;

创建队列

在你的应用程序中,队列的任务类都默认放在 app/Jobs 目录下。如果这个目录不存在,那当你运行 make:job Artisan 命令时目录就会被自动创建。

$ php artisan make:job ProcessPodcast

<?php

namespace App\Jobs;

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

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

    /**
     * 应该处理任务的队列连接
     *
     * @var string
     */
    public $connection = 'sqs';

    /**
     * 任务可以尝试的最大次数
     *
     * @var int
     */
    public $tries = 5;

    /**
     * 任务可以执行的最大时间(注意retry_after)
     *
     * @var int
     */
    public $timeout = 60;

    /**
     * 队列运行时需要的参数
     *
     * @var array
     */
    public $params = [];

    /**
     * 创建队列实例
     *
     * @return void
     */
    public function __construct($params)
    {
        $this->params = $params;
    }

    /**
     * 执行队列任务
     *
     * @return void
     */
    public function handle()
    {
        // Redis::throttle('key')->block(0)->allow(1)->every(5)->then(
        //     function () {
        //         info('Lock obtained...');
        //         // 处理队列
        //     },
        //     function () {
        //         // 无法获取锁
        //         return $this->release(5);
        //     }
        // );
        $result = app('App\Services\CollectService')->handlerCollectData($this->params);
        if ($result < 0) {
            if ($this->attempts() < $this->tries) {
                $this->release(5);
            }
        }
        return $result;
    }

    /**
     * 任务失败的处理过程
     *
     * @param Exception $exception
     * @return void
     */
    public function failed(\Exception $exception)
    {
        info('运行出现错误:' . $exception->getMessage() . ',参数是:', $this->params);
    }

    // /**
    //  * 运行队列中间件
    //  *
    //  * @return array
    //  */
    // public function middleware()
    // {
    //     return [new RateLimited];
    // }
    //
    // /**
    //  * 定义任务超时时间(在给定的时间范围内,任务可以无限次尝试)
    //  *
    //  * @return \DateTime
    //  */
    // public function retryUntil()
    // {
    //     return now()->addSeconds(5);
    // }
}

队列中间件

/**
 * 执行队列任务
 *
 * @return void
 */
public function handle()
{
    Redis::throttle('key')->block(0)->allow(1)->every(5)->then(
        function () {
            info('Lock obtained...');
            // 处理队列
        },
        function () {
            // 无法获取锁
            return $this->release(5);
        }
    );
}

虽然这段代码有效,但 handle 方法的结构变得有噪声,因为它与 Redis 速率限制逻辑混杂在一起。此外,对于我们要进行速率限制的任何其他任务,必须复制此速率限制逻辑。

<?php
namespace App\Jobs\Middleware;

use Illuminate\Support\Facades\Redis;

class RateLimited
{
    /**
     * 处理队列中的任务.
     *
     * @param  mixed  $job
     * @param  callable  $next
     * @return mixed
     */
    public function handle($job, $next)
    {
        Redis::throttle('key')->block(0)->allow(1)->every(5)->then(function () use ($job, $next) {
            // 锁定
            $next($job);
        }, function () use ($job) {
            // 无法获取锁
            $job->release(5);
        });
    }
}

分发队列

// 分发到默认队列
ProcessPodcast::dispatch($podcast);
// 延迟分发队列
ProcessPodcast::dispatch($podcast)->delay(now()->addMinutes(10));
// 同步执行队列
ProcessPodcast::dispatchNow($podcast);
// 发送到指定队列
ProcessPodcast::dispatch($podcast)->onQueue('emails');
// 分发到指定连接
ProcessPodcast::dispatch($podcast)->onConnection('sqs');
// 指定连接和队列
ProcessPodcast::dispatch($podcast)->onConnection('sqs')->onQueue('processing');

// 队列任务链:任务链允许你具体定义一个按序列执行队列任务的列表,一旦序列中的任务失败了,剩余的工作将不会执行。
ProcessPodcast::withChain([
    new OptimizePodcast,
    new ReleasePodcast
])->dispatch();

// 使用函数分发队列
dispatch((new Job)->onQueue('high'));

执行队列:

$ php artisan queue:work --queue=high,low,default
$ php artisan queue:restart
$ php artisan queue:work --timeout=60

supervisor

sudo apt-get install supervisor

Supervisor 的配置文件通常位于 /etc/supervisor/conf.d 目录下。在该目录中,你可以创建任意数量的配置文件,用来控制 supervisor 将如何监控你的进程。

; supervisor config file

[unix_http_server]
file=/var/run/supervisor.sock   ; (the path to the socket file)
chmod=0700                      ; sockef file mode (default 0700)

[supervisord]
logfile=/data/wwwlogs/supervisord.log  ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid     ; (supervisord pidfile;default supervisord.pid)
childlogdir=/data/wwwlogs           ; ('AUTO' child log dir, default $TEMP)

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL  for a unix socket

[include]
files = /etc/supervisor/conf.d/*.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/forge/app.com/artisan queue:work sqs --sleep=3 --tries=3
autostart=true
autorestart=true
user=forge
numprocs=8
redirect_stderr=true
stdout_logfile=/home/forge/app.com/worker.log
$ sudo supervisorctl reread
$ sudo supervisorctl update
$ sudo supervisorctl start laravel-worker:*

相关命令

// 手动重试失败队列
$ php artisan queue:failed
// 重试任务指定队列
$ php artisan queue:retry 5
// 重试所有失败的任务
$ php artisan queue:retry all
// 删除某个失败的任务
$ php artisan queue:forget 5
// 清空所有失败的任务
$ php artisan queue:flush

注意事项

  • Laravel 队列在执行异常时会自动被释放回队列并再次尝试运行,直到成功或者达到所设定的最大失败重试次数。
  • 如果通过 --queue 命令行或 onQueue 方法指定了队列优先级,没有将指定队列和默认队列添加到优先级中,那么你的队列任务将永远也不会执行。
  • 由于队列处理器都是常驻进程,如果代码改变而队列处理器没有重启,是不能应用新代码的,最简单的方式就是重新部署过程中要重启队列处理器。
  • --delay 延迟执行队列任务。
  • --timeout 设置每个任务被允许运行秒数,0 代表不限制时间,--timeout 的值应该比你在 retry_after 中配置的值至少短几秒。
  • --sleep 指定队列要等待多少秒后才能取新的任务来运行。注意:队列在没有任务的状态下才会休眠,如果已经有多个任务在这个队列上等待执行,那么它会持续运行而不休眠。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,270评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,489评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,630评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,906评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,928评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,718评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,442评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,345评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,802评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,984评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,117评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,810评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,462评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,011评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,139评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,377评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,060评论 2 355

推荐阅读更多精彩内容

  • 队列 简介 laravel 的队列服务对各种不同的后台队列服务提供了统一的 API。队列允许你延迟执行消耗时间的任...
    Dearmadman阅读 20,745评论 7 26
  • 当我们在使用laravel框架开发Web项目时,有时会需要异步操作。 Laravel队列配置文件存放在config...
    赵客缦胡缨v吴钩霜雪明阅读 9,094评论 0 13
  • 1 环境 Laravel是一种类似ThinkPHP的php框架,封装的诸多功能可以很方便的使用。队列Queue便是...
    bbdlg阅读 12,589评论 7 10
  • laravel队列文档 1.概念理解 连接(connections ):config/queue.php中有一个c...
    YyYy_G阅读 554评论 0 0
  • 1.队列的应用场景: PHP在异步编程上的短板是众所周知的,这也是当年PHP能够迅速火起来的一个重要特性,当然,这...
    范仁镗阅读 5,856评论 0 3