laravel mysql 队列_Laravel 基于redis队列的解析

news/2024/7/8 10:43:01 标签: laravel mysql 队列

为什么使用队列

使用队列的目的一般是:异步执行

出错重试

解释一下:

异步执行: 部分代码执行很耗时, 为了提高响应速度及避免占用过多连接资源, 可以将这部分代码放到队列中异步执行.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\"...localval = 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)thenredis.call('zremrangebyrank', KEYS[1],0, #val -1)fori =1, #val,100doredis.call('rpush', KEYS[2],unpack(val, i,math.min(i+99, #val)))endendreturnval

从 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脚本形式执行命令时可以确保执行脚本的原子性, 而不会有并发问题.

以上内容希望帮助到大家,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家或 者关注咱们下面的知乎专栏PHP架构师圈子​zhuanlan.zhihu.com6cb7f70d51a23d614ee5c5c3f1a5c259.png


http://www.niftyadmin.cn/n/629175.html

相关文章

html中偏移量和坐标,页面元素坐标和偏移

鼠标事件1. 客户区坐标位置(clientX/clientY)通过event事件对象的clientX/clientY属性,获得事件发生时鼠标指针在视口的水平垂直坐标2. 屏幕坐标位置(screenX/screenY)通过event事件对象的clientX/clientY属性,获得事件发生时鼠标指针相对于整个屏幕的坐标信息function showCoo…

git连接华为云DevCloud

华为云DevCloud官网华为云DevCloud在代码托管方面的个github很类似,引入了代码仓库的概念,同时需要本地安装git客户端,且只能与git进行连接,从这个角度上讲,DevCloud的代码管理部分就是github的功能,下面对…

html双人游戏,百行HTML 5代码实现四种双人对弈游戏(2)

处理事件绘制完棋盘和棋子,我们来分析一下用户的动作。用户的动作无非就是两种,一种是点击棋盘 table,另外一种就是点击棋子 DIV。难点在点击 table 这里,我们要获知用户点击 table 的位置。传统思路可能是这样,使用 e…

mysql 5.7 python 2 7_Django2.* + Mysql5.7开发环境整合

环境:MAC_OS 10.12python 3.6mysql 5.7.25django 2.2.3前提:python django mysql都已经安装成功可单独运行 DATABASES{default:{ENGINE:django.db.backends.mysql,"USER":"user name","PASSWORD":"your password","NAME":&q…

【PHP 每日函数】第 01 周期

2019 - 02 - 20 array_change_key_case() array_change_key_case(array $arr, [, int $case CASE_LOWER]) : array 说明:将所有键名改为全大写或者小写 $arr [name > 张三,Age > 18,gender > 男, ];// case upper print_r(array_change_key_case($arr, C…

mysql 错误日志和监控日志分离_Oracle监控系统错误日志过程

--创建临时表用来存放系统错误信息create table superflow(cust_id number(10),cust_name varchar2(100),d varchar(50),error_in--创建临时表用来存放系统错误信息create table superflow(cust_id number(10),cust_name varchar2(100),d varchar(50),error_info varchar2(500)…

html基础知识选择题,HTML笔试题目(附答案)HTML基础知识笔试题目(附答案).pdf

1、{HTML 题目 } 在 HTML页面中制作一个图像,想要在鼠标指向这个图像时浮出一条提示信息,应该使用的参数是 :( C ) ( 选择 1 项 )A) POP B) SRC C) ALT D) MSG2、{HTML题目 } 在页面中看不见的表单元素是那种? ( C ) (选择 1 项)A) B )C) D )…

华为首款鸿蒙平板发布,华为MatePad Pro发布!首款鸿蒙平板强势来袭

可以说,华为MatePad Pro的出现,让我一个平时并不常用平板电脑的人,再次重拾了对于平板电脑的兴趣。根据媒体发布的信息,新一代华为MatePad Pro或将是华为平板电脑业务升级的重要转折点,就在6月2日晚,华为召…