关于 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 相关文章推荐
刚才在简化php的库,结果发现很多东西
Dec 31 PHP
dede全站URL静态化改造[070414更正]
Apr 17 PHP
实现php加速的eAccelerator dll支持文件打包下载
Sep 30 PHP
PHP之生成GIF动画的实现方法
Jun 07 PHP
php使用PDO操作MySQL数据库实例
Dec 30 PHP
ioncube_loader_win_5.2.dll的错误解决方法
Jan 04 PHP
学习php设计模式 php实现策略模式(strategy)
Dec 07 PHP
Zend Framework教程之MVC框架的Controller用法分析
Mar 07 PHP
PHP简单获取及判断提交来源的方法
Apr 22 PHP
PHP类的特性实例分析
Sep 28 PHP
PHP基于SPL实现的迭代器模式示例
Apr 22 PHP
PHP使用openssl扩展实现加解密方法示例
Feb 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
PHP 错误之引号中使用变量
2009/05/04 PHP
PHP文件读写操作之文件写入代码
2011/01/13 PHP
php控制linux服务器常用功能 关机 重启 开新站点等
2012/09/05 PHP
PHP5函数小全(分享)
2013/06/06 PHP
php使用正则表达式获取图片url的方法
2015/01/16 PHP
php通过asort()给关联数组按照值排序的方法
2015/03/18 PHP
php官方微信接口大全(微信支付、微信红包、微信摇一摇、微信小店)
2015/12/21 PHP
PHP MSSQL 分页实例
2016/04/13 PHP
php base64 编码与解码实例代码
2017/03/21 PHP
PHP获取MySQL执行sql语句的查询时间方法
2018/08/21 PHP
js 数据类型转换总结笔记
2011/01/17 Javascript
jQuery插件slicebox实现3D动画图片轮播切换特效
2015/04/12 Javascript
javascript中获取元素标签中间的内容的实现方法
2016/10/08 Javascript
移动端脚本框架Hammer.js
2016/12/15 Javascript
原生js实现简单的Ripple按钮实例代码
2017/03/24 Javascript
Nginx 配置多站点vhost 的方法
2018/01/07 Javascript
es6中reduce的基本使用方法
2019/09/10 Javascript
js判断在哪个浏览器打开项目的方法
2020/01/21 Javascript
用云开发Cloudbase实现小程序多图片内容安全监测的代码详解
2020/06/07 Javascript
JS轮播图的实现方法2
2020/08/25 Javascript
vue中解决chrome浏览器自动播放音频和MP3语音打包到线上的实现方法
2020/10/09 Javascript
微信小程序实现音乐播放页面布局
2020/12/11 Javascript
python实现的正则表达式功能入门教程【经典】
2017/06/05 Python
Python基于tkinter模块实现的改名小工具示例
2017/07/27 Python
python读取文件名称生成list的方法
2018/04/27 Python
python读取LMDB中图像的方法
2018/07/02 Python
python3.6.3安装图文教程 TensorFlow安装配置方法
2020/06/24 Python
日期和时间问题
2015/01/04 面试题
数控专业个人求职信范文
2014/02/05 职场文书
毕业实习评语
2014/02/10 职场文书
小学生教师节演讲稿
2014/09/03 职场文书
个人对照检查材料思想汇报(四风问题)
2014/09/25 职场文书
上下班时间调整通知
2015/04/23 职场文书
详解Vue的sync修饰符
2021/05/15 Vue.js
SQLServer常见数学函数梳理总结
2022/08/05 MySQL