PHP与redis队列 是如何处理 “耗时任务” ?

97 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情

在项目中,会遇到 “耗时任务”,而 Laravel 中的队列可以很好的对这类任务进行异步处理。如用户上传大文件,可以先对其他信息进行操作,而对大文件的操作甩给队列做,队列处理完成后再将相关信息更新回去。

本篇案例基于 Laravel9 演示,需求--用户上传图片后,对图片进行裁剪

第一步:安装一个全新的 Laravel 项目(省略)

第二步:配置 redis 作为对队列驱动

// .env
QUEUE_CONNECTION=redis

第二步:创建相关模型与控制器

// 1、创建模型-迁移文件-控制器
php artisan make:Demo -mc

// 2、编写迁移文件
Schema::create('demos', function (Blueprint $table) {
   $table->id();
   $table->string('title')->comment('标题');
   $table->string('image')->comment('图片');
   $table->timestamps();
});

// 3、执行迁移文件
php artisan migrate

第三步:安装扩展包

composer reuqire laravel/ui
php artisan ui bootstrap
npm install
npm run dev

composer require intervention/image

第四步:注册路由

// web.php
Route::get('demos/create', [\App\Http\Controllers\DemoController::class'create']);
Route::post('demos/store', [\App\Http\Controllers\DemoController::class'store']);
Route::get('demos/{id}', [\App\Http\Controllers\DemoController::class'show']);

第五步:编写控制方法

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class DemoController extends Controller
{
   public function create()
   {
       return view('demos.create');
   }

   public function store(Request $request)
   {
       $demo new Demo($request->all());

       if ($demo->save()) {
           $image $request->file('image');
           // 获取图片扩展名
           $ext  $image->getClientOriginalExtension();
           $name mt_rand(100000999999) . '.' . $ext;
           // 获取图片二进制数据并使用 base64 进行编码
           $content base64_encode($image->getContent());
           // 将图片裁剪工作推送到 demo-uploads 队列进行异步处理【异步处理关键所在】
           DemoUploadProcessor::dispatch($name$content$demo)->onQueue('demo-uploads');

           return redirect('demos/' . $demo->id);
       }
   }
   
   public function show($id)
   {
       $demo Demo::find($id);
       return view('demos.show'compact('demo'));
   }
}

第六步:创建相关视图

// demos/create.blade.php

@extends('layouts.app')

@section('content')
   <div class="container">
       <div class="card">
           <div class="card-header">发布新文章</div>
           <div class="card-body">
               <form action="/demos/store" method="POST" enctype="multipart/form-data">
                   @csrf

                   <div class="form-group">
                       <label for="title">文章标题:</label>
                       <input type="title" class="form-control" name="title" id="title">
                   </div>
                   <div class="form-group">
                       <label for="image">封面图片:</label>
                       <input type="file" class="form-control" id="image" name="image">
                   </div>

                   <button type="submit" class="btn btn-primary mt-3">发布文章</button>
               </form>
           </div>
       </div>
   </div>
@endsection
// demos/show.blade.php

@extends('layouts.app')

@section('content')
   <div class="contaner">
       <h3>{{$demo->title}}</h3>
       <p>
           @if ($demo->image)
               <img src="{{ '/storage/demos/' .  $demo->image }}" width="200">
           @endif
       </p>=
   </div>
@endsection

第七步:创建图片任务处理类1)创建任务类

php artisan make:job DemoUploadProcessor

2)编写任务类

<?php

namespace App\Jobs;

use App\Models\Demo;
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;
use Illuminate\Support\Facades\Storage;

class DemoUploadProcessor implements ShouldQueue
{
   use DispatchableInteractsWithQueueQueueableSerializesModels;

   // 文件名
   public string $name;
   // demo 实例
   public Demo $demo;

   // 最大尝试次数,超过标记为执行失败
   public int $tries 10;
   // 最大异常数,超过标记为执行失败
   public int $maxExceptions 3;
   // 超时时间,3 分钟,超过则标记为执行失败
   public int $timeout 180;
   
   public function __construct(string $namestring $content, Demo $demo)
   {
       $this->name = $name;
       $this->content = $content;
       $this->demo = $demo;
   }


   public function handle()
   {
       $path =  mt_rand(1000099999) . $this->name;
       // 判断文件是否存在
       if (Storage::disk('public')->exists($path)) {
           return;
       }

       $content base64_decode($this->content);
       // 若图片未保存成功则 5s 后重试
       if (Storage::disk('public')->put('demos/' . $path$content)) {
           $this->demo->image = $path;

           if ($this->demo->save()) {
               $url Storage::disk('public')->url('demos/' . $path);
               // 保存成功后裁剪图片
               \Intervention\Image\Facades\Image::make($url)->crop(300,300,50,50)->save(public_path(). '/crop-demos/'  . $path);
           }
       } else {
           $this->release(5);
       }
   }
}

第八步:执行队列

php artisan queue:work --queue=events,demo-uploads --tries=3

第九步:访问发布图片访问 域名**/demos/create** 进行添加图片