Laravel5.5 使用队列 Queue
使用队列#
上一章节中我们开发了自动生成 Slug
功能,但是因为我们的需要实时请求百度翻译接口,这将会是一个系统性能隐患。
一般情况下,网络请求会存在各种不确定性,如果请求 API
出现超时情况,或者发生不可预知的错误,我们的用户将无法发帖。
生成 Slug 只是一个 优化 功能,并非是发帖的 必要 功能,我们希望无论生成
Slug 的结果如何,用户都能顺利的发帖,并且完全察觉不到延迟。
利用队列系统可以做到这点。队列允许你异步执行消耗时间的任务,比如请求一个 API
并等待返回的结果。这样可以有效的降低请求响应的时间。
1. 配置队列#
队列的配置信息储存于 config/queue.php
文件中,在这个文件中你会发现框架所支持的队列驱动的配置连接示例。这些驱动包括:数据库,Beanstalkd,Amazon
SQS,Redis,和一个同步(本地使用)的驱动。还有一个名为 null
的驱动表明不使用队列任务。
本项目中,我们将使用 Redis 来作为我们的队列驱动器,先使用 Composer
安装依赖:
$ composer require "predis/predis:~1.0"
接下来我们还需要修改环境变量 QUEUE_DRIVER
的值为 redis
:
.env
-
.
-
.
-
.
-
QUEUE_DRIVER=redis
-
-
.
-
.
-
.
失败任务#
有时候队列中的任务会失败。Laravel
内置了一个方便的方式来指定任务重试的最大次数。当任务超出这个重试次数后,它就会被插入到 failed_jobs
数据表里面。我们可以使用 queue:failed-table
命令来创建 failed_jobs
表的迁移文件:
$ php artisan queue:failed-table
会新建 database/migrations/{timestamp}_create_failed_jobs_table.php
文件:
接着使用 migrate
Artisan 命令生成 failed_jobs
表:
$ php artisan migrate
2. 生成任务类#
使用以下 Artisan 命令来生成一个新的队列任务:
$ php artisan make:job TranslateSlug
该命令会在 app/Jobs
目录下生成一个新的类:
app/Jobs/TranslateSlug.php
-
<?php
-
-
namespace App\Jobs;
-
-
use Illuminate\Bus\Queueable;
-
use Illuminate\Queue\SerializesModels;
-
use Illuminate\Queue\InteractsWithQueue;
-
use Illuminate\Contracts\Queue\ShouldQueue;
-
use Illuminate\Foundation\Bus\Dispatchable;
-
-
use App\Models\Topic;
-
use App\Handlers\SlugTranslateHandler;
-
-
class TranslateSlug implements ShouldQueue
-
{
-
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
-
-
protected $topic;
-
-
public function __construct(Topic $topic)
-
{
-
// 队列任务构造器中接收了 Eloquent 模型,将会只序列化模型的 ID
-
$this->topic = $topic;
-
}
-
-
public function handle()
-
{
-
// 请求百度 API 接口进行翻译
-
$slug = app(SlugTranslateHandler::class)->translate($this->topic->title);
-
-
// 为了避免模型监控器死循环调用,我们使用 DB 类直接对数据库进行操作
-
\DB::table('topics')->where('id', $this->topic->id)->update(['slug' => $slug]);
-
}
-
}
该类实现了 Illuminate\Contracts\Queue\ShouldQueue
接口,该接口表明 Laravel
应该将该任务添加到后台的任务队列中,而不是同步执行。
引入了 SerializesModels
trait,Eloquent
模型会被优雅的序列化和反序列化。队列任务构造器中接收了 Eloquent 模型,将会只序列化模型的 ID。这样子在任务执行时,队列系统会从数据库中自动的根据
ID 检索出模型实例。这样可以避免序列化完整的模型可能在队列中出现的问题。
handle
方法会在队列任务执行时被调用。值得注意的是,我们可以在任务的 handle
方法中可以使用类型提示来进行依赖的注入。Laravel
的服务容器会自动的将这些依赖注入进去,与控制器方法类似。
还有一点需要注意,我们将会在模型监控器中分发任务,任务中要避免使用 Eloquent
模型接口调用,如:create()
, update()
, save()
等操作。否则会陷入调用死循环 ——
模型监控器分发任务,任务触发模型监控器,模型监控器再次分发任务,任务再次触发模型监控器.... 死循环。在这种情况下,使用 DB
类直接对数据库进行操作即可。
3. 任务分发#
接下来我们要修改 Topic 模型监控器,将 Slug
翻译的调用修改为队列执行的方式:
app/Observers/TopicObserver.php
-
<?php
-
-
namespace App\Observers;
-
-
use App\Models\Topic;
-
use App\Jobs\TranslateSlug;
-
-
// creating, created, updating, updated, saving,
-
// saved, deleting, deleted, restoring, restored
-
-
class TopicObserver
-
{
-
public function saving(Topic $topic)
-
{
-
// XSS 过滤
-
$topic->body = clean($topic->body, 'user_topic_body');
-
-
// 生成话题摘录
-
$topic->excerpt = make_excerpt($topic->body);
-
-
// 如 slug 字段无内容,即使用翻译器对 title 进行翻译
-
if ( ! $topic->slug) {
-
-
// 推送任务到队列
-
dispatch(new TranslateSlug($topic));
-
}
-
}
-
}
4. 开始测试#
开始之前,我们需要在命令行启动队列系统,队列在启动完成后会进入监听状态:
$ php artisan queue:listen
浏览器打开话题发布页面,填写测试内容:
点击『保存』按钮提交表单后,可以在命令行中看到监听的状态:
可以看到我们的任务 Failed
执行失败了。打开数据库查看 failed_jobs
里的数据:
虽然我们能够从 payload
和 exception
字段中看到报错的信息,但因为是序列化以后的信息,所以并不直观:
5. 队列监控 Horizon#
Horizon 是 Laravel 生态圈里的一员,为 Laravel Redis
队列提供了一个漂亮的仪表板,允许我们很方便地查看和管理 Redis 队列任务执行的情况。
使用 Composer 安装:
$ composer require "laravel/horizon:~1.0"
安装完成后,使用 vendor:publish
Artisan
命令发布相关文件:
$ php artisan vendor:publish --provider="Laravel\Horizon\HorizonServiceProvider"
分别是配置文件 config/horizon.php
和存放在 public/vendor/horizon
文件夹中的
CSS 、JS 等页面资源文件。
至此安装完毕,浏览器打开 http://larabbs.test/horizon 访问控制台:
Horizon 是一个监控程序,需要常驻运行,我们可以通过以下命令启动:
$ php artisan horizon
安装了 Horizon 以后,我们将使用 horizon
命令来启动队列系统和任务监控,无需使用 queue:listen
。
接下来我们再次尝试下发帖,发帖之前,请确保 horizon
命令处于监控状态:
这一次多亏了
Horizon,我们可以清晰的看到更加详尽的错误信息,错误异常是 ModelNotFoundException
,最重要的:
我们发现 Data 区块里,id
的值居然为 null
。我们知道的,队列系统对于构造器里传入的 Elequent 模型,将会只序列化 ID 字段,因为我们是在
Topic 模型监控器的 creating()
方法中分发队列任务的,此时传参的 $topic
变量还未在数据库里创建,所以 $topic->id
为
null。
6. 代码调整#
既然我们已经定位到了问题,解决的方法也很简单,只需要确保分发任务时 $topic->id
有值即可。我们需要修改任务分发的时机:
app/Observers/TopicObserver.php
-
<?php
-
-
namespace App\Observers;
-
-
use App\Models\Topic;
-
use App\Jobs\TranslateSlug;
-
-
// creating, created, updating, updated, saving,
-
// saved, deleting, deleted, restoring, restored
-
-
class TopicObserver
-
{
-
public function saving(Topic $topic)
-
{
-
// XSS 过滤
-
$topic->body = clean($topic->body, 'user_topic_body');
-
-
// 生成话题摘录
-
$topic->excerpt = make_excerpt($topic->body);
-
}
-
-
public function saved(Topic $topic)
-
{
-
// 如 slug 字段无内容,即使用翻译器对 title 进行翻译
-
if ( ! $topic->slug) {
-
-
// 推送任务到队列
-
dispatch(new TranslateSlug($topic));
-
}
-
}
-
}
模型监控器的 saved()
方法对应 Eloquent 的 saved
事件,此事件发生在创建和编辑时、数据入库以后。在 saved()
方法中调用,确保了我们在分发任务时,$topic->id
永远有值。
需要注意的是,
artisan horizon
队列工作的守护进程是一个常驻进程,它不会在你的代码改变时进行重启,当我们修改代码以后,需要在命令行中对其进行重启操作。
重启 horizon
命令后再次尝试:
7. 线上部署须知#
在开发环境中,我们为了测试方便,直接在命令行里调用 artisan horizon
进行队列监控。然而在生产环境中,我们需要配置一个进程管理工具来监控 artisan horizon
命令的执行,以便在其意外退出时自动重启。当服务器部署新代码时,需要终止当前
Horizon 主进程,然后通过进程管理工具来重启,从而使用最新的代码。
简而言之,生产环境下使用队列需要注意以下两个问题:
- 使用 Supervisor 进程工具进行管理,配置和使用请参照 文档 进行配置;
- 每一次部署代码时,需
artisan horizon:terminate
然后再artisan horizon
重新加载代码。
8. 使用 Sync 队列驱动#
既然功能已经开发测试完毕,为了后续开发的方便,我们将开发环境的队列驱动改回 sync
同步模式,也就是说不使用任何队列,实时执行:
.env
QUEUE_DRIVER=sync
Laravel5.5 使用队列 Queue的更多相关文章
- Python进阶【第二篇】多线程、消息队列queue
1.Python多线程.多进程 目的提高并发 1.一个应用程序,可以有多进程和多线程 2.默认:单进程,单线程 3.单进程,多线程 IO操作,不占用CPU python的多线程:IO操作,多线程提供并 ...
- Java中的队列Queue,优先级队列PriorityQueue
队列Queue 在java5中新增加了java.util.Queue接口,用以支持队列的常见操作.该接口扩展了java.util.Collection接口. Queue使用时要尽量避免Collecti ...
- jquery 的队列queue
使用示列代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www ...
- Windows Azure Service Bus (2) 队列(Queue)入门
<Windows Azure Platform 系列文章目录> Service Bus 队列(Queue) Service Bus的Queue非常适合分布式应用.当使用Service Bu ...
- Windows Azure Service Bus (3) 队列(Queue) 使用VS2013开发Service Bus Queue
<Windows Azure Platform 系列文章目录> 在之前的Azure Service Bus中,我们已经介绍了Service Bus 队列(Queue)的基本概念. 在本章中 ...
- (C#)使用队列(Queue)解决简单的并发问题
(C#)使用队列(Queue)解决简单的并发问题 2015-07-16 13:04 13265人阅读 评论(8) 收藏 举报 分类: Asp.Net(8) 版权声明:本文为博主原创文章,未经博主允 ...
- STL中的单向队列queue
转载自:http://blog.csdn.net/morewindows/article/details/6950917 stl中的queue指单向队列,使用时,包含头文件<queue>. ...
- java09 队列Queue与Deque
队列Queue与Deque. Enumeration Hashtable与Hashtable子类Properties(资源配置文件) 引用类型(强.软.弱.虚)与WeakHashMap Identit ...
- 队列Queue和栈
1.队列Queue是常用的数据结构,可以将队列看成特殊的线性表,队列限制了对线性表的访问方式,只能从线性表的一段添加(offer)元素, 从另一段取出(poll)元素,队列遵循先进先出的原则. 2.J ...
随机推荐
- re模块 ,random模块
# 在python中使用正则表达式 # 转义符 : 在正则中的转义符 \ 在python中的转义符# 正则表达式中的转义 :# '\(' 表示匹配小括号# [()+*?/$.] 在字符组中一些特殊的字 ...
- 自定义BeanUtils
package com.icil.booking.service.util; import java.math.BigDecimal; import java.text.ParseException; ...
- Redis 安装 和 启动
Redis下载官网 http://download.redis.io/releases/ 本人下载了stable版 1:安装步骤 ># wget http://download.redis.i ...
- jq js 对象互转
. DOM 对象转成 jQuery 对象 对于已经是一个 DOM 对象,只需要用 $() 把DOM对象包装起来,就可以获得一个 jQuery 对象了,$(DOM 对象) 注: var是定义变量 如: ...
- Flume环境搭建_五种案例(转)
Flume环境搭建_五种案例 http://flume.apache.org/FlumeUserGuide.html A simple example Here, we give an example ...
- HIBERNATE知识复习记录1-连接及常用方法
要去面试了,复习一下HIBERNATE的相关知识吧,原来边看视频边写的代码如下,已经分不清先后次序了,大致看一看吧. 先看下总的配置文件hibernate.cfg.xml: <?xml vers ...
- 移动cup
intel处理器M和U H结尾的有什么具体区别 笔记本CPU 酷睿i 系列U M H详解: U 低压版(低电压-性能消减)最低主频:1.7-1.9GHZ M 准电压(笔记本上的电压标准)最低主频:2. ...
- 提取linux中eth0的IP地址
法1:cut [root@oldboy oldboy]# ifconfig eth0|grep 'inet addr'|cut -d ":" -f2|cut -d " & ...
- Python中文件编码的检测
前言: 文件打开的原则是“ 以什么编码格式保存的,就以什么编码格式打开 ”,我们常见的文件一般是以“ utf-8 ”或“ GBK ”编码进行保存的,由于编辑器一般设置了默认的保存和打开方式,所以我们在 ...
- Containerpilot 配置文件模板
{ "consul": "{{ .CONSUL }}:8500", "logging": { "level": &quo ...