Au cours de la dernière décennie, nous avons développé des applications pour les entreprises Fortune 500 et les entreprises de 500 utilisateurs ou moins. Historiquement, nos ingénieurs ont principalement utilisé PHP pour développer le backend. Mais il y a deux ans, des problèmes sont survenus et ont gravement affecté non seulement les performances de nos produits, mais également leur évolutivité. Nous avons donc introduit Golang (Go) dans notre pile technologique.
Presque simultanément, nous avons découvert que Go nous permettait non seulement de créer des applications plus volumineuses, mais également d'améliorer les performances jusqu'à 40 fois. Grâce à lui, nous sommes en mesure d'étendre les produits existants écrits en PHP et de les améliorer en combinant le meilleur des deux langages.
Nous vous expliquerons, grâce à une vaste expérience de Go et PHP, comment l'utiliser pour résoudre de vrais problèmes de développement et comment nous pouvons le transformer en un outil pour éliminer certains des problèmes associés au PHP Death Model.
Avant d'expliquer comment Go améliore le modèle de mort PHP, comprenons d'abord l'environnement de développement PHP conventionnel.
Habituellement, les applications fonctionnent sur nginx et PHP-FPM. nginx gère les requêtes statiques, tandis que les requêtes dynamiques sont redirigées vers PHP-FPM, qui exécute le code PHP. Peut-être que vous utilisez Apache et mod_php, mais ils ont le même principe et seulement de légères différences dans leur fonctionnement.
Découvrez comment PHP-FPM exécute le code. Lorsqu'une requête est reçue, PHP-FPM initialise le sous-processus PHP et lui transmet les détails de la requête dans le cadre de son statut (_GET, _POST, _SERVER, etc.).
Lors de l'exécution d'un script PHP, l'état ne peut pas être modifié, il n'y a donc qu'une seule façon d'obtenir un nouvel ensemble de données d'entrée : effacer la mémoire du processus et la réinitialiser.
Ce modèle performant présente de nombreux avantages. Vous n'avez pas besoin de trop vous soucier de la consommation de mémoire, tous les processus sont complètement isolés, si l'un des processus "meurt", il sera automatiquement recréé et n'affectera pas les autres processus. Cependant, cette approche présente des inconvénients lorsque vous essayez de faire évoluer votre application.
Si vous développez professionnellement en PHP, vous savez par où commencer lors de la création d'un nouveau projet : choisir un framework. Il s'agit d'une bibliothèque pour l'injection de dépendances, l'ORM, les transformations et les méthodes de modèles. Bien entendu, toutes les données saisies par l'utilisateur peuvent être facilement placées dans un seul objet (Symfony / HttpFoundation ou PSR-7). Ces cadres sont super !
Mais tout a son prix. Dans n'importe quel framework d'entreprise, pour traiter une simple requête utilisateur ou accéder à une base de données, vous devez charger au moins quelques dizaines de fichiers, créer de nombreuses classes et analyser plusieurs configurations. Mais le pire, c'est qu'une fois chaque tâche terminée, vous devez tout réinitialiser et redémarrer : tout le code que vous venez de démarrer deviendra inutile, et avec son aide, vous ne pourrez plus traiter une autre demande. Dites cela à n'importe quel programmeur écrivant dans d'autres langages - et vous verrez la confusion sur son visage.
Depuis des années, les ingénieurs PHP cherchent des moyens de résoudre ce problème, en utilisant la technologie de chargement paresseux, les micro-frames, les bibliothèques d'optimisation, la mise en cache, etc. Mais finalement, vous devrez quand même abandonner toute l'application et recommencer *(Note du traducteur : avec l'émergence du préchargement dans PHP7.4, ce problème sera partiellement résolu)
Vous pouvez écrire des scripts PHP qui durent plus de quelques minutes (jusqu'à quelques heures ou jours) : par exemple des tâches Cron, des analyseurs CSV, des gestionnaires de files d'attente. Toutes ces tâches suivent un modèle : ils obtiennent une tâche, la traitent, puis obtiennent la tâche suivante. Le code réside en mémoire, ce qui évite les opérations supplémentaires de chargement des frameworks et des applications, ce qui permet de gagner un temps précieux.
Mais développer des scripts de longue durée n’est pas si simple. Toute erreur tuera le processus, un débordement de mémoire provoquera un crash et F5 ne pourra pas être utilisé pour déboguer le programme.
Les choses se sont améliorées depuis PHP 7 : un garbage collector fiable est apparu, il est devenu plus facile de gérer les erreurs et les extensions du noyau peuvent éviter les fuites de mémoire. Oui, les ingénieurs doivent encore traiter avec soin la question de la mémoire et de la mémorisation de l'état dans le code (quel langage permet de ne pas prêter attention à ces choses ?) Bien sûr, en PHP 7, il n'y a pas beaucoup de surprises.
Est-il possible d'adopter un modèle de scripts PHP résidents pour des tâches plus triviales comme la gestion des requêtes HTTP, éliminant ainsi le besoin de tout télécharger à partir de zéro pour chaque requête ?
Pour résoudre ce problème, vous devez d'abord implémenter une application serveur capable de recevoir des requêtes HTTP et de les rediriger une par une vers le travailleur PHP au lieu de la tuer à chaque fois.
Nous savons que nous pouvons écrire des serveurs web en PHP pur (PHP-PM) ou avec des extensions C (Swoole). Bien que chaque approche ait ses avantages, aucune des deux options n’a fonctionné pour nous – je voulais quelque chose de plus. Nous avions besoin de plus qu'un simple serveur Web : nous voulions une solution qui nous permettrait d'éviter les problèmes liés aux « redémarrages » en PHP, tout en étant facilement adaptable et extensible pour des applications spécifiques. Autrement dit, nous avons besoin d'un serveur d'applications.
Go peut-il aider à résoudre ce problème ? Nous savons que c'est possible car le langage compile l'application en un seul binaire ; il est multiplateforme ; utilise son propre modèle de traitement parallèle (concurrence) et ses propres bibliothèques pour gérer HTTP et enfin, nous pouvons intégrer davantage de bibliothèques Open source dans notre système. programmes.
Tout d'abord, il est nécessaire de déterminer comment deux ou plusieurs applications peuvent communiquer entre elles.
Par exemple, en utilisant la bibliothèque go-php d'Alex Palaestras, le partage de mémoire entre les processus PHP et Go (tels que mod_php dans Apache) peut être réalisé. Mais la fonctionnalité de cette bibliothèque limite notre utilisation pour résoudre des problèmes.
Nous avons décidé d'utiliser une autre approche plus courante : structurer l'interaction entre les processus en utilisant des sockets/pipelines. Cette approche a prouvé sa fiabilité au cours de la dernière décennie et est bien optimisée au niveau du système d'exploitation.
Tout d'abord, nous avons créé un protocole binaire simple pour échanger des données entre les processus et gérer les erreurs de transmission. Dans sa forme la plus simple, ce type de protocole ressemble à un netstring avec un en-tête de paquet de taille fixe (17 octets dans notre exemple) qui contient des informations sur le type de paquet. Sa taille et les informations de son masque binaire sont utilisées pour vérifier les données. intégrité.
Côté PHP, nous avons utilisé la fonction pack, et côté Go, nous avons utilisé la bibliothèque encoding/binary.
Un protocole est un peu obsolète pour nous et nous avons ajouté la possibilité d'appeler les services net /rpc Go directement depuis PHP. Cette fonctionnalité nous a beaucoup aidé dans les développements ultérieurs car nous avons pu facilement intégrer les bibliothèques Go dans les applications PHP. Les résultats de ce travail peuvent être vus dans un autre de nos produits open source Goridge .
Après la mise en œuvre du mécanisme d'interaction, nous avons commencé à réfléchir à la manière de mieux transférer les tâches vers le processus PHP. Lorsqu'une tâche arrive, le serveur d'applications doit sélectionner un travailleur inactif pour l'exécuter. Si le processus de travail se termine par une erreur ou « meurt », nous l'effaçons et en créons un nouveau. Si le processus de travail s'exécute avec succès, nous le renvoyons au pool de travailleurs où il peut être utilisé pour effectuer des tâches.
Pour stocker le pool de processus de travail actifs, nous utilisons un canal tampon Pour effacer de manière inattendue les processus de travail « morts » du pool, nous avons ajouté un mécanisme pour suivre les erreurs et l'état des processus de travail.
Enfin, nous disposons d'un serveur PHP fonctionnel capable de gérer toute requête rendue sous forme binaire.
Pour que notre application commence à fonctionner en tant que serveur Web, nous devons choisir un standard PHP fiable pour gérer toutes les requêtes HTTP entrantes. Dans notre cas, nous convertissons simplement une simple requête net /http du format Go au format PSR-7 afin qu'elle soit compatible avec la plupart des frameworks PHP disponibles aujourd'hui.
Étant donné que PSR-7 est considéré comme immuable (certains diraient techniquement non), les développeurs doivent écrire des applications qui, en principe, ne traitent pas les requêtes comme des entités globales. Ceci est entièrement cohérent avec le concept de processus résidents PHP. Notre implémentation finale (pas encore reçue de nom) ressemble à ceci :
Notre première tâche de test était un backend API sur lequel, périodiquement, des requêtes Burst imprévisibles (plus fréquentes que d'habitude). Bien que les capacités de nginx soient suffisantes dans la plupart des cas, nous rencontrons souvent des erreurs 502 en raison de l'incapacité d'équilibrer rapidement le système face aux augmentations de charge attendues.
Pour résoudre ce problème, nous avons déployé notre premier serveur d'applications PHP/Go début 2018. Et j’ai obtenu des résultats étonnants immédiatement ! Non seulement nous avons complètement éliminé les erreurs 502, mais nous avons également réduit le nombre de serveurs des deux tiers, économisant ainsi beaucoup d'argent et résolvant un casse-tête pour les ingénieurs et les chefs de produit.
Au milieu de l'année, nous avons amélioré notre solution et l'avons publiée sur GitHub sous licence MIT sous le nom RoadRunner, soulignant ainsi son incroyable rapidité et son efficacité. Comment
RoadRunner nous permet d'utiliser le middleware net/http côté Go, même de faire la validation JWT avant que la requête ne passe en PHP, ainsi que de gérer WebSocket et globals dans le statut d’agrégation Prometheus.
Grâce au RPC intégré, vous pouvez ouvrir l'API de n'importe quelle bibliothèque Go en PHP sans écrire de package d'extension. De plus, avec RoadRunner, vous pouvez déployer de nouveaux serveurs différents du HTTP. Les exemples incluent l'exécution de processeurs AWS Lambda en PHP, la création de puissants sélecteurs de file d'attente et même l'ajout de gRPC à nos applications.
En utilisant à la fois PHP et Go, la solution a été progressivement améliorée, améliorant les performances des applications de 40 fois dans certains tests, améliorant les outils de débogage, mettant en œuvre l'intégration avec le framework Symfony et ajoutant la prise en charge de HTTPS, HTTP /2 et. Prise en charge du PSR-17.
Certaines personnes sont encore liées par le concept dépassé de PHP, pensant que PHP est un langage lent et encombrant adapté uniquement à l'écriture de plugins sous WordPress. Ces gens vont même jusqu'à dire que PHP a une limite : lorsque l'application devient suffisamment volumineuse, il faut choisir un langage plus « mature » et réécrire la base de code accumulée au fil des années.
Ma réponse à ces questions est : détrompez-vous. Nous pensons que c'est vous seul qui avez fixé certaines limites à PHP. Vous pouvez passer votre vie à passer d'une langue à une autre, en essayant de trouver celle qui répond parfaitement à vos besoins, ou vous pouvez traiter les langues comme des outils. Avec un langage comme PHP, ses supposés défauts peuvent être la véritable raison de son succès. Si vous le combinez avec un autre langage comme Go, vous créez un produit plus puissant que la simple utilisation d’un seul langage.
Après avoir utilisé Go et PHP de manière interchangeable, nous pouvons dire que nous les aimons. Nous n’allons pas sacrifier l’un pour l’autre ; nous allons plutôt trouver des moyens de tirer le meilleur parti de cette double architecture.
Adresse originale en anglais : https://sudonull.com/post/6470-RoadRunner-PHP-is-not-created-to-die-or-Golang-to-the-rescue
Adresse de traduction : https : //learnku.com/php/t/61733
Apprentissage recommandé : "Tutoriel vidéo PHP"
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!