Losque l'on crée une application il y a parfois des traitements longs à effectuer. Malheureusement, la nature "synchrone" de PHP, fait que ces opérations vont bloquer le process. La mise en place d'un système de file d'attente va permettre de déléguer une partie des traitements à un processus séparé et ainsi d'améliorer les performances de l'application.
Par exemple lorsqu'un utilisateur upload un avatar on a besoin de générer plusieurs format. On pourrait être tenté de mettre le code dans notre controller.
public function store (Request $request) {
$file = $request->file('avatar')->move('uploads', Auth::user()->id . '.jpg');
$avatar = new Avatar($file->getRealPath());
$avatar->generateThumbs(); // Redimensionne l'image en X formats
return view('success');
}
Créer une tâche
Le problème est alors que l'utilisateur doit attendre que le serveur effectue les redimensionnements avant d'obtenir sa réponse. On va donc créer un "Job" pour effectuer notre traitement.
php artisan make:job ImageResize
Cette commande va permettre de générer une classe qui contiendra 2 méthodes :
- Le constructeur, dans lequel on injectera les objets nécessaires au déroulement de la tâche
- Une méthode
handle()
qui contiendra le code à effectuer lors du traitement de cette tâche
<?php
namespace App\Jobs;
use App\Podcast;
use App\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class ImageResize implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
protected $image;
/**
* On crée une nouvelle instance.
*
* @param Image $image
* @return void
*/
public function __construct(Image $image)
{
$this->image = $image;
}
/**
* Exécute la tache.
*
* @return void
*/
public function handle()
{
$this->image->generateThumbs();
}
}
Le trait SerializesModels
n'est pas utile ici mais permet de serialiser les modèles Eloquent
injectés en ne stockant que l'id. Le modèle sera de nouveau récupéré lors de l'éxécution de la tâche afin d'éviter les problèmes de serialisation. De la même manière vous ne pouvez pas stocker de données binaires (comme des fichiers) et il faudra les convertir en base64. En résumé il faudra essayer d'injecter des informations simples au niveau du constructeur afin d'éviter au maxium les problèmes.
Exécuter la tâche
Maintenant que notre tâche est créée on peut demander son placement en file d'attente gràce à la fonction globale dispatch()
. Cette fonction prend en paramètre une instance de notre Job.
// On remplace notre $avatar->generateThumbs();
dispatch(new ImageResize($image));
Par défaut, ce code va éxécuter notre tâche directement. Il va falloir spécifier le système de file d'attente à utiliser en éditant la configuration queue.php
. Laravel supporte les systèmes suivants :
beanstalkd
, Beanstalksqs
Amazon SQSredis
, Redisdatabase
, Base de donnéessync
, Synchrone (les tâches sont éxécuté de manière synchrone)
Ces systèmes vont permettre de gérer la file d'attente et de stocker les tâche à effectuer. Pour traiter les tâches il faut utiliser la commandes
php artisan queue:work --tries=5 --timeout=30
Cette commande prend d'autres arguments que vous pouvez consulter en utilisant --help
.
Que se passe-t-il en cas d'échec ?
Si une tâche venait à ne pas fonctionner comme attendue elle sera alors automatiquement placée dans une table failed_jobs
dans votre base de données. Pour préparer cette table vous pouvez créer la migration à l'aide de la commande.
php artisan queue:failed-table
php artisan migrate
Vous pouvez lister les tâches qui ont échouées en utilisant cette table ou y accéder depuis la commande artisan
php artisan queue:failed
Vous pourrez ensuite choisir de replacer les tâches dans la fille d'attente
php artisan queue:retry <ID DE LA TACHE>
# Pour tout relancer
php artisan queue:retry all
ou les supprimer
php artisan queue:forget <ID DE LA TACHE>
# Pour tout supprimer
php artisan queue:flush
Enfin, si c'est votre processus qui crash il vous faudra le redémarrer. Vous pouvez le faire de manière automatique à l'aide d'un supervisor.
D'ailleurs, lorsque vous déployez une nouvelle version de votre application, il faudra penser à redémarrer votre process.
php artisan queue:restart
Options supplémentaires
Une tâche possède des méthodes supplémentaires pour mieux contrôler son déroulement.
delay()
Permet de spécifier dans combien de temps la tâche devra être traitée
dispatch((new ImageResize($image))->delay(Carbon::now()->addSeconds(15));
onQueue()
Permet d'envoyer la tâche dans une file d'attente particulière
dispatch((new ImageResize($image))->onQueue('urgent'));
Cela permet de distribuer les tâches ou d'établir un système de priorité :
php artisan queue:work --queue=urgent,default