源码分析 Laravel 重复执行同一个队列任务的原因


Posted in PHP onDecember 25, 2017

前言

laravel 的队列服务对各种不同的后台队列服务提供了统一的 API。队列允许你延迟执行消耗时间的任务,比如发送一封邮件。这样可以有效的降低请求响应的时间。

发现问题

在 Laravel 中使用 Redis 处理队列任务,框架提供的功能非常强大,但是最近遇到一个问题,就是发现一个任务被多次执行,这是为什么呢?

源码分析 Laravel 重复执行同一个队列任务的原因

先说原因:

因为在 Laravel 中如果一个队列(任务)执行时间大于 60 秒,就会被认为执行失败并重新加入队列中,这样就会导致重复执行同一个任务。

这个任务的逻辑就是给用户推送内容,需要根据队列内容取出用户并遍历,通过请求后端 HTTP 接口发送。比如有 10000 个用户,在用户数量多或接口处理速度没那么快的情况下,执行时间肯定会大于 60 秒,于是这个任务就被重新加入队列。情况更糟糕一点,前面的任务如果都没有在 60 秒执行完,就都会重新加入队列,这样同一个任务就不止重复执行一次了,而是多次。

下面从 Laravel 源代码找一下罪魁祸首。

源代码文件:vendor/laravel/framework/src/Illuminate/Queue/RedisQueue.php

/**
 * The expiration time of a job.
 *
 * @var int|null
 */
protected $expire = 60;

这个 $expire 成员变量是一个固定的值,Laravel 认为一个队列再怎么 60 秒也该执行完了吧。取队列方法:

public function pop($queue = null)
{
 $original = $queue ?: $this->default; 
 $queue = $this->getQueue($queue); 
 $this->migrateExpiredJobs($queue.':delayed', $queue); 
 if (! is_null($this->expire)) {
  $this->migrateExpiredJobs($queue.':reserved', $queue);
 } 
 list($job, $reserved) = $this->getConnection()->eval(
  LuaScripts::pop(), 2, $queue, $queue.':reserved', $this->getTime() + $this->expire
 ); 
 if ($reserved) {
  return new RedisJob($this->container, $this, $job, $reserved, $original);
 }
}

取队列有几步操作,因为队列执行失败,或执行超时等都会放入另外的集合保存起来,以便重试,过程如下:

    1.把因执行失败的队列从 delayed 集合重新 rpush 到当前执行的队列中。

    2.把因执行超时的队列从 reserved 集合重新 rpush 到当前执行的队列中。

    3.然后才是从队列中取任务开始执行,同时把队列放入 reserved 的有序集合。

这里使用了 eval 命令执行这个过程,用到了几个 lua 脚本。

从要执行的队列中取任务:

local job = redis.call('lpop', KEYS[1])
local reserved = false
if(job ~= false) then
 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}

可以看到 Laravel 在取 Redis 要执行的队列的时候,同时会放一份到一个有序集合中,并使用过期时间戳作为分值。

只有当这个任务完成后,再把有序集合中这个任务移除。从这个有序集合移除队列的代码就省略,我们看一下 Laravel 如何处理执行时间大于 60 秒的队列。

也就是这段 lua 脚本执行的操作:

local val = redis.call('zrangebyscore', KEYS[1], '-inf', ARGV[1])
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 true

这里 zrangebyscore 找出分值从无限小到当前时间戳的元素,也就是 60 秒之前加入到集合的任务,然后通过 zremrangebyrank 从集合移除这些元素并 rpush 到队列中。

看到这里应该就恍然大悟了。

如果一个队列 60 秒没执行完,那么进程在取队列的时候从 reserved 集合中把这些任务又重新 rpush 到队列中。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

PHP 相关文章推荐
3种平台下安装php4经验点滴
Oct 09 PHP
PHP新手上路(十)
Oct 09 PHP
简单示例AJAX结合PHP代码实现登录效果代码
Jul 25 PHP
PHP写的资源下载防盗链类分享
May 12 PHP
2014年最新推荐的10款 PHP 开发框架
Aug 01 PHP
php中将一个对象保存到Session中的方法
Mar 13 PHP
PHP+MYSQL实现用户的增删改查
Mar 24 PHP
PHP版微信公众平台红包API
Apr 02 PHP
php+ajax实现无刷新动态加载数据技术
Apr 28 PHP
关于Laravel Route重定向的一个注意点
Jan 16 PHP
PHP中常用的魔术方法
Apr 28 PHP
laravel框架模型中非静态方法也能静态调用的原理分析
Nov 23 PHP
浅析PHP中的闭包和匿名函数
Dec 25 #PHP
thinkphp5 加载静态资源路径与常量的方法
Dec 24 #PHP
PHP读取并输出XML文件数据的简单实现方法
Dec 22 #PHP
ajax+php实现无刷新验证手机号的实例
Dec 22 #PHP
Thinkphp5行为使用方法汇总
Dec 21 #PHP
PHP二维数组实现去除重复项的方法【保留各个键值】
Dec 21 #PHP
Laravel 5.5基于内置的Auth模块实现前后台登陆详解
Dec 21 #PHP
You might like
德生PL550的电路分析
2021/03/02 无线电
基于PHP文件操作的详细诠释
2013/06/21 PHP
php中多维数组按指定value排序的实现代码
2014/08/19 PHP
PHP+FFMPEG实现将视频自动转码成H264标准Mp4文件
2014/09/24 PHP
php提高网站效率的技巧
2015/09/29 PHP
PHP基于双向链表与排序操作实现的会员排名功能示例
2017/12/26 PHP
JQuery EasyUI 加载两次url的原因分析及解决方案
2014/08/18 Javascript
animate 实现滑动切换效果【实例代码】
2016/05/05 Javascript
chrome浏览器如何断点调试异步加载的JS
2016/09/05 Javascript
JavaScript编写一个简易购物车功能
2016/09/17 Javascript
d3.js实现简单的网络拓扑图实例代码
2016/11/06 Javascript
纯JS焦点图特效实例(可一个页面多用)
2016/12/07 Javascript
jquery插件treegrid树状表格的使用方法详解(.Net平台)
2017/01/03 Javascript
js实现简单的网页换肤效果
2017/01/18 Javascript
Vue.js 利用v-for中的index值实现隔行变色
2018/08/01 Javascript
Vue模板语法中数据绑定的实例代码
2019/05/17 Javascript
Python 实现简单的电话本功能
2015/08/09 Python
教大家使用Python SqlAlchemy
2016/02/12 Python
Java及python正则表达式详解
2017/12/27 Python
Python+matplotlib+numpy绘制精美的条形统计图
2018/01/02 Python
深入理解Python 关于supper 的 用法和原理
2018/02/28 Python
python_opencv用线段画封闭矩形的实例
2018/12/05 Python
python matplotlib中的subplot函数使用详解
2020/01/19 Python
全网首秀之Pycharm十大实用技巧(推荐)
2020/04/27 Python
pycharm中使用request和Pytest进行接口测试的方法
2020/07/31 Python
图解CSS3制作圆环形进度条的实例教程
2016/05/26 HTML / CSS
利用CSS3实现单选框动画特效示例代码
2016/09/26 HTML / CSS
Notino法国:购买香水和化妆品
2019/04/15 全球购物
科颜氏英国官网:Kiehl’s英国
2019/11/20 全球购物
销售业务实习自我鉴定
2013/09/23 职场文书
高一家长会邀请函
2014/01/12 职场文书
劳动实践课感言
2014/02/01 职场文书
计算机通信专业推荐信
2014/02/22 职场文书
因公司原因离职的辞职信范文
2015/05/12 职场文书
Python实现GIF动图以及视频卡通化详解
2021/12/06 Python
MySQL创建管理KEY分区
2022/04/13 MySQL