[原创]Laravel 基于redis队列的解析
Last-Modified: 2019年5月10日11:44:18
参考链接
本文环境
- Laravel 5.5
- 队列 Redis
为什么使用队列
使用队列的目的一般是:
- 异步执行
- 出错重试
解释一下:
异步执行: 部分代码执行很耗时, 为了提高响应速度及避免占用过多连接资源, 可以将这部分代码放到队列中异步执行.
Eg. 网站新用户注册后, 需要发送欢迎的邮件, 涉及到网络IO无法控制耗时的这一类就很适合放到队列中来执行.
出错重试: 为了保证一些任务的正常执行, 可以将任务放到队列中执行, 若执行出错则可以延迟一段时间后重试, 直到任务处理成功或出错超过N次后取消执行.
Eg. 用户需要绑定手机号, 此时发送短信的接口是依赖第三方, 一个是不确定耗时, 一个是不确定调用的成功, 为了保证调用成功, 必然需要在出错后重试
Laravel 中的队列
以下分析默认使用的队列及其配置如下
- 默认队列引擎:
redis
通过在
redis-cli中使用monitor命令查看具体执行的命令语句
- 默认队列名:
default
分发任务
此处以分发 异步通知(class XxxNotification implement ShouldQueue)为例.
在Laravel中发起异步通知时, Laravel 会往redis中的任务队列添加一条新任务
redis 执行语句
redis> RPUSH queues:default
{
"displayName": "App\\Listeners\\RebateEventListener",
"job": "Illuminate\\Queue\\CallQueuedHandler@call",
"maxTries": null,
"timeout": null,
"timeoutAt": null,
"data": {
"commandName": "Illuminate\\Events\\CallQueuedListener",
"command": "O:36:\"Illuminate\\Events\\CallQueuedListener\":7:{s:5:\"class\";s:33:\"App\\Listeners\\RebateEventListener\";s:6:\"method\";s:15:\"onRebateCreated\";s:4:\"data\";a:1:{i:0;O:29:\"App\\Events\\RebateCreatedEvent\":4:{s:11:\"\u0000*\u0000tbkOrder\";O:45:\"Illuminate\\Contracts\\Database\\ModelIdentifier\":3:{s:5:\"class\";s:19:\"App\\Models\\TbkOrder\";s:2:\"id\";i:416;s:10:\"connection\";s:5:\"mysql\";}s:15:\"\u0000*\u0000notifyAdmins\";b:1;s:13:\"\u0000*\u0000manualBind\";b:0;s:6:\"socket\";N;}}s:5:\"tries\";N;s:9:\"timeoutAt\";N;s:7:\"timeout\";N;s:6:\"\u0000*\u0000job\";N;}"
},
"id": "iTqpbeDqqFb3VoED2WP3pgmDbLAUQcMB",
"attempts": 0
}
上面的redis语句是将任务信息(json格式) rpush 到 redis 队列 queues:default 的尾部.
任务队列 Worker
Laravel 处理任务队列的进程开启方式: php artisan queue:work, 为了更好的观察, 这里使用 --once 选项来指定队列中的单一任务进行处理, 具体的更多参数请自行参考文档
php artisan queue:work --once --delay=1 --tries=3
上述执行语句参数含义:
--once仅执行一次任务, 默认是常驻进程一直执行--tries=3任务出错最多重试3次, 默认是无限制重试--delay=1任务出错后, 每次延迟1秒后再次执行, 默认是延迟0秒
当 Worker 启动时, 它依次执行如下步骤:
此处仍以默认队列
default为例讲解, 且只讲解redis的相关操作
从
queues:default:delayed有序集合中获取可以处理的 "延迟任务", 并rpush到queue:default队列的尾部具体的执行语句:
redis> eval "Lua脚本" 2 queues:default:delayed queues:default 当前时间戳
Lua 脚本内容如下:
-- Get all of the jobs with an expired \"score\"...
local val = redis.call('zrangebyscore', KEYS[1], '-inf', ARGV[1]) -- If we have values in the array, we will remove them from the first queue
-- and add them onto thedestination queue in chunks of 100, which moves
-- all of the appropriate jobs onto the destination queue very safely.
if(next(val) ~= nil) then
redis.call('zremrangebyrank', KEYS[1], 0, #val - 1) for i = 1, #val, 100 do
redis.call('rpush', KEYS[2], unpack(val, i, math.min(i+99, #val)))
end
end return val
从
queue:default:reserved有序集合中获取已过期的 "reserved 任务", 并rpush到queue:default队列的尾部具体的执行语句:
redis> eval "Lua脚本" 2 queues:default:reserved queues:default 当前时间戳
使用的Lua脚本同步骤 1
从
queue:default队列中获取(lpop)一个任务, 增加其attempts次数, 并将该任务保存到queu:default:reserved有序集合中, 该任务的score值为 当前时间 + 90(任务执行超时时间)具体的执行语句:
redis> eval “Lua脚本” 2 queues:default queues:default:reserved 任务超时时间戳
Lua脚本
-- Pop the first job off of the queue...
local job = redis.call('lpop', KEYS[1])
local reserved = false if(job ~= false) then
-- Increment the attempt count and place job on the reserved queue...
reserved = cjson.decode(job)
reserved['attempts'] = reserved['attempts'] + 1
reserved = cjson.encode(reserved)
redis.call('zadd', KEYS[2], ARGV[1], reserved)
end return {job, reserved}
这里的 90 是根据配置而定:
config('queue.connections.redis.retry_after')若预计任务耗时过久, 则应增加该数值, 防止任务还在执行时就被重置
在成功执行上面获取的任务后, 就将该任务从
queues:default:reserved队列中移除掉具体执行语句:
ZREM queues:default:reserved "具体任务"如果执行任务失败, 此时分为2种情况:
任务失败次数未达到指定的重试次数阀值
将该任务从
queues:default:reserved中移除, 并将该任务添加到queue:default:delayed有序集合中,score为该任务下一次执行的时间戳执行语句:
redis> EVAL "Lua脚本" 2 queues:default:delayed queues:default:reserved "失败的任务" 任务延迟执行的时间戳
Lua脚本
-- Remove the job from the current queue...
redis.call('zrem', KEYS[2], ARGV[1]) -- Add the job onto the \"delayed\" queue...
redis.call('zadd', KEYS[1], ARGV[2], ARGV[1]) return true
如果任务失败次数超过指定的重试阀值
将该任务从
queue:default:reserved中移除执行语句:
redis> ZREM queue:default:reserved
注意, 上述使用 Lua 脚本的目的在于操作的原子性, Redis 是单进程单线程模式, 以Lua脚本形式执行命令时可以确保执行脚本的原子性, 而不会有并发问题.
[原创]Laravel 基于redis队列的解析的更多相关文章
- 消息中心 - Laravel的Redis队列(一)
前言 Laravel的队列可以用在轻量级的队列需求中.比如我们系统中的短信.邮件等功能,这些功能有一些普遍的特征,异步.重试.并发控制等.Laravel现在主要支持的队列服务有Null.Sync.Da ...
- laravel中redis队列的使用
一.配置文件 首先我们需要在配置文件中配置默认队列驱动为Redis,队列配置文件是config/queue.php: return [ 'default' => env('QUEUE_DRIVE ...
- laravel使用redis队列实践(只需6步,超详细,超简单)
1.配置使用redis队列 在.env文件找到QUEUE_DRIVER=sync改成QUEUE_DRIVER=redis redis配置一般不用改如果有密码改.env文件的REDIS_PASSWORD ...
- laravel基于redis实现的一个简单的秒杀系统
说明:网上很多redis秒杀系统的文章,看的都是一头雾水,然后自己来实现一个,也方便以后自己学习 实现的方式是用的redis的list队列,框架为laravel 核心部分为list的pop操作,此操作 ...
- [原创]Laravel 的缓存源码解析
目录 前言 使用 源码 Cache Facade CacheManager Repository Store 前言 Laravel 支持多种缓存系统, 并提供了统一的api接口. (Laravel 5 ...
- PHP【Laravel】delayer基于redis的实现订单超时改变状态
实现这个功能前你需要知道以下,不然可能会比较吃力:1.服务器的计划任务,shell脚本,或者你有宝塔自带的计划任务会方便很多.2.有所了解Redis.3.会写PHP业务逻辑. 好了进入在正题,这里使用 ...
- 项目分布式部署那些事(1):ONS消息队列、基于Redis的Session共享,开源共享
因业务发展需要现在的系统不足以支撑现在的用户量,于是我们在一周之前着手项目的性能优化与分布式部署的相关动作. 概况 现在的系统是基于RabbitHub(一套开源的开发时框架)和Rabbit.WeiXi ...
- [转载] 基于Redis实现分布式消息队列
转载自http://www.linuxidc.com/Linux/2015-05/117661.htm 1.为什么需要消息队列?当系统中出现“生产“和“消费“的速度或稳定性等因素不一致的时候,就需要消 ...
- 基于Redis的消息队列php-resque
转载:http://netstu.5iunix.net/archives/201305-835/ 最近的做一个短信群发的项目,需要用到消息队列.因此开始了我对消息队列选型的漫长路. 为什么选型会纠结呢 ...
随机推荐
- php5.6编译安装apache
1.下载源码包wget 网址/php-5.6.30.tar.gz2.解压源码包tar -zxvf php-5.6.30.tar.gz3.创建一个安装目录mkdir /usr/local/php4.进入 ...
- Mysql-5.7 x64安装
首先在官网下载Mysql:https://dev.mysql.com/downloads/mysql/ 选择ZIP Archive下载. 下载安装之后配置环境变量: 编辑现有环境变量Path: PS: ...
- wpf slider刻度
TickFrequency:刻度之间的间隔 IsSnapToTickEnabled:是否对齐到刻度 TickPlacement:刻度位置
- oracle 普通数据文件备份与恢复
普通数据文件指:非system表空间.undo_tablespace表空间.临时表空间和只读表空间的数据文件.它们损坏导致用户数据不能访问,不会导致db自身异常.实例崩溃.数据库不恢复就无法启动的情况 ...
- HTML5——新表单元素 表单属性 语义元素
表单元素 新的表单元素 datalist——输入域选项列表 keygen——提供一种验证用户的可靠方法 output——不同类型的输出,比如计算或脚本输出 表单属性 <form> / &l ...
- 2019JAVA第一次編程总结
2019第二周实验报告 Java实验报告 班级 计算机科学与技术二班 学号 20188442 姓名 吴怡君 完成时间 2019/9/7 评分等级 实验一 Java开发环境与简单Java程序 一. 实验 ...
- C函数调用过程原理及函数栈帧分析(转)
在x86的计算机系统中,内存空间中的栈主要用于保存函数的参数,返回值,返回地址,本地变量等.一切的函数调用都要将不同的数据.地址压入或者弹出栈.因此,为了更好地理解函数的调用,我们需要先来看看栈是怎么 ...
- HTML-图片和多媒体
1.图片和多媒体 (1) 图片:img元素 src 属性:图片路径: alt 属性:图片无法显示时使用的替代文字: title:鼠标悬停时显示的文字 : <img src="图片 ...
- CSS选择符有哪些?哪些属性可以继承
下面是一些常用的选择器: 1.id选择器( # myid) 2.类选择器(.myclassname) 3.标签选择器(div, h1, p) 4.相邻选择器(h1 + p) 5.子选择器(ul > ...
- 编程的智慧(王垠)(http://www.cocoachina.com/programmer/20151125/14410.html)
编程是一件创造性的工作,是一门艺术.精通任何一门艺术,都需要很多的练习和领悟,所以这里提出的“智慧”,并不是号称三天瘦二十斤的减肥药,它并不能代替你自己的勤奋.然而我希望它能给迷惑中的人们指出一些正确 ...