Comment nous avons vu le rendu du serveur et ce qui en est ressorti

Bonjour à tous! Au cours de l'année, nous sommes passés à React et avons réfléchi à la façon de nous assurer que nos utilisateurs n'attendaient pas la modélisation du client, mais voyaient la page le plus rapidement possible. À cette fin, nous avons décidé de faire du rendu côté serveur (SSR - Server Side Rendering) et d'optimiser le référencement, car tous les moteurs de recherche ne sont pas en mesure d'exécuter JS, et ceux qui sont capables de passer du temps sur l'exécution, et le temps d'exploration de chaque site est limité.



Permettez-moi de vous rappeler que le rendu du serveur est l'exécution de code JavaScript côté serveur afin de donner au client du HTML prêt. Cela affecte les performances perçues par l'utilisateur, en particulier sur les machines plus lentes et sur Internet lent. Il n'est pas nécessaire d'attendre que JS soit téléchargé, analysé et exécuté. Le navigateur ne peut rendre HTML immédiatement, sans attendre JSa, l'utilisateur peut déjà lire le contenu.
Ainsi, la phase d'attente passive est réduite. Après le rendu, le navigateur n'aura qu'à parcourir le DOM fini, vérifier qu'il correspond à ce qui a été rendu
sur le client et ajoutez des écouteurs d'événements. Ce processus est appelé hydratation . Si dans le processus d'hydratation il y a une différence entre le contenu du serveur et celui généré par le navigateur, nous recevrons un avertissement dans la console et un rendu supplémentaire sur le client. Cela ne devrait pas être le cas, il est nécessaire de s'assurer que les résultats du rendu du serveur et du client correspondent. S'ils divergent, cela devrait être traité comme un bogue, car cela annule les avantages du rendu du serveur. Si un élément doit diverger, ajoutez-lui suppressHydrationWarning={true} .


De plus, il y a une mise en garde: il n'y a pas de window sur le serveur. Le code qui y accède doit être exécuté dans des méthodes de cycle de vie qui ne sont pas appelées côté serveur. Autrement dit, vous ne pouvez pas utiliser la window dans UNSAFE_componentWillMount () ou, dans le cas des crochets, uselayouteffect .


En fait, le processus de rendu côté serveur se résume à obtenir l'initialState du backend, à l'exécuter via renderToString() , à récupérer l'initialState et le HTML finis à la sortie et à les envoyer au client.


Dans hh.ru, les randonnées à partir du client JS ne sont autorisées que dans la passerelle api en python. C'est pour la sécurité et l'équilibrage de charge. Python va déjà aux backends nécessaires pour les données, les prépare et les donne au navigateur. Node.js est utilisé uniquement pour le rendu du serveur. En conséquence, après avoir préparé les données, le python a besoin d'un trajet supplémentaire vers le nœud, en attendant le résultat et en transmettant la réponse au client.


Vous devez d'abord choisir un serveur pour travailler avec HTTP. Nous nous sommes arrêtés à koa . J'ai aimé la syntaxe moderne avec await . La modularité est un middleware léger qui, si nécessaire, est installé séparément ou facilement écrit indépendamment. Le serveur lui-même est léger et rapide . Oui, et écrit par koa par la même équipe de développement qu'ils écrivent express, leur expérience captive.


Après avoir appris à déployer notre service, nous avons écrit le code le plus simple sur KOA, qui a pu en donner 200, et l'avons téléchargé sur le prod. Cela ressemblait à ceci:


 const Koa = require('koa'); const app = new Koa(); const SERVER_PORT = 9400; app.use(async (ctx) => { ctx.body = 'Hello World'; }); app.listen(SERVER_PORT); 

Dans hh.ru, tous les services vivent dans des conteneurs dockers . Avant la première version, vous devez écrire des playbooks ansibles , à l'aide desquels le service se déploie dans des environnements de production et sur des bancs d'essai. Chaque développeur et testeur a son propre environnement de test, qui ressemble le plus à prod. Nous avons passé la plupart de notre temps et de notre énergie à écrire des livres de lecture. Cela s'est produit parce que deux rendus frontaux l'ont fait, et c'est le premier service sur un nœud dans hh.ru. Nous devions trouver comment faire passer le service en mode développement, le faire en parallèle avec le service pour lequel le rendu a lieu. Remettez des fichiers dans un conteneur. Lancez un serveur nu pour que le conteneur Docker démarre sans attendre la génération. Générez et reconstruisez le serveur avec le service qui l'utilise. Déterminez la quantité de RAM dont nous avons besoin.


En mode développement, ils prévoyaient la possibilité d'une reconstruction automatique et d'un redémarrage ultérieur du service lors de la modification des fichiers inclus dans la version finale. Le nœud doit redémarrer pour charger le code exécutable. Webpack surveille les modifications et les builds . Webpack est nécessaire pour convertir ESM en CommonJS commun. Pour redémarrer, ils ont pris nodemon , qui s'occupe des fichiers collectés.


Ensuite, nous avons appris le serveur de routage. Pour un équilibrage correct, vous devez savoir quelles instances de serveur sont actives. Pour vérifier cela, le rythme cardiaque opérationnel passe à /status toutes les quelques secondes et s'attend à en recevoir 200 en réponse. Si le serveur ne répond pas plus que le nombre de fois spécifié dans la configuration, il est supprimé de l'équilibrage. Cela s'est avéré être une tâche simple, quelques lignes et un routage prêt:


 export default async function(ctx, next) { if (routeMap[ctx.request.path]) { routeMap[ctx.request.path](ctx); } else { ctx.throw(NOT_FOUND, getStatusText(NOT_FOUND)); } next(); } 

Et nous répondons 200 à l'url de droite:


 export default (ctx) => { ctx.status = 200; ctx.body = '200'; }; 

Après cela, nous avons créé un serveur primitif qui a renvoyé l'état en <script> et le HTML prêt.


Il fallait contrôler le fonctionnement du serveur. Pour ce faire, vous devez sécuriser la journalisation et la surveillance. Les journaux ne sont pas écrits en JSON, mais afin de prendre en charge le format de journal de nos autres services, principalement Java. Log4js a été choisi en fonction de critères de référence - il est rapide, facile à configurer et écrit au format dont nous avons besoin. Un format de journal commun est nécessaire pour simplifier la prise en charge de la surveillance - pas besoin d'écrire des réguliers supplémentaires pour analyser les journaux. En plus des journaux, nous écrivons toujours des erreurs dans la sentinelle . Je ne donnerai pas le code des enregistreurs, c'est très simple, en gros, il y a des réglages.


Ensuite, il était nécessaire de prévoir un arrêt gracieux - lorsque le serveur tombe malade ou lorsque la version roule, le serveur n'accepte plus de connexions entrantes, mais exécute toutes les demandes suspendues. Il existe de nombreuses solutions prêtes à l'emploi pour un nœud. Ils ont pris http-graceful-shutdown , tout ce qui devait être fait était de boucler l'appel gracefulShutdown(app.listen(SERVER_PORT))


À ce stade, nous avons obtenu une solution prête pour la production. Pour vérifier comment cela fonctionne, ils ont activé le rendu du serveur pour 5% des utilisateurs sur une seule page. Nous avons examiné les mesures, nous avons réalisé qu'elles amélioraient considérablement le FMP pour les téléphones mobiles, pour les ordinateurs de bureau, la valeur n'a pas changé. Ils ont commencé à tester les performances, ont découvert qu'un serveur contient environ 20 RPS (ce fait était très amusant pour les Javist). Découvrez les raisons pour lesquelles il en est ainsi:


  • L'un des principaux problèmes s'est avĂ©rĂ© ĂŞtre construit sans NODE_ENV = production (nous avons dĂ©fini l'ENV dont nous avions besoin pour la construction du serveur). Dans ce cas, la rĂ©action donne l'assemblage hors production, qui tourne environ 30% plus lentement.


  • Nous avons augmentĂ© la version du nĹ“ud de 8 Ă  10, obtenant encore 20-25% des performances.


  • Ce que nous avons laissĂ© pour la dernière fois, c'est lancer un nĹ“ud sur plusieurs noyaux. Nous pensions que c'Ă©tait très difficile, mais ici aussi, tout s'est avĂ©rĂ© très prosaĂŻque. Le nĹ“ud a un mĂ©canisme intĂ©grĂ© - cluster . Il vous permet d'exĂ©cuter le nombre requis de processus indĂ©pendants, y compris un processus maĂ®tre qui disperse les tâches pour eux.



 if (cluster.isMaster) { cluster.on('exit', (worker, exitCode) => { if (exitCode !== SUCCESS) { cluster.fork(); } }); for (let i = 0; i < serverConfig.cpuCores; i++) { cluster.fork(); } } else { runApp(); } 

Dans ce code, le processus maître démarre, les processus démarrent en fonction du nombre de CPU allouées au serveur. Si l'un des processus enfants se bloque - le code de sortie n'est pas 0 (nous avons nous-mêmes éteint le serveur), le processus maître le redémarre.
Et les performances augmentent d'environ le nombre de processeurs alloués au serveur.


Comme je l'ai écrit ci-dessus, la plupart du temps consacré à l'écriture des manuels originaux - environ 3 semaines. Il a fallu environ 2 semaines pour rédiger l'intégralité du SSR, et pendant environ un mois, nous l'avons lentement rappelé. Tout cela a été fait par des forces de 2 fronts, sans expérience d'entreprise du nœud js. N'ayez pas peur de faire de la SSR, surtout - n'oubliez pas de spécifier NODE_ENV=production , il n'y a rien de compliqué. Les utilisateurs et le référencement vous en seront reconnaissants.

Source: https://habr.com/ru/post/fr445816/


All Articles