Wie wir Server-Rendering gesehen haben und was daraus wurde

Hallo allerseits! Im Laufe des Jahres haben wir zu React gewechselt und darüber nachgedacht, wie wir sicherstellen können, dass unsere Benutzer nicht auf die Client-Vorlage warten, sondern die Seite so schnell wie möglich sehen. Zu diesem Zweck haben wir uns für das serverseitige Rendern (SSR - Server Side Rendering) und die Optimierung der Suchmaschinenoptimierung entschieden, da nicht alle Suchmaschinen JS ausführen können und diejenigen, die Zeit für die Ausführung aufwenden können, und die Crawling-Zeit jeder Site begrenzt ist.



Ich möchte Sie daran erinnern, dass beim Server-Rendering JavaScript-Code auf der Serverseite ausgeführt wird, um dem Client HTML-fähig zu machen. Dies wirkt sich auf die vom Benutzer wahrgenommene Leistung aus, insbesondere auf langsameren Computern und im langsamen Internet. Sie müssen nicht warten, bis JS heruntergeladen, analysiert und ausgeführt wurde. Der Browser kann HTML nur sofort rendern, ohne auf JSa zu warten, der Benutzer kann den Inhalt bereits lesen.
Dadurch wird die passive Wartephase reduziert. Nach dem Rendern muss der Browser nur das fertige DOM durchlaufen und überprüfen, ob es mit dem gerenderten übereinstimmt
auf dem Client und fügen Sie Ereignis-Listener hinzu. Dieser Vorgang wird als Hydratation bezeichnet . Wenn während des Hydratationsprozesses eine Diskrepanz zwischen dem Inhalt des Servers und dem vom Browser generierten Inhalt besteht, erhalten wir eine Warnung in der Konsole und einen zusätzlichen Renderer auf dem Client. Dies sollte nicht der Fall sein. Es muss sichergestellt werden, dass die Ergebnisse des Server- und Client-Renderings übereinstimmen. Wenn sie voneinander abweichen, sollte dies als Fehler behandelt werden, da dies die Vorteile des Server-Renderings zunichte macht. Wenn ein Element abweichen sollte, fügen Sie suppressHydrationWarning={true} .


Darüber hinaus gibt es eine Einschränkung: Es gibt kein window auf dem Server. Der Code, der darauf zugreift, muss in Lebenszyklusmethoden ausgeführt werden, die nicht auf der Serverseite aufgerufen werden. Das heißt, Sie können das window in UNSAFE_componentWillMount () oder im Fall von Hooks in uselayouteffect verwenden .


Im Wesentlichen läuft der serverseitige Renderprozess darauf hinaus, den initialState aus dem Backend abzurufen, ihn über renderToString() , den fertigen initialState und HTML an der Ausgabe abzurufen und an den Client zu senden.


In hh.ru sind Wanderungen vom Client JS nur im API-Gateway in Python zulässig. Dies dient der Sicherheit und dem Lastausgleich. Python geht bereits zu den erforderlichen Backends für Daten, bereitet sie vor und gibt sie an den Browser weiter. Node.js wird nur zum Rendern von Servern verwendet. Dementsprechend benötigt der Python nach dem Vorbereiten der Daten eine zusätzliche Fahrt zum Knoten, wartet auf das Ergebnis und sendet die Antwort an den Client.


Zuerst mussten Sie einen Server auswählen, um mit HTTP zu arbeiten. Wir hielten bei Koa an . Gefiel die moderne Syntax mit await . Modularität ist eine leichte Middleware, die bei Bedarf separat installiert oder einfach unabhängig geschrieben werden kann. Der Server selbst ist leicht und schnell . Ja, und von Koa von demselben Entwicklungsteam geschrieben, das sie Express schreiben, ist ihre Erfahrung faszinierend.


Nachdem wir gelernt hatten, wie wir unseren Service einführen können, haben wir den einfachsten Code auf KOA geschrieben, der 200 geben konnte, und ihn auf das Produkt hochgeladen. Es sah so aus:


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

In hh.ru befinden sich alle Dienste in Docker- Containern. Vor der ersten Veröffentlichung müssen Sie ansible Playbooks schreiben, mit deren Hilfe der Service in Produktionsumgebungen und auf Prüfständen eingeführt wird. Jeder Entwickler und Tester hat seine eigene Testumgebung, die dem Produkt am ähnlichsten ist. Wir haben die meiste Zeit und Energie damit verbracht, Spielbücher zu schreiben. Dies geschah, weil zwei Front-End-Renderer dies taten und dies der erste Dienst auf einem Knoten in hh.ru ist. Wir mussten herausfinden, wie der Dienst in den Entwicklungsmodus geschaltet werden kann, und zwar parallel zu dem Dienst, für den das Rendern stattfindet. Liefern Sie Dateien in einen Container. Starten Sie einen nackten Server, damit der Docker-Container gestartet wird, ohne auf den Build zu warten. Erstellen und erstellen Sie den Server zusammen mit dem Dienst, der ihn verwendet, neu. Bestimmen Sie, wie viel RAM wir benötigen.


Im Entwicklungsmodus wurde die Möglichkeit einer automatischen Neuerstellung und eines anschließenden Neustarts des Dienstes beim Ändern der im endgültigen Build enthaltenen Dateien bereitgestellt. Der Knoten muss neu gestartet werden, um ausführbaren Code zu laden. Webpack überwacht Änderungen und Builds . Webpack wird benötigt, um ESM in Common CommonJS zu konvertieren. Für den Neustart nahmen sie nodemon , das sich um die gesammelten Dateien kümmert.


Dann haben wir den Routing-Server gelernt. Für einen ordnungsgemäßen Ausgleich müssen Sie wissen, welche Serverinstanzen aktiv sind. Um dies zu überprüfen, wechselt der betriebsbereite Herzschlag alle paar Sekunden in den /status und erwartet als Antwort 200. Wenn der Server nicht mehr als die in der Konfiguration angegebene Anzahl von Malen antwortet, wird er aus dem Ausgleich entfernt. Dies stellte sich als einfache Aufgabe heraus, ein paar Zeilen und ein fertiges Routing:


 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(); } 

Und wir antworten 200 mit der richtigen URL:


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

Danach haben wir einen primitiven Server erstellt, der den Status in <script> und bereitem HTML zurückgibt.


Es musste gesteuert werden, wie der Server funktioniert. Dazu müssen Sie die Protokollierung und Überwachung beschleunigen. Protokolle werden nicht in JSON geschrieben, sondern um das Protokollformat unserer anderen Dienste, hauptsächlich Java, zu unterstützen. Log4js wurde nach Benchmarks ausgewählt - es ist schnell, einfach zu konfigurieren und schreibt in dem von uns benötigten Format. Zur Vereinfachung der Überwachungsunterstützung ist ein gemeinsames Protokollformat erforderlich. Sie müssen keine zusätzlichen Stammdaten schreiben, um Protokolle zu analysieren. Zusätzlich zu den Protokollen schreiben wir immer noch Fehler in den Wachposten . Ich werde den Code der Logger nicht angeben, es ist sehr einfach, im Grunde gibt es Einstellungen.


Dann musste für ein ordnungsgemäßes Herunterfahren gesorgt werden. Wenn der Server krank wird oder wenn die Freigabe erfolgt, akzeptiert der Server keine eingehenden Verbindungen mehr, sondern führt alle daran hängenden Anforderungen aus. Es gibt viele vorgefertigte Lösungen für einen Knoten. Sie nahmen http-graceful-shutdown , alles was getan werden musste, war den gracefulShutdown(app.listen(SERVER_PORT)) Aufruf gracefulShutdown(app.listen(SERVER_PORT))


Zu diesem Zeitpunkt haben wir eine produktionsreife Lösung erhalten. Um zu überprüfen, wie es funktioniert, haben sie das Server-Rendering für 5% der Benutzer auf einer Seite aktiviert. Wir haben uns die Metriken angesehen und festgestellt, dass sie den FMP für Mobiltelefone erheblich verbessert haben. Bei Desktops hat sich der Wert nicht geändert. Sie begannen, die Leistung zu testen, und stellten fest, dass ein Server ~ 20 RPS enthält (diese Tatsache war für die Javisten sehr amüsant). Finden Sie die Gründe heraus, warum dies so ist:


  • Eines der Hauptprobleme stellte sich heraus, dass sie ohne NODE_ENV = Produktion erstellt wurden (wir haben die ENV festgelegt, die wir für die Servererstellung benötigen). In diesem Fall ergibt die Reaktion die Nichtproduktionsanordnung, die etwa 30% langsamer läuft.


  • Wir haben die Version des Knotens von 8 auf 10 erhöht und weitere 20-25% der Leistung erzielt.


  • Was wir zum letzten Mal verlassen haben, ist das Starten eines Knotens auf mehreren Kerneln. Wir vermuteten, dass es sehr schwierig war, aber auch hier erwies sich alles als sehr prosaisch. Der Knoten verfügt über einen integrierten Mechanismus - Cluster . Sie können die erforderliche Anzahl unabhängiger Prozesse ausführen, einschließlich eines Masterprozesses, der Aufgaben für sie streut.



 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(); } 

In diesem Code startet der Master-Prozess, die Prozesse starten entsprechend der Anzahl der für den Server zugewiesenen CPUs. Wenn einer der untergeordneten Prozesse abstürzt - der Exit-Code ist nicht 0 (wir haben den Server selbst ausgeschaltet), wird er vom Master-Prozess neu gestartet.
Die Leistung erhöht sich um etwa die Anzahl der für den Server zugewiesenen CPUs.


Wie ich oben schrieb, verbrachte ich die meiste Zeit damit, die ursprünglichen Spielbücher zu schreiben - ungefähr 3 Wochen. Es dauerte ungefähr 2 Wochen, um die gesamte SSR zu schreiben, und ungefähr einen Monat lang erinnerten wir uns langsam daran. All dies wurde durch Kräfte von 2 Fronten ohne Unternehmenserfahrung von Knoten js getan. Haben Sie vor allem keine Angst vor SSR - vergessen Sie nicht, NODE_ENV=production anzugeben, es ist nichts Kompliziertes daran. Benutzer und SEO werden es Ihnen danken.

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


All Articles