关于 Laravel Redis 多个进程同时取队列问题详解


Posted in PHP onDecember 25, 2017

前言

最近在工作中遇到了一个问题,开启多个进程处理队列会重复读取 Redis 中队列吗?是否因此导致重复执行任务?下面就来通过示例代码详细介绍下。

使用 Supervisor 监听 Laravel 队列任务,其中 Supervisor 的配置如下:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/xxx.cn/artisan queue:work --queue=sendfile --tries=3 --daemon
autostart=true
autorestart=true
numprocs=8
redirect_stderr=true
stdout_logfile=/var/www/xxx.cn/worker.log

注意: numprocs = 8,代表开启 8 个进程来执行 command 中的命令。

如下:

PS C:\Users\tanteng\website\laradock> docker-compose exec php-worker sh
/etc/supervisor/conf.d # ps -ef | grep php
 7 root  0:00 php /var/www/xxx.cn/artisan queue:work --queue=sendfile --tries=3 --daemon
 8 root  0:00 php /var/www/xxx.cn/artisan queue:work --queue=sendfile --tries=3 --daemon
 9 root  0:00 php /var/www/xxx.cn/artisan queue:work --queue=sendfile --tries=3 --daemon
 10 root  0:00 php /var/www/xxx.cn/artisan queue:work --queue=sendfile --tries=3 --daemon
 11 root  0:00 php /var/www/xxx.cn/artisan queue:work --queue=sendfile --tries=3 --daemon
 12 root  0:00 php /var/www/xxx.cn/artisan queue:work --queue=sendfile --tries=3 --daemon
 13 root  0:00 php /var/www/xxx.cn/artisan queue:work --queue=sendfile --tries=3 --daemon
 14 root  0:00 php /var/www/xxx.cn/artisan queue:work --queue=sendfile --tries=3 --daemon
 44 root  0:00 grep php

Laravel 多进程读取队列内容是否会重复

在 Laravel 的某个控制器方法,一次放入多个任务队列:

public function index(Request $request)
{
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
 $this->dispatch((new SendFile3())->onQueue('sendfile'));
}

在队列处理的方法打印日志,打印处理的队列的 ID:

app/Jobs/SendFile3.php

public function handle()
{
 info('invoke SendFile3');
 dump('invoke handle');
 $rawbody = $this->job->getRawBody();
 $info = json_decode($rawbody, true);
 info('queue id:' . $info['id']);
}

Laravel 使用 Redis 的 list 作为队列的数据结构,并会为每个队列分配一个 ID,数据结构如下:

{
 "job": "Illuminate\\Queue\\CallQueuedHandler@call",
 "data": {
 "commandName": "App\\Jobs\\SendFile3",
 "command": "O:18:\"App\\Jobs\\SendFile3\":4:{s:6:\"\u0000*\u0000job\";N;s:10:\"connection\";N;s:5:\"queue\";s:8:\"sendfile\";s:5:\"delay\";N;}"
 },
 "id": "hadBcy3IpNsnOofQQdHohsa451OkQs88",
 "attempts": 1
}

请求这个控制器路由(或者命令行方式),就可以看到 Redis 中多了很多队列任务了,如图:

关于 Laravel Redis 多个进程同时取队列问题详解

这个时候开启 Supervisor 处理队列任务,并查看日志:

[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:JaClJzhDEvntzLCRIz6uRQkCVLbE8Y9C
[2017-12-23 19:01:01] local.INFO: queue id:ukHv0Li4P2VgPa55qU6yEOJM27Mo5YwJ
[2017-12-23 19:01:01] local.INFO: queue id:ObMpwDTmnaveBUkU7aan5abt3Agyt90l
[2017-12-23 19:01:01] local.INFO: queue id:fo2qZn2ftSdQtdnKOciMK7iJb4qlhRGE
[2017-12-23 19:01:01] local.INFO: queue id:uLjFMoOU7Wk7bOAd4zpHb3ccRMJHBtR6
[2017-12-23 19:01:01] local.INFO: queue id:87ULqPBObFmGr16nl5wxFVOi71zGCeRM
[2017-12-23 19:01:01] local.INFO: queue id:9UVl0muQLzBqlRI99rChGW2ElXwVEMIE
[2017-12-23 19:01:01] local.INFO: queue id:a0vgyZuz9HtmH7DGHEpXqesFTcQU3QAF
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:2cXuXxopPkgYiV4WO8gv9CJ6CwXeKtYL
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:9acTAYa8cxpJX6Q3Gb1sULokotP8reqZ
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:BPHQvBboChlv4gr2I0vyLVyw9bijtTYJ
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:Fm6tNajdxYKtdQbDMYDmwWJFLnNikRyg
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:nyAbcvSkBVPbaH3e2ItQkoLJlP1ficib
[2017-12-23 19:01:01] local.INFO: queue id:WBHsSVZtP43569UoPXxfLLJcvYmPW7cP
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:bliPnKcRSDApwVmKLNxEhaKelhm0RDEY
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:eOAoQucEIwRz9uZ64xm6IDKgiqj9Xc3W
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:lzise9EiqQqINrhALbmAI4qNg7qylpb2
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:WXYKvcfOhS1pPnwOwUTsenoMv5l5EUXe
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:XtH5JiwLgnrwWzI02Oyi70pihAOkuJUD
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:9ehmE5HImlpNubpY0xWN8UVrOzxeMqws
[2017-12-23 19:01:01] local.INFO: queue id:C1sK87cpZl47edLA0zhfo7PJ9MIEcoyx
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:2kwl51oH4lyyRrljCReGUCkNiJRDl7oe
[2017-12-23 19:01:01] local.INFO: queue id:ObRpoqrYTPYiyv2delMlOXu3sAPpWJlN
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:6qgu6W3TapLjSrt688yv9HRXvDDLxntz
[2017-12-23 19:01:01] local.INFO: queue id:wiTlERhwn7s9cQkfUF9lLlNADpXjKncI
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:ZSLW0VLFBDpL4wjTJzu3Yb3V45pNe807
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:qhZlXLGfGWRluIeNm7VbllmTJZYb2h5n
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:LUx1IByD3L2psNl9BZwHhk2knXyRPzW6
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:M2RESPjyo5hpAFxxL0EQbWwsUq4jpmWn
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:hUsGaiIAOO6ZfGQc5kGHGpsv5RpoRPYO
[2017-12-23 19:01:01] local.INFO: queue id:cEHJsOy6bLeZ4NbncPziaHqlarMeyyEF
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:w4bkFiJKMU5saqG2xKN3ZRL5BYXGATMk
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:0zBuwbxlrEhhxKfYBkVyTY4z35f154sI
[2017-12-23 19:01:01] local.INFO: queue id:mvoZvyDPvq4tcPjEy9G7PMtH3MwPkPik
[2017-12-23 19:01:01] local.INFO: invoke SendFile3
[2017-12-23 19:01:01] local.INFO: queue id:TLvF74eeidECWKtjZqWvW03UJTRPTL9r
[2017-12-23 19:01:01] local.INFO: queue id:me8wyPfgcz0nf9xvcXz0hf2xVxqa1FFS

这 8 个进程并发处理队列,但从打印的日志看,没有出现同样的 ID. 我们再看一下 Laravel 如何使用 Redis 处理队列的。

分析一下 Laravel 队列的处理

Laravel 中入队列方法

public function pushRaw($payload, $queue = null, array $options = [])
{
 $this->getConnection()->rpush($this->getQueue($queue), $payload);
 
 return Arr::get(json_decode($payload, true), 'id');
}

用的是 Redis 的 rpush 命令。

Laravel 中取队列方法

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);
 }
}

这里用的是 lua 脚本取队列,如下:

public static function pop()
{
 return <<<'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}
LUA;
}

那么结论是:从 Laravel 的处理方式和打印的日志结果看,即使多个进程读取同一个队列,也不会读取到一样的数据。

总结

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

PHP 相关文章推荐
最省空间的计数器
Oct 09 PHP
PHP4实际应用经验篇(5)
Oct 09 PHP
php 更新数据库中断的解决方法
Jun 05 PHP
PHP CKEditor 上传图片实现代码
Nov 06 PHP
php中判断字符串是否全是中文或含有中文的实现代码
Sep 16 PHP
PHP四大安全策略
Mar 12 PHP
php页码形式分页函数支持静态化地址及ajax分页
Mar 28 PHP
PHP使用CURL实现对带有验证码的网站进行模拟登录的方法
Jul 23 PHP
Zend Framework+smarty用法实例详解
Mar 19 PHP
详解php与ethereum客户端交互
Apr 28 PHP
PHP折半(二分)查找算法实例分析
May 12 PHP
Smarty模板变量与调节器实例详解
Jul 20 PHP
源码分析 Laravel 重复执行同一个队列任务的原因
Dec 25 #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
You might like
ubuntu 编译安装php 5.3.3+memcache的方法
2010/08/05 PHP
基于php中使用excel的简单介绍
2013/08/02 PHP
zf框架db类的分页示例分享
2014/03/14 PHP
ThinkPHP使用Ueditor的方法详解
2016/05/20 PHP
PHP处理CSV表格文件的常用操作方法总结
2016/07/01 PHP
PHP实现多级分类生成树的方法示例
2017/02/07 PHP
PHP中函数gzuncompress无法使用的解决方法
2017/03/02 PHP
jquery利用ajax调用后台方法实例
2013/08/23 Javascript
jquery实现input输入框实时输入触发事件代码
2014/01/28 Javascript
实例分析浏览器中“JavaScript解析器”的工作原理
2016/12/12 Javascript
jquery插件bootstrapValidator表单验证详解
2016/12/15 Javascript
微信小程序 数据封装,参数传值等经验分享
2017/01/09 Javascript
JavaScript异步加载问题总结
2018/02/17 Javascript
vue中使用iview自定义验证关键词输入框问题及解决方法
2018/03/26 Javascript
react项目如何使用iconfont的方法步骤
2019/03/13 Javascript
vue 对象添加或删除成员时无法实时更新的解决方法
2019/05/01 Javascript
ES6中的迭代器、Generator函数及Generator函数的异步操作方法
2019/05/12 Javascript
vue-cli3项目展示本地Markdown文件的方法
2019/06/07 Javascript
vue 路由meta 设置导航隐藏与显示功能的示例代码
2020/09/04 Javascript
详解Vue2的diff算法
2021/01/06 Vue.js
python PyTorch预训练示例
2018/02/11 Python
pandas通过字典生成dataframe的方法步骤
2019/07/23 Python
Python如何使用函数做字典的值
2019/11/30 Python
一款纯css3实现的颜色渐变按钮的代码教程
2014/11/12 HTML / CSS
HTML5自定义属性前缀data-及dataset的使用方法(html5 新特性)
2017/08/24 HTML / CSS
优衣库英国官网:UNIQLO英国
2016/12/25 全球购物
澳大利亚领先的在线美容商城:Adore Beauty
2017/04/14 全球购物
数控技术应届生求职信
2013/11/13 职场文书
大四本科生的自我评价
2013/12/30 职场文书
就业表自我评价分享
2014/02/06 职场文书
公司会计主管岗位责任制
2014/03/01 职场文书
党校培训自我鉴定范文
2014/04/10 职场文书
婚前协议书范本
2014/04/15 职场文书
计算机毕业大学生求职信
2014/06/26 职场文书
合作协议书模板2014
2014/09/26 职场文书
Python序列化模块JSON与Pickle
2022/06/05 Python