很高兴,我们来到了Laravel入坑指南的第11篇。这一系列的文章已经接近尾声了,在这一节里面,我们一起讨论列队的用法。
列队,顾名思义,将需要处理的任务一个一个排好队,等待处理程序来处理。这机的列队机制,适用于需要异步处理的任务。
所以在这里,我们首先需要关注三个问题:谁来投递任务?将任务投递到哪里?谁来处理任务?
1、谁来投递任务?
在Laravel里,任务的投递和处理,都是由JOB类负责的。而且哪个JOB类投递的任务,就由那个JOB类进行处理(划重点)。首先,执行以下命令,就可以创建自己的JOB类:
php artisan make:job ProcessMyJob
这时,就会创建出/app/Jobs/ProcessMyJob.php。在这个类中,有一个构造函数,以及一个handle参数。构造函数的参数,就是分发任务时指定的参数,在获取任务实例化JOB时,就将列队中的任务参数传递进来;handle函数则是处理函数。
有了个这JOB类之后,我们可以在我们需要投递任务的地方(比如某个Controller里)进行投递:
//投递到默认列队,默认列队队名由配置文件决定,一般叫作default ProcessMyJob::dispatch("参数"); //投递到名字为default2的列队 ProcessMyJob::dispatch("参数")->onQueue("myqueue");
现在问题来了,默认列队和名为myqueue的列队分别在哪里呢?
2、将任务投递到哪里?
关于这个问题,取决于配置文件/config/queue.php。在这个配置文件中,我们需要关注两个配置项:default和connections。
2.1 connections
'connections' => [ 'sync' => [ 'driver' => 'sync', ], 'database' => [ 'driver' => 'database', 'table' => 'jobs', 'queue' => 'default', 'retry_after' => 90, ], 'beanstalkd' => [ 'driver' => 'beanstalkd', 'host' => 'localhost', 'queue' => 'default', 'retry_after' => 90, 'block_for' => 0, ], 'sqs' => [ 'driver' => 'sqs', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 'queue' => env('SQS_QUEUE', 'your-queue-name'), 'region' => env('AWS_REGION', 'us-east-1'), ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => env('REDIS_QUEUE', 'default'), 'retry_after' => 90, 'block_for' => null, ], ],
这个配置项,让Laravel可以用同步、数据库、beanstalkd、亚马逊sql以及redis的方法来保存列队数据。当然,博主个人建议用redis来进行存储,所以下面所有的示例也会以redis作为例子进行讨论。我们在第6节中,已经配置完redis。如果还不明白的同学,请返回查看第6节。
根据上面所示connections配置项中,我们只需在/.evn文件中添加一个配置项:
REDIS_QUEUE=default
这一项就是指定上面所说的默认列队名字。当任务投递时没有指定名字,就会投递到这个名下的列队中。
2.2 default
这一项则是指定了名为default的列队,用哪种方式保存列队数据。因为上面的例子中,我们要把任务投递到名字为myqueue的列队中,所以我们还需要一个名为myqueue的列队。所以我们的配置如下:
在/.env配置文件中QUEUE_CONNECTION的值为redis:
QUEUE_CONNECTION=redis
到这里,我们知道任务由谁投递的,任务保存在哪里。那么在指定的地方投递完任务后,由谁调用JOB的handle函数,对任务进行处理呢?就是我们接下来关注的重点。
3、谁来处理任务?
在这里,我们需要操作系统长驻进程,一直监视着列队是否有任务要处理。所以我们还是离不开php的命令行。我们可以用以下方式启动监视进程:
#启动对所有列队的监听 php artisan queue:work #启动对default和myqueue的监听,default列队的优先级会高于myqueue的优先级 php artisan queue:work --queue=default,myqueue #在redis连接上启动对email列队的监听 #这种情况就不需要在配置文件中写email列队的配置项 php artisan queue:work redis --queue=email #列队清空后就退出 php artisan queue:work --stop-when-empty #启动两个监听进程,两个列队没有优先级关系 php artisan queue:work --queue=default php artisan queue:work --queue=myqueue
被监听的列队,一旦有任务存在,监听进程就会实例化对应的JOB类,并调用handle函数进行处理。
4、万一监听进程挂了,怎么办?
为了解决这个问题,官方文档介绍了可以用Linux下的supervisor进行进程守护。现在让我们简单探讨
在CentOS下安装supervisor只需要执行:
yum install supervisor
安装完成后在就会创建出/etc/supervisord.conf配置文件和/etc/supervisord.d目录。其中,配置文件中有以下配置项:
[include] files = supervisord.d/*.ini
所有的进程守护配置文件都存放在/etc/supervisord.d路径中。
安装完成后,我们需要创建启动脚本/etc/init.d/supervisor
#!/bin/sh # Source init functions . /etc/rc.d/init.d/functions prog="supervisord" prefix="/usr/local" exec_prefix="${prefix}" prog_bin="${exec_prefix}/bin/supervisord" PIDFILE="/var/run/$prog.pid" start() { echo -n $"Starting $prog: " daemon $prog_bin -c /etc/supervisord.conf --pidfile $PIDFILE [ -f $PIDFILE ] && success $"$prog startup" || failure $"$prog startup" echo } stop() { echo -n $"Shutting down $prog: " [ -f $PIDFILE ] && killproc $prog || success $"$prog shutdown" echo } case "$1" in start) start ;; stop) stop ;; status) status $prog ;; restart) stop start ;; *) echo "Usage: $0 {start|stop|restart|status}" ;; esac
接下来,让我们进行一下测试,看看supervisor是否有效。创建一个配置文件:
/etc/supervisord.d/myqueue.ini
[program:myqueue] process_name=%(program_name)s_%(process_num)02d command=php /www/wwwroot/default/artisan queue:work autostart=true autorestart=true numprocs=4 redirect_stderr=true stdout_logfile=/tmp/test.out.log
创建完成后,执行
/etc/init.d/supervisor restart
会发现我们的列队监视进程已经启动,并且在我们kill掉进程时会自动重启。
到这里,我们列队的话题大功告成。
最后我们关注上一节遗留下的最后一个问题,关于事件Listener处理。如里我们需要以列队进行事件的处理,我们只需要让侦听器实现ShouldQueue接口即可:
php namespace App\Listeners; use Illuminate\Contracts\Queue\ShouldQueue; class MyListener implements ShouldQueue { /** * 任务将被推送到的连接名称. * * @var string|null */ public $connection = 'redis'; /** * 任务将被推送到的列队名称. * * @var string|null */ public $queue = 'queue_name'; /** * 任务被处理之前的延迟时间(秒) * * @var int */ public $delay = 60; }
--------------------------- 我是可爱的分割线 ----------------------------
最后博主借地宣传一下,漳州编程小组招新了,这是一个面向漳州青少年信息学/软件设计的学习小组,有意向的同学点击链接,联系我吧。