thinkphp5 queue redis队列使用,原创可用
介绍:
thinkphp-queue 是thinkphp 官方提供的一个消息队列服务,它支持消息队列的一些基本特性:
- 消息的发布,获取,执行,删除,重发,失败处理,延迟执行,超时控制等
- 队列的多队列, 内存限制 ,启动,停止,守护等
- 消息队列可降级为同步执行
thinkphp-queue 内置了 Redis,Database,Topthink ,Sync这四种驱动。本文主要介绍 thinkphp-queue 结合其内置的 redis 驱动的使用方式和基本原理。
使用目的是为了,异步执行,减少业务流程等待时间。
实际场景是业务中记录很多调试日志,为了尽量少的影响业务响应时间,先将写入日志的请求发到队列里,然后通过消费者异步消费写入日志。
安装thinkphp-queue:
composer install topthink/think-queue
目录结构:
- 配置文件:/application/extra/queue.php。
- 消费者:/application/queue/job/DebugLog。
- 生产者:/application/queue/push/PushQueue。
代码:
配置:/application/extra/queue.php
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
use \think\Env;
return [
// 'connector' => 'Sync',
'connector' => 'Redis', // Redis 驱动
'expire' => 60, // 任务的过期时间,默认为60秒; 若要禁用,则设置为 null
'default' => 'default', // 默认的队列名称
'host' => Env::get('redis.REDIS_MASTER_HOST', '127.0.0.1'), // redis 主机ip
'port' => Env::get('redis.REDIS_MASTER_PORT', '6379'), // redis 端口
'password' => Env::get('redis.REDIS_MASTER_PASSWORD', 'ups123456ups'), // redis 密码
'select' => 0, // 使用哪一个 db,默认为 db0
'timeout' => 0, // redis连接的超时时间
'persistent' => false, // 是否是长连接
];
配置文件中的 expire 参数说明:
expire 参数指的是任务的过期时间, 单位为秒。 过期的任务,其准确的定义是
任务的状态为执行中
任务的开始执行的时刻 + expire > 当前时刻
expire 不为null 时 ,thinkphp-queue 会在每次获取下一个任务之前检查并重发过期(执行超时)的任务。
expire 为null 时,thinkphp-queue 不会检查过期的任务,性能相对较高一点。但是需要注意:
这些执行超时的任务会一直留在消息队列中,需要开发者另行处理(删除或者重发)!
消费者:/application/queue/job/DebugLog.php(注:这里也可改成通用的消费者)
<?php
namespace app\queue\job;
use think\Cache;
use think\queue\Job;
/**
* 消费者
* 调试日志
* Class DebugLog
* 队列名:DebugLogQueue
* 启动方式:php think queue:work --daemon --queue DebugLogQueue
* @package app\queue\job
*/
class DebugLog
{
/**
* Explain: fire方法是消息队列默认调用的方法
* @param Job $job 当前的任务对象
* @param $data 发布任务时自定义的数据
* User: smt \(^o^)/~
* Date: 2020-10-14 15:12
* php think queue:work --daemon --sleep 3 --tries 2 --delay 3 --queue debugLogJobQueue
* php think queue:work --daemon --tries 2 --delay 3 --queue debugLogJobQueue
* daemon:后台守护进程 helloJobQueue:队列名称 sleep:无任务休眠3秒 tries:执行次数上线,比如异常 delay:抛出异常时,延迟3秒在执行
*/
public function fire(Job $job, $data)
{
if (empty($data)) return;
$data = $data['content'];
echo "in hand... \n";
// 有些消息在到达消费者时,可能已经不再需要执行了
$isJobStillNeedToBeDone = $this->checkDatabaseToSeeIfJobNeedToBeDone($data);
if(!$isJobStillNeedToBeDone){
$job->delete();
return;
}
// 业务处理
$isJobDone = $this->handleTask($data);
if ($isJobDone) {
// 如果任务执行成功, 记得删除任务
$job->delete();
}else{
if ($job->attempts() > 3) {
//通过这个方法可以检查这个任务已经重试了几次了
logs($data,'debug_log_queue_error');
// $job->delete();
// 也可以重新发布这个任务
$job->release(2); //$delay为延迟时间,表示该任务延迟2秒后再执行
}
}
echo "ending \n";
}
/**
* Explain: 有些消息在到达消费者时,可能已经不再需要执行了
* @param $data 发布任务时自定义的数据
* @return bool 任务执行的结果
* User: smt \(^o^)/~
* Date: 2020-10-14 15:15
*/
private function checkDatabaseToSeeIfJobNeedToBeDone($data){
return true;
}
/**
* Explain: 根据消息中的数据进行实际的业务处理...
* @param $data 发布任务时自定义的数据
* @return bool
* User: smt \(^o^)/~
* Date: 2020-10-14 15:16
*/
private function handleTask($data)
{
$res = \app\common\model\DebugLog::create($data);
if (empty($res)){
logs($data,'debug_log_queue_handleTask_error');
return false;
}
return true;
}
}
生产者:/application/queue/push/PushQueue.php
<?php
namespace app\queue\push;
use think\Queue;
/**
* 生产者
* 调试日志
* Class DebugLog
* @package app\queue\job
*/
class PushQueue
{
/**
* Explain: 说明
* @param $data 发送到队列中的数据
* @param $consumerJob 消费者任务文件名 DebugLog
* @param $queueName 队列名称 debugLogJobQueue
* User: smt \(^o^)/~
* Date: 2020-10-14 16:08
*/
public static function pushMsg($data,$consumerJob,$queueName){
$consumerJob = "app\queue\job\\$consumerJob";
$content = [
'str'=>time().uniqid(),
'content' => $data
];
// 入队:队列类,队列数据参数,队列名称:用于执行队列命名
$isPushed = Queue::push($consumerJob, $content, $queueName);
// database 驱动时,返回值为 1|false ; redis 驱动时,返回值为 随机字符串|false
if( $isPushed === false ){
logs(['data'=>$content,'consumerJob'=>$consumerJob,'queueName'=>$queueName],'push_queue_error');
}else{
logs(['data'=>$content,'consumerJob'=>$consumerJob,'queueName'=>$queueName],'push_queue_ok');
}
return true;
}
}
业务中使用:
1、先启动消费者进行监听消费:
// 启动消费者
$ php think queue:work --daemon --tries 2 --delay 3 --queue DebugLogQueue
附上linux docker环境启动方式:
[root@VM_0_2_centos ups_develop]# docker exec -t e8085f79d2cd /bin/bash -c "php /home/wwwroot/ups-dev/think queue:work --daemon --tries 2 --delay 3 --queue DebugLogQueue"
in hand...
ending
Processed: app\queue\job\DebugLog
in hand...
ending
Processed: app\queue\job\DebugLog
in hand...
ending
Processed: app\queue\job\DebugLog
linux环境想要退出监控预览、后台执行,直接 ctrl + c ;
2、发送任务到队列:
use app\queue\push\PushQueue;
// 发送到队列,data:消息、DebugLog:job消费者文件名、DebugLogQueue:队列名称
PushQueue::pushMsg($data,'DebugLog','DebugLogQueue');
命令模式介绍:
- queue:work 命令
- work 命令: 该命令将启动一个 work 进程来处理消息队列。
php think queue:work --queue DebugLogQueue
- queue:listen 命令
listen 命令: 该命令将会启动一个 listen 进程 ,然后由 listen 进程通过 proc_open(‘php think queue:work --queue="%s" --delay=%s --memory=%s --sleep=%s --tries=%s’) 的方式来周期性地创建一次性的 work 进程来消费消息队列, 并且限制该 work 进程的执行时间, 同时通过管道来监听 work 进程的输出。
php think queue:listen --queue DebugLogQueue
命令行参数:
Work 模式
php think queue:work \
--daemon //是否循环执行,如果不加该参数,则该命令处理完下一个消息就退出
--queue helloJobQueue //要处理的队列的名称
--delay 0 \ //如果本次任务执行抛出异常且任务未被删除时,设置其下次执行前延迟多少秒,默认为0
--force \ //系统处于维护状态时是否仍然处理任务,并未找到相关说明
--memory 128 \ //该进程允许使用的内存上限,以 M 为单位
--sleep 3 \ //如果队列中无任务,则sleep多少秒后重新检查(work+daemon模式)或者退出(listen或非daemon模式)
--tries 2 //如果任务已经超过尝试次数上限,则触发‘任务尝试次数超限’事件,默认为0
Listen 模式
php think queue:listen \
--queue helloJobQueue \ //监听的队列的名称
--delay 0 \ //如果本次任务执行抛出异常且任务未被删除时,设置其下次执行前延迟多少秒,默认为0
--memory 128 \ //该进程允许使用的内存上限,以 M 为单位
--sleep 3 \ //如果队列中无任务,则多长时间后重新检查
--tries 0 \ //如果任务已经超过重发次数上限,则进入失败处理逻辑,默认为0
--timeout 60 // work 进程允许执行的最长时间,以秒为单位
end