Fermer

juin 25, 2018

Utilisation du traitement en arrière-plan pour accélérer les temps de chargement des pages –


Cet article fait partie d'une série de sur la construction d'un exemple d'application – un blog de galerie multi-images – pour l'analyse comparative des performances et les optimisations. (Voir le repo ici.)


Dans un article précédent, nous avons ajouté le redimensionnement d'image à la demande . Les images sont redimensionnées à la première demande et mises en cache pour une utilisation ultérieure. En faisant cela, nous avons ajouté quelques frais généraux à la première charge; le système doit rendre les vignettes à la volée et «bloque» le rendu de la page du premier utilisateur jusqu'à ce que le rendu de l'image soit effectué

L'approche optimisée consisterait à rendre les vignettes après la création d'une galerie. Vous pensez peut-être: «D'accord, mais nous bloquerons alors l'utilisateur qui crée la galerie?» Non seulement ce serait une mauvaise expérience utilisateur, mais ce n'est pas une solution évolutive. L'utilisateur serait confus au sujet des longs temps de chargement ou, pire encore, rencontrer des délais d'attente et / ou des erreurs si les images sont trop lourdes pour être traitées. La meilleure solution consiste à placer ces tâches lourdes en arrière-plan.

Travaux d'arrière-plan

Les travaux d'arrière-plan constituent la meilleure façon de traiter les tâches lourdes. Nous pouvons immédiatement informer notre utilisateur que nous avons reçu sa demande et l'avons programmé pour traitement. De la même manière que YouTube avec les vidéos mises en ligne: elles ne sont pas accessibles après le téléchargement. L'utilisateur doit attendre que la vidéo soit complètement traitée pour la prévisualiser ou la partager.

Le traitement ou la génération de fichiers, l'envoi de courriels ou d'autres tâches non critiques doivent être effectués en arrière-plan.

L'approche de traitement en arrière-plan comporte deux composants clés: la file d'attente et le (s) travailleur (s). L'application crée des tâches qui doivent être gérées pendant que les travailleurs attendent et prennent la file d'attente un travail à la fois.

 Emplois d'arrière-plan

Vous pouvez créer plusieurs instances de travail pour accélérer le traitement , hacher un gros travail en morceaux plus petits et les traiter simultanément. C'est à vous de voir comment vous voulez organiser et gérer le traitement en arrière-plan, mais notez que le traitement parallèle n'est pas une tâche triviale: vous devez prendre soin des conditions de concurrence potentielles et gérer les tâches échouées avec élégance.

utilisez la file d'attente de travaux Beanstalkd pour stocker des tâches, le composant Symfony Console pour implémenter des workers comme commandes de console et Supervisor pour prendre soin des processus de travail.

Si vous utilisez Homestead Improved Beanstalkd et Supervisor sont déjà installés, vous pouvez donc ignorer les instructions d'installation ci-dessous.

Installation de Beanstalkd

Beanstalkd est

une file d'attente rapide avec une interface générique conçue pour réduire le latence des pages vues dans les applications Web à haut volume en exécutant des tâches chronophages de manière asynchrone.

Vous pouvez utiliser de nombreuses bibliothèques clientes disponibles . Dans notre projet, nous utilisons Pheanstalk .

Pour installer Beanstalkd sur votre serveur Ubuntu ou Debian, lancez simplement sudo apt-get install beanstalkd . Jetez un coup d'œil à la page de téléchargement officielle pour apprendre à installer Beanstalkd sur d'autres systèmes d'exploitation.

Une fois installé, Beanstalkd est démarré en tant que démon, attendant que les clients se connectent et créent (ou traitent)

 /etc/init.d/beanstalkd
Utilisation: /etc/init.d/beanstalkd {start | stop | force-stop | redémarrage | force-recharge | statut}

Installer Pheanstalk comme dépendance en exécutant composer nécessite pda / pheanstalk .

La file d'attente sera utilisée à la fois pour créer et récupérer des jobs, donc nous allons centraliser la création de file dans un service d'usine. ] JobQueueFactory :

 hôte, $ this-> port);
    }
}

Maintenant, nous pouvons injecter le service d'usine partout où nous devons interagir avec les files d'attente Beanstalkd. Nous définissons le nom de la file d'attente comme une constante et nous y référons lorsque nous mettons le travail dans la file d'attente ou surveillons la file d'attente des travailleurs.

Installation du superviseur

Selon la page officielle Supervisor est un

système client / serveur qui permet à ses utilisateurs de surveiller et de contrôler un certain nombre de processus sur des systèmes d'exploitation de type UNIX

Nous allons l'utiliser pour démarrer, redémarrer, mettre à l'échelle et surveiller les processus de travail. Superviseur sur votre serveur Ubuntu / Debian en exécutant
sudo apt-get install supervisor . Une fois installé, Supervisor sera exécuté en arrière-plan en tant que démon. Utilisez supervisorctl pour contrôler les processus de supervision:

 $ sudo supervisorctl help

commandes par défaut (type help ):
===============================
ajouter sortie ouvrir recharger redémarrer démarrer queue
avail fg pid supprimer la mise à jour de l'état d'arrêt
effacer le message principal quitter quitter le signal arrêter la version

Pour contrôler les processus avec Supervisor, nous devons d'abord écrire un fichier de configuration et décrire comment nous voulons que nos processus soient contrôlés. Les configurations sont stockées dans /etc/supervisor/conf.d/ . Une configuration simple de superviseur pour les travailleurs de redimensionnement ressemblerait à ceci:

 [program:resize-worker]
nom_processus =% (nom_programme) s _% (no_processus) 02d
command = php PATH-À-VOTRE-APP / bin / console app: resize-image-worker
autostart = true
autorestart = true
numprocs = 5
stderr_logfile = PATH-À-VOTRE-APP / var / log / resize-travailleur-stderr.log
stdout_logfile = PATH-A-VOTRE-APP / var / log / resize-travailleur-stdout.log

Nous indiquons à Supervisor comment nommer les processus engendrés, le chemin d'accès à la commande à exécuter, pour démarrer et redémarrer automatiquement les processus, le nombre de processus que nous voulons avoir et l'endroit où enregistrer les résultats. En savoir plus sur les configurations de superviseur ici .

Redimensionner les images en arrière-plan

Une fois que notre infrastructure est installée (Beanstalkd et Supervisor installés), nous pouvons modifier notre application pour redimensionner les images dans le arrière-plan après la création de la galerie. Pour ce faire, nous devons:

  • mettre à jour la logique de diffusion d'images dans le ImageController
  • implémenter le redimensionnement des opérateurs en tant que commandes console
  • créer la configuration du superviseur pour nos employés
  • mettre à jour les images

Mise à jour de la logique de traitement d'images

Jusqu'à présent, nous avons redimensionné les images à la première requête: si le fichier image d'une taille demandée n'existe pas, il est créé à la volée.

modifiez ImageController pour renvoyer les réponses d'image pour la taille demandée uniquement si le fichier image redimensionné existe (c'est-à-dire uniquement si l'image a déjà été redimensionnée).

Sinon, l'application renvoie une image générique réponse disant que l'image est en cours de redimensionnement pour le moment. Notez que la réponse d'image d'espace réservé a différents en-têtes de contrôle de cache, puisque nous ne voulons pas mettre en cache les images d'espace réservé; nous voulons que l'image soit rendue dès que le processus de redimensionnement est terminé.

Nous allons créer un événement simple appelé GalleryCreatedEvent avec l'ID Gallery comme charge utile. Cet événement sera distribué dans le UploadController après la création réussie de la galerie:

 ...

$ this-> em-> persist ($ gallery);
$ this-> em-> flush ();

$ this-> eventDispatcher-> dispatch (
    GalleryCreatedEvent :: classe,
    new GalleryCreatedEvent ($ gallery-> getId ())
)

$ this-> flashBag-> add ('succès', 'Galerie créée! Les images sont en cours de traitement.');
...

En outre, nous mettrons à jour le message flash avec "Les images sont en cours de traitement." afin que l'utilisateur sache que nous avons encore du travail à faire sur ses images avant qu'elles ne soient prêtes.

Nous allons créer un abonné à l'événement GalleryEventSubscriber qui réagira au GalleryCreatedEvent et demander un travail de redimensionnement pour chaque image de la galerie nouvellement créée:

 public function onGalleryCreated (GalleryCreatedEvent $ event)
{
    $ queue = $ this-> jobQueueFactory
        -> createQueue ()
        -> useTube (JobQueueFactory :: QUEUE_IMAGE_RESIZE);

    $ gallery = $ this-> entityManager
        -> getRepository (Galerie :: classe)
        -> find ($ event-> getGalleryId ());

    if (vide ($ gallery)) {
        revenir;
    }

    / ** @var Image $ image * /
    foreach ($ gallery-> getImages () as $ image) {
        $ queue-> put ($ image-> getId ());
    }
}

Maintenant, lorsqu'un utilisateur crée avec succès une galerie, l'application affichera la page de la galerie, mais certaines images ne seront pas affichées comme leurs vignettes alors qu'elles ne sont pas encore prêtes:

 Page de la galerie

Une fois l'opération de redimensionnement terminée, l'actualisation suivante doit afficher la page Galerie complète

Implémenter les éléments de redimensionnement comme commandes de console

L'opérateur est un processus simple effectuant le même travail pour chaque tâche qu'il reçoit . L'exécution du travail est bloquée à l'appel $ queue-> reserve () jusqu'à ce que l'un ou l'autre travail soit réservé à ce travailleur ou qu'un délai expire

Un seul travailleur peut prendre et traiter un Job. Le travail contient généralement une charge utile – par exemple, une chaîne ou un tableau / objet sérialisé. Dans notre cas, il s'agira d'une UUID d'une galerie créée.

Un simple worker ressemble à ceci:

 // Construire une file d'attente Pheanstalk et définir la file d'attente à surveiller.
$ queue = $ this-> getContainer ()
    -> get (JobQueueFactory :: classe)
    -> createQueue ()
    -> watch (JobQueueFactory :: QUEUE_IMAGE_RESIZE);

// Bloquer l'exécution de ce code jusqu'à ce que le travail soit ajouté à la file d'attente
// L'argument optionnel est timeout en secondes
$ job = $ queue-> reserve (60 * 5);

// Au timeout
if (false === $ job) {
    $ this-> output-> writeln ('Expiré');

    revenir;
}

essayez {
    // Faites le travail ici, mais assurez-vous d'attraper des exceptions
    // et enterrer le travail afin qu'il ne retourne pas à la file d'attente
    $ this-> resizeImage ($ job-> getData ());

    // Supprimer un travail de la file d'attente le marquera comme traité
    $ queue-> delete ($ job);
} catch ( Exception $ e) {
    $ queue-> enter ($ job);
    lancer $ e;
}

Vous avez peut-être remarqué que les employés vont quitter après un délai défini ou lorsqu'un travail est traité. Nous pourrions enrouler la logique de travail dans une boucle infinie et lui demander de répéter son travail indéfiniment, mais cela pourrait causer des problèmes tels que des délais de connexion à la base de données après une longue période d'inactivité et rendre les déploiements plus difficiles. Pour éviter cela, notre cycle de vie des travailleurs sera terminé après l'achèvement d'une seule tâche. Le superviseur redémarre alors un worker en tant que nouveau processus.

Jetez un coup d'oeil à ResizeImageWorkerCommand pour obtenir une image claire de la structure de la commande Worker. Le travailleur implémenté de cette manière peut également être démarré manuellement en tant que commande de console Symfony: ./ bin / console app: resize-image-worker .

Créer une configuration de superviseur

Nous voulons que nos travailleurs démarrer automatiquement, nous allons donc définir une directive autostart = true dans la configuration.
Comme le worker doit être redémarré après un timeout ou une tâche de traitement réussie, nous allons également mettre un ] autorestart = true directive.

La meilleure partie du traitement en arrière-plan est la facilité de traitement en parallèle. Nous pouvons définir une directive numprocs = 5 et Supervisor va générer cinq instances de nos travailleurs. Ils attendent des emplois et les traitent de manière indépendante, ce qui nous permet de faire évoluer notre système facilement. Au fur et à mesure que votre système se développe, vous devrez probablement augmenter le nombre de processus. Comme plusieurs processus sont en cours d'exécution, nous devons définir la structure d'un nom de processus. Nous définissons donc une directive nom_processus =% (nom_programme) s _% (numéro_processus) 02d . mais non des moindres, nous voulons stocker les résultats des travailleurs afin que nous puissions les analyser et les déboguer en cas de problème. Nous allons définir les chemins stderr_logfile et stdout_logfile .

La configuration complète de Supervisor pour nos redimensionneurs ressemble à ceci:

 [program:resize-worker]
nom_processus =% (nom_programme) s _% (no_processus) 02d
command = php PATH-À-VOTRE-APP / bin / console app: resize-image-worker
autostart = true
autorestart = true
numprocs = 5
stderr_logfile = PATH-À-VOTRE-APP / var / log / resize-travailleur-stderr.log
stdout_logfile = PATH-A-VOTRE-APP / var / log / resize-travailleur-stdout.log

Après avoir créé (ou mis à jour) le fichier de configuration situé dans le répertoire /etc/supervisor/conf.d/ vous devez indiquer à Supervisor de relire et de mettre à jour sa configuration en exécutant les commandes suivantes:

 supervorctl relu
supervisorctl mise à jour

Si vous utilisez Homestead Improved (et vous devriez l'être!) Vous pouvez utiliser scripts / setup-supervisor.sh pour générer la configuration du superviseur pour ce projet: sudo ./scripts/setup-supervisor.sh.

Mises à jour de mise à jour

Les vignettes d'images ne seront plus rendues sur la première requête, nous devons donc demander explicitement le rendu pour chaque image lorsque nous sommes chargement de nos appareils dans la classe de fixture LoadGalleriesData :

 $ imageResizer = $ this-> container-> get (ImageResizer :: class);
$ fileManager = $ this-> container-> get (FileManager :: class);

...

$ gallery-> addImage ($ image);
$ manager-> persist ($ image);

$ fullPath = $ fileManager-> getFilePath ($ image-> getFilename ());
if (false === vide ($ fullPath)) {
    foreach ($ imageResizer-> getSupportedWidths () en $ largeur) {
        $ imageResizer-> getResizedPath ($ fullPath, $ largeur, true);
    }
}

Maintenant, vous devriez sentir comment le chargement des projecteurs est ralenti, et c'est pourquoi nous l'avons déplacé en arrière-plan au lieu de forcer nos utilisateurs à attendre que cela soit fait!

Trucs et astuces

l'arrière-plan de sorte que même après avoir déployé une nouvelle version de votre application, les employés obsolètes seront exécutés jusqu'à ce qu'ils ne soient pas redémarrés pour la première fois.

Dans notre cas, nous devrons attendre que tous nos employés terminer leurs tâches ou le temps d'attente (5 minutes) jusqu'à ce que nous sommes sûrs que tous nos travailleurs sont mis à jour. Soyez conscient de cela lors de la création de procédures de déploiement!




Source link