Les tâches PHP asynchrones résolvent les goulots d'étranglement typiques lorsque les tâches cron provoquent des pics de charge, des durées d'exécution longues et un manque de transparence. Je vais vous montrer comment. PHP asynchrone avec des files d'attente et des travailleurs, allège les requêtes Web, adapte les charges de travail et amortit les pannes sans frustration.
Points centraux
Pour commencer, je vais résumer les idées principales sur lesquelles je m'appuie dans cet article et que j'applique quotidiennement dans la pratique. Principes de base
- Découplage de la requête et de la tâche : la requête Web reste rapide, les tâches s'exécutent en arrière-plan.
- Mise à l'échelle À propos des pools de travailleurs : plus d'instances, moins de temps d'attente.
- Fiabilité par Retries : relancer les tâches qui ont échoué.
- Transparence Par surveillance : longueur de la file d'attente, durées d'exécution, taux d'erreurs en un coup d'œil.
- Séparation selon les charges de travail : courte, par défaut, longue avec des limites appropriées.
Pourquoi les tâches cron ne suffisent plus
Une tâche cron démarre strictement à l'heure, et non après un véritable événement. Dès que les utilisateurs déclenchent quelque chose, je veux réagir immédiatement, au lieu d'attendre la minute suivante. De nombreuses exécutions cron simultanées génèrent un pic de charge qui surcharge temporairement la base de données, le CPU et les E/S. Le parallélisme reste limité et il m'est difficile de représenter des priorités fines. Avec les files d'attente, je place immédiatement les tâches dans une file d'attente, je laisse plusieurs travailleurs travailler en parallèle et je maintiens l'interface web continue. réactif. Les utilisateurs de WordPress bénéficient d'avantages supplémentaires s'ils Comprendre WP-Cron et souhaite configurer proprement afin que les planifications temporisées soient transférées de manière fiable dans la file d'attente.
Traitement asynchrone : explication succincte du principe « Job–Queue–Worker »
Je regroupe les tâches coûteuses dans un Emploi, qui décrit ce qu'il faut faire, y compris les références de données. Cette tâche est placée dans une file d'attente que j'utilise comme tampon contre les pics de charge et qui dessert plusieurs consommateurs. Un worker est un processus permanent qui lit les tâches dans la file d'attente, les exécute et confirme le résultat. Si un worker tombe en panne, la tâche reste dans la file d'attente et peut être traitée ultérieurement par une autre instance. Ce couplage lâche rend l'application dans son ensemble tolérant aux erreurs et garantit des temps de réponse cohérents dans le frontend.
Comment fonctionnent les files d'attente et les workers dans l'environnement PHP
En PHP, je définis une tâche comme une classe simple ou comme une classe sérialisable. charge utile avec Handler. La file d'attente peut être une table de base de données, Redis, RabbitMQ, SQS ou Kafka, en fonction de la taille et des exigences en matière de latence. Les processus de travail s'exécutent de manière autonome, souvent sous forme de services de supervision, de système ou de conteneur, et récupèrent les tâches en continu. J'utilise des mécanismes ACK/NACK pour signaler clairement les traitements réussis et ceux qui ont échoué. Il reste important que je Débit le travailleur s'adapte au volume de travail prévu, sinon la file d'attente ne cesse de s'allonger.
Les PHP Workers dans les environnements d'hébergement : l'équilibre plutôt que le goulot d'étranglement
Trop peu de PHP Workers génèrent des retards, trop nombreux, ils sollicitent le CPU et la RAM et ralentissent tout, y compris Requêtes Web. Je planifie séparément le nombre de travailleurs et la concurrence par file d'attente afin que les tâches courtes ne restent pas bloquées dans les rapports longs. Je définis également des limites de mémoire et des redémarrages réguliers afin d'intercepter les fuites. Si vous n'êtes pas sûr des limites, des cœurs de processeur et de la concurrence, lisez mon bref Guide sur les workers PHP avec des stratégies d'équilibre typiques. Cet équilibre crée finalement la nécessaire Planification pour une croissance et des temps de réponse réguliers.
Délais d'attente, nouvelles tentatives et idempotence : garantir un traitement fiable
J'attribue une note à chaque travail Délai d'attente, afin qu'aucun travailleur ne reste indéfiniment bloqué sur des tâches défectueuses. Le courtier reçoit un délai d'expiration de visibilité légèrement supérieur à la durée maximale de la tâche afin qu'une tâche n'apparaisse pas deux fois par erreur. Comme de nombreux systèmes utilisent une livraison „ au moins une fois “, j'implémente des gestionnaires idempotents : les appels en double n'entraînent pas de doublons dans les e-mails ou les paiements. Je poursuis les tentatives avec un backoff afin de ne pas surcharger les API externes. Je maintiens ainsi la Taux d'erreur faible et peut diagnostiquer les problèmes avec précision.
Séparer les charges de travail : courte, par défaut et longue
Je crée des files d'attente distinctes pour les tâches courtes, moyennes et longues afin qu'une exportation ne bloque pas dix notifications et que la Utilisateur attend. Chaque file d'attente dispose de ses propres pools de travailleurs avec des limites appropriées en termes de durée d'exécution, de concurrence et de mémoire. Les tâches courtes bénéficient d'un parallélisme plus élevé et de délais d'attente stricts, tandis que les processus longs bénéficient de plus de CPU et de durées d'exécution plus longues. Je contrôle les priorités en répartissant les travailleurs entre les files d'attente. Cette séparation claire garantit une prévisibilité Latence dans l'ensemble du système.
Comparaison des options de file d'attente : quel système choisir dans quel cas ?
Je choisis délibérément la file d'attente en fonction de la latence, de la persistance, du fonctionnement et de la trajectoire de croissance, afin de ne pas avoir à effectuer une migration coûteuse par la suite et de pouvoir Mise à l'échelle reste sous contrôle.
| système de file d'attente | Utilisation | Latence | Caractéristiques |
|---|---|---|---|
| Base de données (MySQL/PostgreSQL) | Petites configurations, démarrage facile | Moyens | Manipulation simple, mais rapide goulot de bouteille en cas de charge élevée |
| Redis | Charge faible à moyenne | Faible | Très rapide dans la RAM, nécessite une mémoire claire Configuration pour la fiabilité |
| RabbitMQ / Amazon SQS / Kafka | Grands systèmes distribués | Faible à moyen | Fonctionnalités étendues, bonne Mise à l'échelle, plus de frais d'exploitation |
Utiliser Redis correctement – éviter les écueils courants
Redis semble ultra-rapide, mais des paramètres incorrects ou des structures de données inadaptées peuvent entraîner des comportements étranges. Temps d'attente. Je fais attention aux stratégies AOF/RDB, à la latence du réseau, aux charges utiles trop importantes et aux commandes bloquantes. Je sépare également la mise en cache et les charges de travail en file d'attente afin que les pics de cache ne ralentissent pas la récupération des tâches. Pour obtenir une liste de contrôle concise des erreurs de configuration, consultez ce guide sur Erreurs de configuration Redis. Une configuration correcte garantit une réponse rapide et fiable. file d'attente pour de nombreuses applications.
Surveillance et mise à l'échelle dans la pratique
Je mesure la longueur de la file d'attente au fil du temps, car l'augmentation retards signalent un manque de ressources de travail. La durée moyenne des tâches aide à définir des délais d'attente réalistes et à planifier les capacités. Les taux d'erreur et le nombre de tentatives me montrent quand les dépendances externes ou les chemins de code sont instables. Dans les conteneurs, je fais automatiquement évoluer les ressources de travail en fonction des métriques du processeur et de la file d'attente, tandis que les configurations plus petites peuvent se contenter de scripts simples. La visibilité reste cruciale, car seuls les chiffres permettent de prendre des décisions éclairées. Décisions permettent.
Cron plus Queue : une répartition claire des rôles plutôt qu'une concurrence
J'utilise Cron comme horloge qui planifie les tâches en fonction du temps, tandis que les travailleurs effectuent le véritable Travail . Cela évite les pics de charge importants à chaque minute et permet de réagir immédiatement aux événements spontanés avec des tâches mises en file d'attente. Je planifie les rapports collectifs récurrents via Cron, mais chaque détail du rapport est traité par un worker. Pour les configurations WordPress, je respecte les directives telles que celles décrites dans „Comprendre WP-Cron“, afin que la planification reste cohérente. Cela me permet de respecter le calendrier et de m'assurer Flexibilité dans l'exécution.
Environnements d'exécution PHP modernes : RoadRunner et FrankenPHP en interaction avec les files d'attente
Les processus persistants des travailleurs réduisent les frais généraux de démarrage, maintiennent les connexions ouvertes et réduisent les Latence. RoadRunner et FrankenPHP misent sur des processus durables, des pools de travailleurs et une mémoire partagée, ce qui augmente considérablement l'efficacité sous charge. En combinaison avec des files d'attente, je maintiens un débit régulier et profite de ressources réutilisées. Je sépare souvent la gestion HTTP et les consommateurs de files d'attente dans des pools distincts afin que le trafic web et les tâches en arrière-plan ne se gênent pas mutuellement. Travailler de cette manière permet de créer un environnement calme. Performance même en cas de forte variation de la demande.
Sécurité : traiter les données avec parcimonie et les crypter
Je ne place jamais de données personnelles directement dans la charge utile, mais uniquement des identifiants que je recharge ultérieurement afin de Protection des données Toutes les connexions au courtier sont cryptées et j'utilise le cryptage au repos, si le service le propose. Les producteurs et les consommateurs reçoivent des autorisations distinctes avec des droits minimaux. Je fais régulièrement tourner les données d'accès et je garde les secrets hors des journaux et des métriques. Cette approche réduit la surface d'attaque et protège les Confidentialité informations sensibles.
Scénarios d'utilisation pratiques pour Async-PHP
Je n'envoie plus d'e-mails dans Webrequest, mais je les classe comme tâches afin que les utilisateurs n'aient pas à attendre le expédition Attendre. Pour le traitement des médias, je télécharge les images, je donne immédiatement une réponse et je génère les vignettes plus tard, ce qui rend l'expérience de téléchargement nettement plus fluide. Je lance les rapports contenant de nombreux enregistrements de manière asynchrone et je mets les résultats à disposition en téléchargement dès que le travailleur a terminé. Pour les intégrations avec les systèmes de paiement, de CRM ou de marketing, je découple les appels API afin d'amortir sereinement les délais d'attente et les pannes sporadiques. Je déplace le préchauffage du cache et les mises à jour de l'index de recherche en arrière-plan afin que le UI reste rapide.
Conception des tâches et flux de données : charges utiles, gestion des versions et clés d'idempotence
Je garde les charges utiles aussi légères que possible et ne stocke que des références : une ID, un type, une version et une clé de corrélation ou d'idempotence. J'utilise une version pour identifier le schéma de charge utile et je peux ainsi continuer à développer des gestionnaires en toute tranquillité, tandis que les anciennes tâches continuent d'être traitées correctement. Une clé d'idempotence empêche les effets secondaires doubles : elle est enregistrée dans la mémoire de données au démarrage et comparée en cas de répétitions afin d'éviter la création d'un deuxième e-mail ou d'une deuxième réservation. Pour les tâches complexes, je décompose les tâches en petites étapes clairement définies (commandes) au lieu de regrouper des flux de travail entiers dans une seule tâche, afin de faciliter les réessais et le traitement des erreurs. ciblé saisir.
Pour les mises à jour, j'utilise le Modèle de boîte d'envoi: les modifications sont enregistrées dans une table de boîte d'envoi au cours d'une transaction de base de données, puis publiées dans la file d'attente réelle par un travailleur. Cela permet d'éviter les incohérences entre les données de l'application et les tâches envoyées et d'obtenir une „au moins une fois“ Livraison avec des effets secondaires clairement définis.
Erreurs, DLQ et „ poison messages “
Toutes les erreurs ne sont pas transitoires. Je fais clairement la distinction entre les problèmes qui peuvent être résolus par Retries résoudre (réseau, limites de débit) et erreurs finales (données manquantes, validations). Pour ces dernières, je mets en place une File d'attente des lettres mortes (DLQ) : après un nombre limité de tentatives, la tâche aboutit là. Dans la DLQ, j'enregistre la raison, l'extrait de la trace de la pile, le nombre de tentatives et un lien vers les entités pertinentes. Je peux ainsi prendre une décision ciblée : relancer manuellement, corriger les données ou réparer le gestionnaire. Je reconnais les „ messages toxiques “ (tâches qui plantent de manière reproductible) à leur échec immédiat et je les bloque rapidement afin qu'ils ne ralentissent pas l'ensemble du pool.
Arrêt en douceur, déploiements et redémarrages progressifs
Lors du déploiement, je m'en tiens à Arrêt progressif: Le processus traite les tâches en cours jusqu'à leur terme, mais n'accepte plus de nouvelles tâches. Pour ce faire, j'intercepte SIGTERM, je définis un statut „ draining “ et, si nécessaire, je prolonge la visibilité (Visibility Timeout) afin que le courtier n'attribue pas la tâche à un autre travailleur. Dans les configurations de conteneurs, je planifie une période de grâce de terminaison généreuse, adaptée à la durée maximale du travail. Je réduis les redémarrages progressifs à de petits lots afin que le Capacité ne tombe pas en panne. De plus, je mets en place des Heartbeats/Healthchecks qui garantissent que seuls les travailleurs en bonne santé effectuent des tâches.
Batching, limites de débit et contre-pression
Je regroupe plusieurs petites opérations lorsque cela s'avère judicieux. lots Ensemble : un worker charge 100 identifiants, les traite en une seule fois et réduit ainsi la surcharge due à la latence du réseau et à l'établissement de la connexion. Pour les API externes, je respecte les limites de débit et contrôle les taux d'interrogation. Si le taux d'erreurs augmente ou si les latences s'allongent, le Worker réduit automatiquement le parallélisme (concurrence adaptative) jusqu'à ce que la situation se stabilise. La contre-pression signifie que les producteurs réduisent leur production lorsque la longueur de la file d'attente dépasse certains seuils, ce qui permet d'éviter les avalanches qui submergent le système.
Priorités, équité et séparation des clients
Je ne détermine pas les priorités uniquement à l'aide de files d'attente prioritaires individuelles, mais aussi à l'aide de pondéré Répartition des travailleurs : un pool fonctionne à 70% „ court “, à 20% „ par défaut “ et à 10% „ long “, afin qu'aucune catégorie ne soit complètement laissée pour compte. Dans les configurations multi-locataires, j'isole les locataires critiques avec leurs propres files d'attente ou des pools de travailleurs dédiés afin de Voisins bruyants Pour les rapports, j'évite les priorités rigides qui repoussent indéfiniment les tâches longues ; à la place, je planifie des créneaux horaires (par exemple la nuit) et limite le nombre de tâches lourdes parallèles afin que la plateforme puisse fonctionner pendant la journée. vif reste.
Observabilité : journaux structurés, corrélation et SLO
Je consigne les informations de manière structurée : ID de tâche, ID de corrélation, durée, statut, nombre de tentatives et paramètres importants. Cela me permet de corréler la requête frontale, la tâche mise en file d'attente et l'historique des tâches. À partir de ces données, je définis SLOs: environ 95% de tous les travaux „ courts “ en 2 secondes, „ par défaut “ en 30 secondes, „ longs “ en 10 minutes. Des alertes se déclenchent en cas d'augmentation du backlog, de taux d'erreur croissants, de durées d'exécution inhabituelles ou lorsque les DLQ augmentent. Les runbooks décrivent des étapes concrètes : mettre à l'échelle, réduire, redémarrer, analyser les DLQ. Ce n'est qu'avec des métriques claires que je peux prendre les bonnes décisions. décisions relatives aux capacités.
Développement et tests : locaux, reproductibles, fiables
Pour le développement local, j'utilise une Fausse file d'attente ou une instance réelle en mode Dev et lancez Worker en avant-plan afin que les journaux soient immédiatement visibles. J'écris des tests d'intégration qui mettent en file d'attente une tâche, exécutent Worker et vérifient le résultat attendu (par exemple, une modification de la base de données). Je simule des tests de charge avec des tâches générées et mesure le débit, les percentiles 95/99 et les taux d'erreur. Il est important de disposer d'un ensemencement reproductible des données et de gestionnaires déterministes afin que les tests restent stables. Les fuites de mémoire sont détectées lors des tests d'endurance ; je planifie des redémarrages périodiques et surveille les courbe de stockage.
Gestion des ressources : CPU vs E/S, mémoire et parallélisme
Je fais la distinction entre les tâches gourmandes en CPU et celles gourmandes en E/S. Je limite clairement le parallélisme des tâches gourmandes en CPU (par exemple, les transformations d'images) et je réserve des cœurs. Les tâches gourmandes en E/S (réseau, base de données) bénéficient d'une plus grande concurrence, tant que la latence et les erreurs restent stables. Pour PHP, je mise sur opcache, je veille à ce que les connexions soient réutilisables (connexions persistantes) dans les workers persistants et je libère explicitement les objets à la fin d'une tâche afin de Fragmentation . Une limite stricte par tâche (mémoire/durée d'exécution) empêche les valeurs aberrantes d'affecter l'ensemble du pool.
Migration progressive : du cronjob à l'approche « queue-first »
Je procède à une migration progressive : je commence par transférer les tâches non critiques liées aux e-mails et aux notifications dans la file d'attente. Viennent ensuite le traitement des médias et les appels d'intégration, qui provoquent souvent des délais d'attente. Les tâches cron existantes restent synchronisées, mais leur travail est transféré dans la file d'attente. À l'étape suivante, je sépare les charges de travail en courtes/par défaut/longues et je les mesure de manière cohérente. Enfin, je supprime la logique cron lourde dès que les travailleurs fonctionnent de manière stable et je passe à piloté par des événements Points d'enqueueage (par exemple „ utilisateur enregistré “ → „ envoyer un e-mail de bienvenue “). Cela réduit les risques et permet à l'équipe et à l'infrastructure de s'adapter de manière contrôlée au nouveau modèle.
Gouvernance et exploitation : politiques, quotas et contrôle des coûts
Je définis des politiques claires : taille maximale de la charge utile, durée d'exécution autorisée, cibles externes autorisées, quotas par client et créneaux horaires pour les tâches coûteuses. Je contrôle les coûts en ajustant les pools de travailleurs pendant la nuit, en regroupant les tâches par lots pendant les heures creuses et en fixant des limites pour les services cloud qui Fugueurs prévenir. En cas d'incident, je dispose d'une procédure d'escalade : alerte DLQ → analyse → correctif ou correction des données → reprocessus contrôlé. Grâce à cette discipline, le système reste maîtrisable, même s'il prend de l'ampleur.
Conclusion : du cronjob à l'architecture asynchrone évolutive
Je résous les problèmes de performances en dissociant les tâches lentes de la réponse Web et en les exécutant via Travailleur traite. Les files d'attente tamponnent la charge, hiérarchisent les tâches et mettent de l'ordre dans les réessais et les messages d'erreur. Grâce à des charges de travail séparées, des délais d'expiration clairs et des gestionnaires idempotents, le système reste prévisible. Je décide de l'hébergement, des limites des travailleurs et du choix du courtier sur la base de mesures réelles, et non de mon intuition. Ceux qui misent tôt sur cette architecture obtiennent des réponses plus rapides, de meilleurs Mise à l'échelle et nettement plus de sérénité dans les activités quotidiennes.


