Advertisement
  1. Code
  2. PHP
  3. Laravel

Report de tâches dans Laravel à l'aide de files d'attente

Scroll to top
Read Time: 17 min

() translation by (you can also view the original English article)

Dans cet article, nous allons explorer l'API Queue dans le framework Web Laravel. Il vous permet de différer des tâches gourmandes en ressources lors de l'exécution de script afin d'améliorer l'expérience globale de l'utilisateur final. Après avoir introduit la terminologie de base, je vais vous la démontrer en implémentant un exemple concret.

Le temps de chargement des pages est un aspect important de tout site Web performant, et il ne faut pas négliger son importance, car elle affecte également le référencement du site et l'expérience globale de l'utilisateur final. Plus souvent qu'autrement, vous finissez par avoir besoin de déboguer des pages Web avec des temps de chargement longs. Bien sûr, vous pouvez utiliser différentes approches pour remédier à ce problème.

Après enquête, vous réalisez souvent que certains blocs de code entraînent un retard dans l'exécution de la page. La prochaine chose que vous pouvez essayer est d’identifier les blocs qui peuvent être différés pour le traitement et qui n’ont pas d’impact réel sur le résultat final de la page en cours. Cela devrait vraiment améliorer la vitesse globale de la page Web, car nous avons éliminé les blocs de code à l'origine du retard.

Aujourd'hui, nous allons explorer un concept similaire dans le contexte du framework Web Laravel. En fait, Laravel fournit déjà une API intégrée utile qui nous permet de différer le traitement des tâches - l’API de file d’attente. Sans perdre beaucoup de votre temps, je vais maintenant aborder les éléments de base de l'API Queue.

Pilotes, connexions, files d'attente et travaux

L'objectif fondamental de l'API Queue est d'exécuter des travaux ajoutés dans une file d'attente. Ensuite, la file d'attente peut appartenir à une connexion spécifique et cette connexion peut appartenir à un pilote de file d'attente spécifique configuré avec cette connexion elle-même. Essayons brièvement de comprendre ce que je viens de dire.

Pilotes de file d'attente

De la même manière que vous auriez utilisé un pilote différent pour votre connexion de base de données, vous pouvez également choisir parmi une variété de pilotes de file d'attente différents. L'API de file d'attente prend en charge différents adaptateurs tels que base de données, beanstalkd, sqs et redis.

Le pilote de file d'attente est simplement un emplacement utilisé pour stocker des informations relatives à la file d'attente. Ainsi, si vous utilisez un pilote de file d'attente de base de données, par exemple, le nouveau travail sera ajouté à la table des travaux de la base de données. Par ailleurs, si vous avez configuré Redis en tant que pilote de file d'attente par défaut, le travail sera ajouté au serveur Redis.

L'API de file d'attente fournit également deux pilotes de file d'attente spéciaux à des fins de test: sync et null. Le pilote de la file d'attente de synchronisation est utilisé pour exécuter un travail en file d'attente immédiatement, tandis que le pilote de la file d'attente nulle est utilisé pour ignorer un travail afin qu'il ne soit pas exécuté du tout.

Les liaisons

Lorsque vous configurez l'API de file d'attente pour la première fois, vous devez spécifier une connexion par défaut à utiliser pour le traitement de la file d'attente par défaut. À tout le moins, la connexion devrait fournir les informations suivantes:

  • le pilote de file d'attente qui sera utilisé
  • les valeurs de configuration spécifiques du pilote de file d'attente
  • le nom de la file d'attente par défaut dans lequel le travail sera ajouté

Les files d'attente

Lorsque vous ajoutez un travail dans une file d'attente, il sera ajouté à la file d'attente par défaut. En fait, cela devrait aller dans la plupart des cas, à moins que vous n'ayez des emplois qui nécessitent une priorité plus élevée que d'autres. Dans ce cas, vous pouvez créer une file d'attente nommée haut et placer les travaux les plus prioritaires dans cette file d'attente.

Lorsque vous exécutez un programme de traitement de file d'attente traitant des travaux en file d'attente, vous pouvez éventuellement transmettre le paramètre --queue, qui vous permet de répertorier les noms de file d'attente dans l'ordre dans lequel ils doivent être traités. Par exemple, si vous spécifiez --queue=high,default, les tâches de la file d'attente haute sont traitées en premier, puis, une fois l'opération terminée, elles récupèrent les tâches de la file d'attente par défaut.

Emplois

Un travail dans l'API de file d'attente est une tâche différée du flux d'exécution principal. Par exemple, si vous souhaitez créer une vignette lorsque l'utilisateur télécharge une image depuis le serveur, vous pouvez créer un nouveau travail qui gère le traitement des vignettes. De cette manière, vous pouvez différer la tâche de traitement des vignettes du flux d'exécution principal.

C'était une introduction de base à la terminologie de l'API de file d'attente. Dans la section suivante, nous verrons comment créer un travail de file d'attente personnalisé et l'exécuter à l'aide d'un programme de travail de file d'attente Laravel.

Créez votre premier travail en file d'attente

A présent, vous devriez avoir confiance en vos tâches en file d'attente. À partir de cette section, nous allons implémenter un exemple concret illustrant le concept de travail en file d'attente dans Laravel.

Le plus souvent, vous vous retrouvez dans la situation où vous devez créer différentes versions miniatures d'une image téléchargée par un utilisateur. Dans la plupart des cas, le développeur essaie de le traiter en temps réel afin que différentes versions des images soient créées immédiatement lorsque l'utilisateur télécharge une image.

Cela semble être une approche raisonnable si vous voulez créer deux versions et cela ne prend pas trop de temps au départ. D'autre part, si vous utilisez une application qui nécessite un traitement lourd et consomme donc plus de ressources, le traitement en temps réel peut aboutir à une mauvaise expérience utilisateur.

L'option évidente qui vous vient à l'esprit est de différer le traitement de la génération de vignettes le plus tard possible. L'approche la plus simple que vous pouvez implémenter dans ce scénario spécifique consiste à définir un travail cron qui déclenche le traitement à intervalles réguliers, et tout va bien.

Une approche bien meilleure, en revanche, consiste à différer la tâche et à la placer dans une file d’attente, et à laisser le responsable du traitement de la file d’attente la traiter quand elle en aura l’occasion. Dans un environnement de production, l’ouvrier de file d’attente est un script démon qui exécute et traite en permanence les tâches d’une file. L'avantage évident de cette approche est une expérience utilisateur bien meilleure, et vous n'avez pas à attendre l'exécution de cron car le travail sera traité dès que possible.

Je suppose que c'est assez de théorie pour commencer avec une implémentation réelle.

Dans notre cas, nous allons utiliser le pilote de la file d'attente database, ce qui nous oblige à créer la table des travaux dans la base de données. La table jobs contient tous les jobs devant être traités lors de la prochaine exécution du programme de traitement de file d'attente.

Avant de créer la table jobs, changeons la configuration de la file d'attente par défaut d'sync en database dans le fichier config/queue.php.

1
...
2
...
3
/*

4
|--------------------------------------------------------------------------

5
| Default Queue Driver

6
|--------------------------------------------------------------------------

7
|

8
| Laravel's queue API supports an assortment of back-ends via a single

9
| API, giving you convenient access to each back-end using the same

10
| syntax for each one. Here you may set the default queue driver.

11
|

12
| Supported: "sync", "database", "beanstalkd", "sqs", "redis", "null"

13
|

14
*/
15
16
'default' => env('QUEUE_DRIVER', 'database'),
17
...
18
...

En fait, Laravel fournit déjà une commande artisanale qui nous aide à créer la table jobs . Exécutez la commande suivante à la racine de votre application Laravel et créez la migration de base de données nécessaire pour créer la table jobs .

1
$php artisan queue:table

Le fichier de migration généré dans la base de database/migrations/YYYY_MM_DD_HHMMSS_create_jobs_table.php devrait ressembler à ceci:

1
<?php
2
use Illuminate\Support\Facades\Schema;
3
use Illuminate\Database\Schema\Blueprint;
4
use Illuminate\Database\Migrations\Migration;
5
6
class CreateJobsTable extends Migration
7
{
8
    /**

9
     * Run the migrations.

10
     *

11
     * @return void

12
     */
13
    public function up()
14
    {
15
        Schema::create('jobs', function (Blueprint $table) {
16
            $table->bigIncrements('id');
17
            $table->string('queue');
18
            $table->longText('payload');
19
            $table->unsignedTinyInteger('attempts');
20
            $table->unsignedInteger('reserved_at')->nullable();
21
            $table->unsignedInteger('available_at');
22
            $table->unsignedInteger('created_at');
23
24
            $table->index(['queue', 'reserved_at']);
25
        });
26
    }
27
28
    /**

29
     * Reverse the migrations.

30
     *

31
     * @return void

32
     */
33
    public function down()
34
    {
35
        Schema::dropIfExists('jobs');
36
    }
37
}

Ensuite, exécutons la commande migrate afin qu’elle crée réellement la table jobs dans une base de données.

1
php artisan migrate

C'est tout ce qui concerne la migration jobs .

Ensuite, créons le modèle Image qui sera utilisé pour gérer les images téléchargées par l'utilisateur final. Le modèle d'image nécessite également une table de base de données associée. Nous allons donc utiliser l'option --migrate lors de la création du modèle Image.

1
php artisan make:model Image --migration

La commande ci-dessus doit créer la classe de modèle Image et une migration de base de données associée.

La classe de modèles Image devrait ressembler à ceci:

1
<?php
2
// app/Image.php

3
namespace App;
4
5
use Illuminate\Database\Eloquent\Model;
6
7
class Image extends Model
8
{
9
    //

10
}

Et le fichier de migration de base de données doit être créé dans database/migrations/YYYY_MM_DD_HHMMSS_create_images_table.php. Nous souhaitons également stocker le chemin d'origine de l'image téléchargée par l'utilisateur final. Révisons le code du fichier de migration de la base de données Image comme suit.

1
<?php
2
// database/migrations/YYYY_MM_DD_HHMMSS_create_images_table.php

3
use Illuminate\Support\Facades\Schema;
4
use Illuminate\Database\Schema\Blueprint;
5
use Illuminate\Database\Migrations\Migration;
6
7
class CreateImagesTable extends Migration
8
{
9
    /**

10
     * Run the migrations.

11
     *

12
     * @return void

13
     */
14
    public function up()
15
    {
16
        Schema::create('images', function (Blueprint $table) {
17
            $table->increments('id');
18
            $table->timestamps();
19
            $table->string('org_path');
20
        });
21
    }
22
23
    /**

24
     * Reverse the migrations.

25
     *

26
     * @return void

27
     */
28
    public function down()
29
    {
30
        Schema::dropIfExists('images');
31
    }
32
}

Comme vous pouvez le constater, nous avons ajouté la colonne $table-> string ('org_path') pour stocker le chemin de l'image d'origine. Ensuite, il vous suffit d’exécuter la commande migrate pour créer cette table dans la base de données.

1
$php artisan migrate

Et c'est tout ce qui concerne le modèle Image.

Ensuite, créons un travail de file d'attente réel chargé du traitement des vignettes d'image. Pour le traitement des vignettes, nous allons utiliser une bibliothèque de traitement d’images très populaire, Intervention Image.

Pour installer la bibliothèque Image d'intervention, continuez et exécutez la commande suivante à la racine de votre application.

1
$php composer.phar require intervention/image

Maintenant, il est temps de créer la classe Job et nous utiliserons une commande artisanale pour le faire.

1
$php artisan make:job ProcessImageThumbnails

Cela devrait créer le modèle de classe Job dans app/Jobs/ProcessImageThumbnails.php. Remplaçons le contenu de ce fichier par le suivant.

1
<?php
2
// app/Jobs/ProcessImageThumbnails.php

3
namespace App\Jobs;
4
5
use App\Image as ImageModel;
6
use Illuminate\Bus\Queueable;
7
use Illuminate\Queue\SerializesModels;
8
use Illuminate\Queue\InteractsWithQueue;
9
use Illuminate\Contracts\Queue\ShouldQueue;
10
use Illuminate\Foundation\Bus\Dispatchable;
11
use Illuminate\Support\Facades\DB;
12
13
class ProcessImageThumbnails implements ShouldQueue
14
{
15
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
16
17
    protected $image;
18
19
    /**

20
     * Create a new job instance.

21
     *

22
     * @return void

23
     */
24
    public function __construct(ImageModel $image)
25
    {
26
        $this->image = $image;
27
    }
28
29
    /**

30
     * Execute the job.

31
     *

32
     * @return void

33
     */
34
    public function handle()
35
    {
36
        // access the model in the queue for processing

37
        $image = $this->image;
38
        $full_image_path = public_path($image->org_path);
39
        $resized_image_path = public_path('thumbs' . DIRECTORY_SEPARATOR .  $image->org_path);
40
41
        // create image thumbs from the original image

42
        $img = \Image::make($full_image_path)->resize(300, 200);
43
        $img->save($resized_image_path);
44
    }
45
}

Lorsque le programme de traitement de file d'attente commence à traiter un travail, il recherche la méthode handle . C'est donc la méthode du handle qui contient la logique principale de votre travail.

Dans notre cas, nous devons créer une vignette d'une image téléchargée par l'utilisateur. Le code de la méthode handle est assez simple: nous récupérons une image du modèle ImageModel et créons une vignette à l'aide de la bibliothèque Image d'intervention. Bien sûr, nous devons adopter le modèle Image correspondant lorsque nous répartissons notre travail, et nous le verrons dans un instant.

Pour tester notre travail nouvellement créé, nous allons créer un formulaire de téléchargement simple permettant à l'utilisateur de télécharger une image. Bien sûr, nous ne créerons pas de vignettes d’images tout de suite; nous allons reporter cette tâche pour qu'elle puisse être traitée par le travailleur de la file d'attente.

Créons un fichier de contrôleur dans app/Http/Controllers/ImageController.php comme indiqué ci-dessous.

1
<?php
2
namespace App\Http\Controllers;
3
4
use App\Image;
5
use App\Jobs\ProcessImageThumbnails;
6
use Illuminate\Http\Request;
7
use Illuminate\Support\Facades\Redirect;
8
use App\Http\Controllers\Controller;
9
use Validator;
10
11
class ImageController extends Controller
12
{
13
    /**

14
     * Show Upload Form

15
     *

16
     * @param  Request  $request

17
     * @return Response

18
     */
19
    public function index(Request $request)
20
    {
21
        return view('upload_form');
22
    }
23
24
    /**

25
     * Upload Image

26
     *

27
     * @param  Request  $request

28
     * @return Response

29
     */
30
    public function upload(Request $request)
31
    {
32
        // upload image

33
        $this->validate($request, [
34
          'demo_image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
35
        ]);
36
        $image = $request->file('demo_image');
37
        $input['demo_image'] = time().'.'.$image->getClientOriginalExtension();
38
        $destinationPath = public_path('/images');
39
        $image->move($destinationPath, $input['demo_image']);
40
41
        // make db entry of that image

42
        $image = new Image;
43
        $image->org_path = 'images' . DIRECTORY_SEPARATOR . $input['demo_image'];
44
        $image->save();
45
46
        // defer the processing of the image thumbnails

47
        ProcessImageThumbnails::dispatch($image);
48
49
        return Redirect::to('image/index')->with('message', 'Image uploaded successfully!');
50
    }
51
}

Créons un fichier de vue associé dans resources/views/upload_form.blade.php.

1
<!DOCTYPE html>
2
<html lang="{{ config('app.locale') }}">
3
    <head>
4
        <meta charset="utf-8">
5
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
        <meta name="viewport" content="width=device-width, initial-scale=1">
7
        <meta name="csrf-token" content="{{ csrf_token() }}" />
8
        <title>Laravel</title>
9
10
        <!-- Fonts -->
11
        <link href="https://fonts.googleapis.com/css?family=Raleway:100,600" rel="stylesheet" type="text/css">
12
13
        <!-- Styles -->
14
        <style>
15
            html, body {
16
                background-color: #fff;
17
                color: #636b6f;
18
                font-family: 'Raleway', sans-serif;
19
                font-weight: 100;
20
                height: 100vh;
21
                margin: 0;
22
            }
23
24
            .full-height {
25
                height: 100vh;
26
            }
27
28
            .flex-center {
29
                align-items: center;
30
                display: flex;
31
                justify-content: center;
32
            }
33
34
            .position-ref {
35
                position: relative;
36
            }
37
38
            .top-right {
39
                position: absolute;
40
                right: 10px;
41
                top: 18px;
42
            }
43
44
            .content {
45
                text-align: center;
46
            }
47
48
            .title {
49
                font-size: 84px;
50
            }
51
52
            .links > a {
53
                color: #636b6f;
54
                padding: 0 25px;
55
                font-size: 12px;
56
                font-weight: 600;
57
                letter-spacing: .1rem;
58
                text-decoration: none;
59
                text-transform: uppercase;
60
            }
61
62
            .m-b-md {
63
                margin-bottom: 30px;
64
            }
65
            
66
            .alert {
67
                color: red;
68
                font-weight: bold;
69
                margin: 10px;
70
            }
71
            .success {
72
                color: blue;
73
                font-weight: bold;
74
                margin: 10px;
75
            }
76
        </style>
77
    </head>
78
    <body>
79
        <div class="flex-center position-ref full-height">
80
            @if (Route::has('login'))
81
                <div class="top-right links">
82
                    @if (Auth::check())
83
                        <a href="{{ url('/home') }}">Home</a>
84
                    @else
85
                        <a href="{{ url('/login') }}">Login</a>
86
                        <a href="{{ url('/register') }}">Register</a>
87
                    @endif
88
                </div>
89
            @endif
90
91
            <div class="content">
92
                <div class="m-b-md">
93
                    <h1 class="title">Demo Upload Form</h1>
94
                    
95
                    @if ($errors->any())
96
                        <div class="alert alert-danger">
97
                            <ul>
98
                                @foreach ($errors->all() as $error)
99
                                    <li>{{ $error }}</li>
100
                                @endforeach
101
                            </ul>
102
                        </div>
103
                    @endif
104
                    
105
                    @if (session('message'))
106
                        <div class="success">
107
                            {{ session('message') }}
108
                        </div>
109
                    @endif
110
                    
111
                    <form method="post" action="{{ url('/image/upload') }}" enctype="multipart/form-data">
112
                      <div>
113
                        <input type="file" name="demo_image" />
114
                      </div>
115
                      <br/>
116
                      <div>
117
                        <input type="hidden" name="_token" value="{{ csrf_token() }}">
118
                        <input type="submit" value="Upload Image"/>
119
                      </div>
120
                    </form>
121
                </div>
122
            </div>
123
        </div>
124
    </body>
125
</html>

Enfin, ajoutons des itinéraires pour les actions index et upload dans le fichier routes/web.php.

1
Route::get('image/index', 'ImageController@index');
2
Route::post('image/upload', 'ImageController@upload');

Dans le contrôleur ImageController, la méthode d'index est utilisée pour rendre un formulaire de téléchargement.

1
public function index(Request $request)
2
{
3
    return view('upload_form');
4
}

Lorsque l'utilisateur soumet un formulaire, la méthode upload est appelée.

1
public function upload(Request $request)
2
{
3
    // upload image

4
    $this->validate($request, [
5
        'demo_image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
6
    ]);
7
    $image = $request->file('demo_image');
8
    $input['demo_image'] = time().'.'.$image->getClientOriginalExtension();
9
    $destinationPath = public_path('/images');
10
    $image->move($destinationPath, $input['demo_image']);
11
12
    // make db entry of that image

13
    $image = new Image;
14
    $image->org_path = 'images' . DIRECTORY_SEPARATOR . $input['demo_image'];
15
    $image->save();
16
17
    // defer the processing of the image thumbnails

18
    ProcessImageThumbnails::dispatch($image);
19
20
    return Redirect::to('image/index')->with('message', 'Image uploaded successfully!');
21
}

Au début de la méthode upload , vous remarquerez le code de téléchargement de fichier habituel qui déplace le fichier téléchargé vers le répertoire public/images. Ensuite, nous insérons un enregistrement de base de données en utilisant le modèle App/Image.

Enfin, nous utilisons le travail ProcessImageThumbnails pour différer la tâche de traitement des vignettes. Il est important de noter que c'est la méthode dispatch qui est utilisée pour différer une tâche. À la fin, l'utilisateur est redirigé vers la page de téléchargement avec un message de réussite.

À ce stade, le travail est ajouté à la table jobs pour traitement. Confirmons-le en émettant la requête suivante.

1
mysql> select * FROM lvl_jobs;
2
|  1 | default | {"displayName":"App\\Jobs\\ProcessImageThumbnails","job":"Illuminate\\Queue\\CallQueuedHandler@call","maxTries":null,"timeout":null,"data":{"commandName":"App\\Jobs\\ProcessImageThumbnails","command":"O:31:\"App\\Jobs\\ProcessImageThumbnails\":5:{s:8:\"\u0000*\u0000image\";O:45:\"Illuminate\\Contracts\\Database\\ModelIdentifier\":2:{s:5:\"class\";s:9:\"App\\Image\";s:2:\"id\";i:2;}s:6:\"\u0000*\u0000job\";N;s:10:\"connection\";N;s:5:\"queue\";N;s:5:\"delay\";N;}"}} |        0 |        NULL |   1510219099 | 1510219099 |

Vous devez vous demander, que faut-il pour traiter un travail alors? Ne vous inquiétez pas, c'est ce dont nous allons discuter dans la section suivante.

Travailleur en file d'attente

Le travail du gestionnaire de file d'attente Laravel consiste à traiter les travaux mis en file d'attente pour traitement. En fait, une commande artisanale nous aide à démarrer le processus de traitement de la file d'attente.

1
$php artisan queue:work

Dès que vous exécutez cette commande, les tâches en attente sont traitées. Dans notre cas, il convient de traiter le travail ProcessImageThumbnails mis en file d'attente lorsque l'utilisateur a téléchargé une image plus tôt.

1
$php artisan queue:work
2
[YYYY-MM-DD HHMMSS] Processing: App\Jobs\ProcessImageThumbnails
3
[YYYY-MM-DD HHMMSS] Processed:  App\Jobs\ProcessImageThumbnails

Vous auriez remarqué que lorsque vous démarrez un ouvrier de file d'attente, celui-ci continue à fonctionner jusqu'à ce que vous le supprimiez manuellement ou fermiez le terminal. En fait, il attend que le prochain travail soit traité. Dès qu'un nouveau travail est en file d'attente, il est immédiatement traité si le programme de traitement de file d'attente est en cours d'exécution.

Bien sûr, nous ne pouvons pas continuer à le faire de cette manière. Nous devons donc trouver un moyen de permettre au système de traitement des files d'attente de s'exécuter en permanence en arrière-plan.

À notre secours, il existe plusieurs outils de gestion de processus parmi lesquels vous pouvez choisir. Pour en nommer quelques-uns, voici une liste:

  • Circus
  • daemontools
  • Monit
  • Supervisor
  • Upstart

Vous devez choisir un outil avec lequel vous êtes à l'aise pour gérer le travailleur de la file d'attente Laravel. En gros, nous voulons nous assurer que le programme de traitement de file d'attente doit s'exécuter indéfiniment afin de pouvoir traiter immédiatement les travaux en file d'attente.

Voilà donc l'API de file d'attente à votre disposition. Vous pouvez l'utiliser dans votre développement quotidien pour différer des tâches fastidieuses afin d'améliorer l'expérience utilisateur.

Conclusion

Dans cet article, nous avons discuté de l’API de file d’attente dans Laravel, ce qui est très utile si vous souhaitez différer le traitement des tâches consommatrices de ressources.

Nous avons commencé par une introduction de base à l'API Queue, qui impliquait une discussion sur les connexions, les files d'attente et les travaux. Dans la seconde moitié de l'article, nous avons créé un travail de file d'attente personnalisé qui montrait comment utiliser l'API de file d'attente dans le monde réel.

Pour ceux d'entre vous qui débutent avec Laravel ou qui souhaitent élargir leurs connaissances, leur site ou leur application avec des extensions, nous pouvons étudier de nombreuses choses sur le marché Envato.

N'hésitez pas à utiliser le formulaire de commentaires ci-dessous pour poster vos questions et suggestions.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.