Laravel重复执行同一队列任务原因的源代码分析
前言
Laravel的队列服务为各种后台队列服务提供了统一的API。队列允许您延迟耗时的任务,例如发送电子邮件。这可以有效减少请求响应时间。
发现问题
在Laravel中使用Redis处理队列任务,框架提供了强大的功能,但是最近遇到了一个问题,就是一个任务被多次执行。为什么呢?
先说原因:
因为在Laravel中,如果一个队列(任务)执行时间超过60秒,就会被认为是执行失败,重新加入队列,从而导致同一任务的重复执行。
这个任务的逻辑是将内容推送给用户,需要根据队列内容取出用户,遍历并通过请求后端HTTP接口发送。比如有10000个用户,当用户数量大或者接口处理速度没那么快的时候,执行时间肯定会超过60秒,所以这个任务会重新加入队列。情况更糟。如果前面的任务没有一个在60秒内完成,它们将重新加入队列,因此同一任务将被执行多次。
让我们从Laravel源代码中找到罪魁祸首。
源代码文件:vendor/laravel/framework/src/light/queue/redisqueue . PHP
/** *作业的到期时间。* * @ var int | null */protected $ expire=60;这个$expire成员变量是一个固定值。Laravel认为一个队列应该在60秒内执行。队列提取方法:
公共函数pop($ queue=null){ $ original=$ queue?美元这-违约;$ 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 33603360 PoP(),2,$queue,$queue。' :reserved ',$ this-getTime()$ this-expire);if($reserved){ return new RedisJob($this-container,$ this,$job,$ reserved,$ original);}}获取队列有几个操作,因为队列执行失败,或者执行超时等。将放入另一套进行保存,以便再次尝试。流程如下:
1.将未能执行的队列从延迟集推送到当前正在执行的队列。
2.由于执行超时,将队列从保留集重新推送到当前执行队列。
3.然后从队列中取出任务开始执行,并将队列放入预留的有序集中。
这里,eval命令用于执行这个过程,并且使用了几个lua脚本。
从队列中取出要执行的任务:
local job=redis.call('lpop ',KEYS[1])local reserved=false if(job ~=false)然后reserved=CJ son . decode(job)reserved['尝试']=reserved['尝试'] 1 reserved=cjson。编码(保留)Redis。call ('zadd ',keys [2],argv [1],reserved) endreturn {job,reserved}可以看到,Laravel在获取redis要执行的队列时,会将一个副本放入有序集中,并使用过期的时间戳作为评分。
只有当此任务完成时,才能从有序集中删除此任务。省略了从该有序集中移除队列的代码。让我们看看Laravel如何处理执行时间超过60秒的队列。
这是由这个lua脚本执行的操作:
local val=redis . call(' zrangebycore ',KEYS[1],'-inf ',ARGV[1])if(next(val) ~=nil)然后redis.call('zremrangebyrank ',KEYS[1],0,#val - 1)对于i=1,#val,100 do redis.call('rpush ',KEYS[2],unpack(val,I,math.min(i 99,# val)))endonddreturn true在这里zrangebycore找出得分从无穷大开始的元素
看到这里应该是恍然大悟。
如果队列在60秒内没有被执行,进程将在获取队列时将这些任务从保留集推送到队列。
摘要
以上就是本文的全部内容。希望本文的内容对大家的学习或工作有一定的参考价值。有问题可以留言交流。谢谢你的支持。
版权声明:Laravel重复执行同一队列任务原因的源代码分析是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。