最近在学习laravel框架,突然发现我似乎从未发布过关于队列消息方面的文章,今天结合laravel框架来说说队列消息方面的内容。
队列消息是什么?
消息(Message)是指在应用之间传送的数据,消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象。
消息队列(Message Queue)是一种应用间的通信方式,消息发送后可以立即返回,有消息系统来确保信息的可靠专递,消息发布者只管把消息发布到MQ中而不管谁来取,消息使用者只管从MQ中取消息而不管谁发布的,这样发布者和使用者都不用知道对方的存在。
队列消息可以做什么?
增强web响应,将不需要立即响应的消息交给后台任务来执行。
举个例子,一个用户A完成了注册操作,注册成功后我们需要发一份welcome邮件给用户,传统方案是采用同步执行发送(即是在后台业务程序处理中将邮件发送出去,发送出去后才返回消息给用户)。采用队列消息后,我们将发送邮件的工作交给队列去执行,而先把响应内容返回给用户,相关的邮件通知后续会由队列任务的执行而发送(队列任务是一个进程,它将一直在后台执行)
将复杂耗时的任务交由后台任务处理,从而快速给用户响应
laravel 队列消息的使用
首先需要配置队列消息,我采用的是依赖redis来存储队列消息,所以你在使用队列消息的时候最好先把laravel的redis配置先配置好
话不多说,上代码
1、要实现将一个任务放置至redis中,需要类实现ShouldQueue接口,并在类方法中定义handle方法,laravel 会自动扫描这个类是否实现了ShouldQueue接口,从而将类的实例放置进入redis。
<?php
namespace App\Jobs;
use App\Exceptions\AppException;
use App\Exceptions\Ecode;
use App\Models\User;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class UserVersionJob implements ShouldQueue
{
//队列特性trait
use Batchable,Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $userORM;
/**
* Create a new job instance.
* @param User $userORM 一个用户ORM模型对象
* @return void
*/
public function __construct(User $userORM)
{
$this->userORM = $userORM;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
/*
if($this->userORM->versionId >= 2)
{
throw new AppException(Ecode::RUNNING_ERROR);
}else{
$this->userORM->versionId = $this->userORM->versionId+1;
$this->userORM->save();
}*/
//执行的工作任务是让他的版本号自增1
$this->userORM->increment('versionId',1);
//$this->userORM->save();
//return true;
}
}
如上我们就定义好了一个队列任务类,那么如何将这个类加入到redis中呢?又如何在后台去执行这个任务呢?
任务类的使用
<?php
namespace App\Http\Controllers;
use App\Jobs\UserVersionJob;
use App\Services\User\UserService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\RateLimiter;
class TestController extends Controller
{
public function index5(UserService $userService)
{
$userORM = $userService->get(1);
//分派此任务,因我设置的队列匹配redis,所以执行到这一步后,此任务会被加入到redis队列
UserVersionJob::dispatch($userORM);
}
}
如上所示,我们通过此方法将任务派发到了redis。有人可能会问,我传递是一个ORM对象,这样也可以吗?其实是可以的,内容中的ORM会被序列化只取其中的主键ID存入了redis,当这个任务从redis中获取出来时,会自动进行数据库操作重新生成userORM(userORM 就是Eloquent ORM 对象)
任务类的执行
在命令行中运行,即可监听任务的使用
php artisan queue:work
有时,我们还会为每个任务或是每一类任务定义一个专属名称的对垒,比如一班的所有人工作任务,二班所有人的工作任务,其实本质就是给每个任务放置到之列的任务队列上去。
<?php
namespace App\Http\Controllers;
use App\Jobs\UserVersionJob;
use App\Services\User\UserService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\RateLimiter;
class TestController extends Controller
{
public function index5(UserService $userService)
{
$userORM = $userService->get(1);
//my_queue_name 就是自定义的队列名称,接受字符串
UserVersionJob::dispatch($userORM)->onQueue('my_queue_name');
}
}
运行指定队列任务的执行
php artisan queue:work --queue=my_queue_name
当然,队列消息还有许多其他的特性,比如unique 单一任务特性,队列运行时关于进程的睡眠(其实我很推荐这个,当没有任务需要执行的时候,可以让进程进入睡眠状态,节省服务器资源。),队列优先级,批任务的处理
队列睡眠参数–sleep,如下,含义为当没有任务的时候进程睡眠3秒后再次获取是否有任务需要执行,如有就执行任务,没得再次睡眠3秒
php artisan queue:work --queue=my_queue_name --sleep=3
说道这里不得不说一个安全性的问题。
特殊场景,当多个任务依赖同一个userORM时,第1个任务就修改了userORM对象的属性数据,那么之后再执行的任务是否拿到的是一个脏数据(即是没有被任务1修改后的数据,这里我做过测试以及查阅过文档:内容中的ORM会被序列化只取其中的主键ID存入了redis,当这个任务从redis中获取出来时,会自动进行数据库操作重新生成userORM)
/**
* Notes:任务链
* Author:tanyong
* DateTime:2022/5/10
*/
public function byRun()
{
$userORM = (new UserService())->get(1);
$logService = new LogService();
$batch = Bus::batch([
new UserVersionJob($userORM),
new UserVersionJob($userORM),
new UserVersionJob($userORM),
new UserVersionJob($userORM)
])->then(function() use($userORM,$logService){
// 所有任务都成功完成...
$logService->add('then handler success',0);
})->catch(function() use($userORM,$logService){
// 检测到第一批任务失败...
$logService->add('exception handler success',0);
})->finally(function() use($userORM,$logService){
// 批处理已完成执行...
$logService->add('finally handler success',0);
})->dispatch();
dd($batch->id);
}
如上所示任务链,每个任务都会对userORM对象进行操作,但是userORM对象每次在执行时都会通过id重新去数据库获取