Création d'un service Pub/Sub en interne à l'aide de Node.js et Redis
Publié: 2022-03-10Le monde d'aujourd'hui fonctionne en temps réel. Qu'il s'agisse d'échanger des actions ou de commander de la nourriture, les consommateurs s'attendent aujourd'hui à des résultats immédiats. De même, nous nous attendons tous à savoir des choses immédiatement, que ce soit dans l'actualité ou le sport. Zéro, en d'autres termes, est le nouveau héros.
Cela s'applique également aux développeurs de logiciels - sans doute parmi les personnes les plus impatientes ! Avant de plonger dans l'histoire de BrowserStack, ce serait négligent de ma part de ne pas fournir quelques informations sur Pub/Sub. Pour ceux d'entre vous qui connaissent les bases, n'hésitez pas à sauter les deux paragraphes suivants.
De nombreuses applications reposent aujourd'hui sur le transfert de données en temps réel. Regardons de plus près un exemple : les réseaux sociaux. Les goûts de Facebook et Twitter génèrent des flux pertinents , et vous (via leur application) les consommez et espionnez vos amis. Ils accomplissent cela avec une fonction de messagerie, dans laquelle si un utilisateur génère des données, elles seront publiées pour que d'autres les consomment en un clin d'œil. Tous les retards importants et les utilisateurs se plaindront, l'utilisation diminuera et, si elle persiste, se détachera. Les enjeux sont importants, tout comme les attentes des utilisateurs. Alors, comment des services comme WhatsApp, Facebook, TD Ameritrade, Wall Street Journal et GrubHub prennent-ils en charge de gros volumes de transferts de données en temps réel ?
Tous utilisent une architecture logicielle similaire à haut niveau appelée modèle "Publish-Subscribe", communément appelé Pub/Sub.
"Dans l'architecture logicielle, la publication-abonnement est un modèle de messagerie où les expéditeurs de messages, appelés éditeurs, ne programment pas les messages à envoyer directement à des récepteurs spécifiques, appelés abonnés, mais classent plutôt les messages publiés dans des classes sans savoir quels abonnés, si n'importe lequel, il peut y en avoir. De même, les abonnés expriment leur intérêt pour une ou plusieurs classes et ne reçoivent que les messages qui les intéressent, sans savoir quels éditeurs, le cas échéant, il y a.“
- Wikipédia
Lassé par la définition ? Revenons à notre histoire.
Chez BrowserStack, tous nos produits prennent en charge (d'une manière ou d'une autre) les logiciels avec un important composant de dépendance en temps réel - qu'il s'agisse de journaux de tests automatisés, de captures d'écran de navigateur fraîchement préparées ou de streaming mobile à 15 ips.
Dans de tels cas, si un seul message tombe, un client peut perdre des informations vitales pour prévenir un bogue . Par conséquent, nous devions nous adapter aux exigences de taille de données variées. Par exemple, avec les services d'enregistrement de périphérique à un moment donné, il peut y avoir 50 Mo de données générées sous un seul message. Des tailles comme celle-ci pourraient planter le navigateur. Sans oublier que le système de BrowserStack devrait évoluer pour des produits supplémentaires à l'avenir.
Comme la taille des données de chaque message varie de quelques octets à 100 Mo, nous avions besoin d'une solution évolutive capable de prendre en charge une multitude de scénarios. En d'autres termes, nous cherchions une épée capable de couper tous les gâteaux. Dans cet article, je vais discuter du pourquoi, du comment et des résultats de la création de notre service Pub/Sub en interne.
À travers le prisme du problème du monde réel de BrowserStack, vous obtiendrez une compréhension plus approfondie des exigences et du processus de création de votre propre Pub/Sub .
Notre besoin d'un service Pub/Sub
BrowserStack contient environ 100 millions de messages, dont chacun se situe entre environ 2 octets et plus de 100 Mo. Ceux-ci sont transmis à tout moment dans le monde entier, le tout à des vitesses Internet différentes.
Les plus grands générateurs de ces messages, par taille de message, sont nos produits BrowserStack Automate. Les deux ont des tableaux de bord en temps réel affichant toutes les demandes et réponses pour chaque commande d'un test utilisateur. Ainsi, si quelqu'un exécute un test avec 100 requêtes où la taille moyenne de la requête-réponse est de 10 octets, cela transmet 1 × 100 × 10 = 1000 octets.
Considérons maintenant la situation dans son ensemble car, bien sûr, nous n'exécutons pas un seul test par jour. Plus d'environ 850 000 tests BrowserStack Automate et App Automate sont exécutés avec BrowserStack chaque jour. Et oui, nous avons en moyenne environ 235 demandes-réponses par test. Étant donné que les utilisateurs peuvent prendre des captures d'écran ou demander des sources de page dans Selenium, notre taille moyenne de requête-réponse est d'environ 220 octets.
Donc, revenons à notre calculatrice :
850 000 × 235 × 220 = 43 945 000 000 octets (environ) ou seulement 43,945 Go par jour
Parlons maintenant de BrowserStack Live et App Live. Nous avons sûrement Automate comme gagnant en termes de taille de données. Cependant, les produits Live prennent les devants en ce qui concerne le nombre de messages transmis. Pour chaque test en direct, environ 20 messages sont transmis à chaque minute. Nous effectuons environ 100 000 tests en direct, chaque test d'une durée moyenne d'environ 12 minutes, ce qui signifie :
100 000×12×20 = 24 000 000 messages par jour
Passons maintenant à la partie impressionnante et remarquable : nous créons, exécutons et maintenons l'application pour ce pousseur appelé avec 6 instances t1.micro d'ec2. Le coût de fonctionnement du service ? Environ 70 $ par mois .
Choisir de construire plutôt que d'acheter
Tout d'abord : en tant que startup, comme la plupart des autres, nous avons toujours été ravis de construire des choses en interne. Mais nous avons quand même évalué quelques services là-bas. Les principales exigences que nous avions étaient :
- Fiabilité et stabilité,
- Hautes performances, et
- Rentabilité.
Laissons de côté les critères de rentabilité, car je ne vois aucun service externe coûtant moins de 70 $ par mois (tweetez-moi si vous en connaissez un !). Notre réponse est donc évidente.
En termes de fiabilité et de stabilité, nous avons trouvé des entreprises qui fournissaient Pub/Sub en tant que service avec un SLA de disponibilité supérieur à 99,9 %, mais de nombreuses conditions générales étaient associées. Le problème n'est pas aussi simple que vous le pensez, surtout si l'on considère les vastes terres de l'Internet ouvert qui se situent entre le système et le client. Toute personne familiarisée avec l'infrastructure Internet sait qu'une connectivité stable est le plus grand défi. De plus, la quantité de données envoyées dépend du trafic. Par exemple, un canal de données qui est à zéro pendant une minute peut éclater pendant la suivante. Les services offrant une fiabilité adéquate lors de tels moments de rafale sont rares (Google et Amazon).
Pour notre projet, les performances signifient obtenir et envoyer des données à tous les nœuds d'écoute avec une latence proche de zéro . Chez BrowserStack, nous utilisons des services cloud (AWS) ainsi qu'un hébergement en colocation. Cependant, nos éditeurs et/ou abonnés pourraient être placés n'importe où. Par exemple, cela peut impliquer un serveur d'application AWS générant des données de journal indispensables ou des terminaux (machines sur lesquelles les utilisateurs peuvent se connecter en toute sécurité pour effectuer des tests). Pour en revenir au problème d'Internet ouvert, si nous devions réduire nos risques, nous devions nous assurer que notre Pub/Sub exploitait les meilleurs services d'hébergement et AWS.
Une autre exigence essentielle était la capacité de transmettre tous les types de données (octets, texte, données multimédia étranges, etc.). Tout bien considéré, cela n'avait aucun sens de s'appuyer sur une solution tierce pour prendre en charge nos produits. A notre tour, nous avons décidé de relancer notre esprit startup en retroussant nos manches pour coder notre propre solution.

Construire notre solution
Pub/Sub signifie qu'il y aura un éditeur, générant et envoyant des données, et un abonné qui les accepte et les traite. Ceci est similaire à une radio : une chaîne de radio diffuse (publie) du contenu partout dans une plage. En tant qu'abonné, vous pouvez décider de syntoniser cette chaîne et d'écouter (ou d'éteindre complètement votre radio).
Contrairement à l'analogie radio où les données sont gratuites pour tous et n'importe qui peut décider de se connecter, dans notre scénario numérique, nous avons besoin d'une authentification, ce qui signifie que les données générées par l'éditeur ne peuvent être que pour un seul client ou abonné particulier.

Ci-dessus, un diagramme donnant un exemple d'un bon Pub/Sub avec :
- Éditeurs
Ici, nous avons deux éditeurs générant des messages basés sur une logique prédéfinie. Dans notre analogie avec la radio, ce sont nos jockeys radio qui créent le contenu. - Les sujets
Il y en a deux ici, ce qui signifie qu'il y a deux types de données. Nous pouvons dire que ce sont nos canaux radio 1 et 2. - Les abonnés
Nous en avons trois qui lisent chacun des données sur un sujet particulier. Une chose à noter est que l'Abonné 2 lit à partir de plusieurs sujets. Dans notre analogie avec la radio, ce sont les personnes qui sont à l'écoute d'un canal radio.
Commençons à comprendre les exigences nécessaires pour le service.
- Un composant événementiel
Cela ne se déclenche que lorsqu'il y a quelque chose à faire. - Stockage transitoire
Cela conserve les données persistantes pendant une courte durée, donc si l'abonné est lent, il a encore une fenêtre pour les consommer. - Réduction de la latence
Connecter deux entités sur un réseau avec un minimum de sauts et de distance.
Nous avons choisi une pile technologique qui répondait aux exigences ci-dessus :
- Node.js
Parce que pourquoi pas ? Eventuellement, nous n'aurions pas besoin d'un traitement de données lourd, et il est facile à intégrer. - Redis
Prend parfaitement en charge les données de courte durée. Il a toutes les capacités pour lancer, mettre à jour et expirer automatiquement. Cela réduit également la charge de l'application.
Node.js pour la connectivité de la logique métier
Node.js est un langage presque parfait lorsqu'il s'agit d'écrire du code incorporant des E/S et des événements. Notre problème particulier avait les deux, faisant de cette option la plus pratique pour nos besoins.
D'autres langages tels que Java pourraient sûrement être plus optimisés, ou un langage comme Python offre une évolutivité. Cependant, le coût de démarrage avec ces langages est si élevé qu'un développeur pourrait finir d'écrire du code dans Node dans la même durée.
Pour être honnête, si le service avait la possibilité d'ajouter des fonctionnalités plus compliquées, nous aurions pu envisager d'autres langues ou une pile complète. Mais ici, c'est un mariage fait au paradis. Voici notre package.json :
{ "name": "Pusher", "version": "1.0.0", "dependencies": { "bstack-analytics": "*****", // Hidden for BrowserStack reasons. :) "ioredis": "^2.5.0", "socket.io": "^1.4.4" }, "devDependencies": {}, "scripts": { "start": "node server.js" } }
En termes simples, nous croyons au minimalisme, en particulier lorsqu'il s'agit d'écrire du code. D'un autre côté, nous aurions pu utiliser des bibliothèques comme Express pour écrire du code extensible pour ce projet. Cependant, nos instincts de startup ont décidé de transmettre cela et de le conserver pour le prochain projet. Outils supplémentaires que nous avons utilisés :
- ioredis
C'est l'une des bibliothèques les plus prises en charge pour la connectivité Redis avec Node.js utilisée par des entreprises telles qu'Alibaba. - socket.io
La meilleure bibliothèque pour une connectivité et une solution de secours élégantes avec WebSocket et HTTP.
Redis pour le stockage transitoire
Les échelles Redis en tant que service sont extrêmement fiables et configurables. De plus, il existe de nombreux fournisseurs de services gérés fiables pour Redis, y compris AWS. Même si vous ne souhaitez pas utiliser de fournisseur, Redis est facile à utiliser.
Décomposons la partie configurable. Nous avons commencé avec la configuration maître-esclave habituelle, mais Redis est également livré avec des modes cluster ou sentinelle. Chaque mode a ses propres avantages.
Si nous pouvions partager les données d'une manière ou d'une autre, un cluster Redis serait le meilleur choix. Mais si nous partageons les données par n'importe quelle heuristique, nous avons moins de flexibilité car l'heuristique doit être suivie à travers . Moins de règles, plus de contrôle, c'est bon pour la vie !
Redis Sentinel fonctionne mieux pour nous car la recherche de données se fait dans un seul nœud, se connectant à un moment donné alors que les données ne sont pas partagées. Cela signifie également que même si plusieurs nœuds sont perdus, les données sont toujours distribuées et présentes dans d'autres nœuds. Vous avez donc plus de HA et moins de risques de perte. Bien sûr, cela a empêché les avantages d'avoir un cluster, mais notre cas d'utilisation est différent.
Architecture à 30000 pieds
Le diagramme ci-dessous fournit une image de très haut niveau du fonctionnement de nos tableaux de bord Automate et App Automate. Vous souvenez-vous du système en temps réel que nous avions de la section précédente ?

Dans notre diagramme, notre flux de travail principal est mis en évidence avec des bordures plus épaisses. La section « automatiser » comprend :
- Terminaux
Composé des versions vierges de Windows, OSX, Android ou iOS que vous obtenez lors des tests sur BrowserStack. - Moyeu
Le point de contact pour tous vos tests Selenium et Appium avec BrowserStack.
La section "service utilisateur" ici est notre gardien, garantissant que les données sont envoyées et enregistrées pour la bonne personne. C'est aussi notre gardien de la sécurité. La section "poussoir" intègre le cœur de ce dont nous avons discuté dans cet article. Il se compose des suspects habituels dont :
- Redis
Notre stockage transitoire pour les messages, où dans notre cas les journaux automatisés sont temporairement stockés. - Éditeur
Il s'agit essentiellement de l'entité qui obtient les données du hub. Toutes vos réponses à la demande sont capturées par ce composant qui écrit sur Redis avecsession_id
comme canal. - Abonné
Cela lit les données de Redis générées pour lesession_id
. C'est également le serveur Web permettant aux clients de se connecter via WebSocket (ou HTTP) pour obtenir des données, puis de les envoyer à des clients authentifiés.
Enfin, nous avons la section du navigateur de l'utilisateur, représentant une connexion WebSocket authentifiée pour garantir l'envoi des journaux session_id
. Cela permet au JS frontal de l'analyser et de l'embellir pour les utilisateurs.
Semblable au service de journaux, nous avons ici un pusher qui est utilisé pour d'autres intégrations de produits. Au lieu de session_id
, nous utilisons une autre forme d'ID pour représenter ce canal. Tout cela fonctionne à partir d'un poussoir !
Conclusion (TLDR)
Nous avons eu un succès considérable dans la création de Pub/Sub. Pour résumer pourquoi nous l'avons construit en interne :
- S'adapte mieux à nos besoins ;
- Moins cher que les services externalisés ;
- Contrôle total de l'architecture globale.
Sans oublier que JS est la solution idéale pour ce type de scénario. Une boucle d'événements et une quantité massive d'E/S sont ce dont le problème a besoin ! JavaScript est la magie d'un pseudo thread unique.
Les événements et Redis en tant que système simplifient les choses pour les développeurs, car vous pouvez obtenir des données d'une source et les transmettre à une autre via Redis. Nous l'avons donc construit.
Si l'utilisation correspond à votre système, je vous recommande de faire de même !