Neues Odnoklassniki-Frontend: Starten von React in Java. Teil I



Viele haben den Namen GraalVM gehört, aber bisher hatte nicht jeder die Möglichkeit, diese Technologie in der Produktion zu testen. Für Odnoklassniki ist diese Technologie bereits zum "heiligen Gral" geworden, der das Frontend verändert.

In diesem Artikel möchte ich darüber sprechen, wie wir es geschafft haben, uns mit Java und JavaScript anzufreunden und auf ein riesiges System mit viel Legacy-Code zu migrieren, und wie GraalVM auf diese Weise hilft.

Beim Verfassen des Artikels stellte sich heraus, dass das gesamte Material nicht in die herkömmliche Größe des HABR passt. Wenn Sie die gesamte Veröffentlichung veröffentlichen, dauert das Lesen mehrere Stunden. Aus diesem Grund haben wir uns entschlossen, den Artikel in zwei Teile zu unterteilen.

Im ersten Teil lernen Sie die Geschichte des Frontends in Odnoklassniki kennen, lernen dessen historische Merkmale kennen, finden eine Lösung für die Probleme, die sich in den letzten 11 Jahren des Projekts bei uns angesammelt haben, und tauchen ganz am Ende in die technischen Merkmale der Serverimplementierung der von uns getroffenen Entscheidung ein.

Hintergrund


Die erste Version von Odnoklassniki erschien vor 13 Jahren im Jahr 2006. Die Site wurde auf .NET erstellt, dann gab es kein JavaScript auf der Site, alles wurde auf dem Server gerendert.



Ein Jahr später hatte Odnoklassniki über eine Million Nutzer. Im Jahr 2007 waren dies unglaubliche Zahlen, und der Standort, der der Last nicht standhalten konnte, begann zu fallen. Die Entwickler lösten das Problem mit Hilfe des One.lv-Projekts, das von der lettischen Firma Forticom entwickelt wurde und dessen Kernkompetenzen in der Java-Entwicklung lagen. Aus diesem Grund wurde beschlossen, Odnoklassniki von .NET auf Java umzuschreiben.

Im Laufe der Zeit entwickelte sich das Projekt und es entstand der Bedarf an neuen Kundenlösungen. Wenn Sie beispielsweise auf einer Website navigieren, wird diese nicht vollständig aktualisiert, sondern nur in bestimmten Teilen. Oder damit beim Absenden nur das Formular aktualisiert wird und nicht die gesamte Seite. Gleichzeitig funktionierte die Site nur in Java.

Zu diesem Zweck haben sie ein System entwickelt, in dem die Site mit benannten Blöcken gekennzeichnet wurde. Während der Navigation wurde eine Anfrage an den Server gesendet, der über diesen Link überlegte, was geändert werden sollte, und dem Client wurden die erforderlichen Teile übergeben. Die Client-Engine ersetzte einfach die erforderlichen Teile und so wurde die Client-Dynamik implementiert. Sehr praktisch, da sich die gesamte Geschäftslogik auf dem Server befindet. Dank der kleinen Engine konnte das Unternehmen weiterhin Java-Code schreiben, um den Client zu verwalten.

Natürlich war ohne minimales JavaScript nicht genug. Um ein Popup zu erstellen, sind Manipulationen erforderlich: Zum Beispiel durch Bewegen des Mauszeigers über eine Div-Anzeige: Block wurde aufgelegt oder mit Anzeige ausgeblendet: Keine.

Gleichzeitig wurde der Inhalt des Popups vom Server angefordert, die gesamte Geschäftslogik war vorhanden und befand sich in Java.



2018


Nach 12 Jahren entwickelte sich Odnoklassniki zu einem riesigen Dienst mit mehr als 70 Millionen Nutzern. Wir haben mehr als 7.000 Maschinen in 4 Rechenzentren, und nur 600.000 Anfragen pro Sekunde gehen an das OK.RU-Front-End.

Odnoklassnikis Frontserver funktioniert weiterhin in Java, und die Codebasis der Fronts allein überschreitet zwei Millionen Zeilen.



Die auf der Client-Seite implementierten Technologien standen auch nicht still: Viele Lösungen wurden mit verschiedenen Bibliotheken erstellt: GWT, jQuery, DotJs, RequireJS und viele andere.

Zu dieser Zeit waren Standards wie React, Angular und Vue nicht üblich. Jeder Entwickler versuchte, mit allen verfügbaren Tools die optimale Lösung zu finden.

Es wurde klar, dass es sehr schwierig ist, damit zu leben, weil sich eine Vielzahl von Problemen angesammelt haben:

  • Viele alte Bibliotheken
  • Es gibt keinen einheitlichen Rahmen
  • Kein Isomorphismus (da das Backend in Java ist, ist der Client in JS)
  • Auf dem Client befindet sich keine einzige strukturierte Anwendung
  • Schlechte Reaktionsfähigkeit
  • Unzureichende Werkzeuge
  • Hohe Eintrittsschwelle

Die Welt war bereits 2018 und es war notwendig, sich zu ändern.

Mit der ganzen Kraft des technischen Denkens haben wir vier grundlegende Anforderungen zur Lösung von Problemen durchdacht und formuliert:

  1. Klassenkameraden sollten isomorphen Code für die Benutzeroberfläche haben. Da es unmöglich ist, den Server ständig in Java zu schreiben und dann, wenn Sie eine Art Dynamik hinzufügen müssen, dasselbe auf dem Client zu spielen.
  2. Ein reibungsloser Übergang ist erforderlich. Weil es unmöglich ist, schnell die zweite Version von Odnoklassniki zu erstellen und zu wechseln
  3. Benötigt Server-Rendering (mehr dazu weiter unten)
  4. Die neue Lösung, die mit der gleichen Menge Eisen arbeitet, sollte die Leistung und die Fehlertoleranz unter unseren Lasten nicht beeinträchtigen.

Warum serverseitiges Rendern?


Odnoklassniki hat viele Benutzer, die weit von Moskau entfernt leben und nicht immer über ein gutes Internet verfügen.



Mithilfe des Server-Renderings können diese Benutzer Inhalte schneller abrufen. Während die Bilder geladen sind, können sie etwas lesen:



Wir haben eine Reihe von Experimenten durchgeführt, um zu verstehen, was passieren würde, wenn einige Daten (z. B. Band) mit einer Erwartung bereits an den Kunden geliefert würden. Als Ergebnis stellte sich heraus, dass sich dies negativ auf die Benutzeraktivität auswirkte.

Wie der Server jetzt funktioniert


Der Browser fordert die OK-Site an und ruft die OK-WEB-Anwendung auf, die vollständig in Java geschrieben ist. Die Anwendung folgt den Daten in der API. Zwischen WEB und API wird ein in Odnoklassniki entwickelter One-Nio-Fast-Binary-Transport implementiert. Anforderungen werden in weniger als einer Millisekunde ausgeführt. Sie können sehen, was es separat ist . Mit One-nio können Sie viele Abfragen kostengünstig durchführen, ohne sich über Verzögerungen Gedanken machen zu müssen.

Die API holt die Daten heraus und gibt sie an das Web weiter. Das Web generiert HTML-Seiten mit einer Java-Engine und gibt diese an den Browser weiter.

All dies dauert jetzt weniger als 200 ms.



Suche nach einer Lösung


Zunächst wurde das Konzept der auf Widgets basierenden Migration entwickelt.

Bewerbungen werden in kleinen Stücken an die Baustelle geliefert. Im Inneren werden sie auf einen neuen Stapel geschrieben. Und für den Rest der Site wird es nur ein DOM-Element mit einer Art benutzerdefiniertem Verhalten sein.



Dies ähnelt dem <video> -Tag: Ein benutzerdefiniertes DOM-Element mit Attributen, Methoden und Ereignissen. Infolgedessen befindet sich die DOM-API außerhalb, während die Widget-Funktionalität innerhalb des neuen Stacks implementiert ist.

Welchen Stapel soll ich wählen?


Nachdem das Konzept umgesetzt werden musste, begannen sie, die Optionen zu klären.

Kotlin


Der erste Prototyp wurde bei Kotlin hergestellt. Die Idee war wie folgt: Schreiben Sie für neue Komponenten Logik in Kotlin und beschreiben Sie das Komponenten-Markup in XML. Alles kann auf dem Server in der JVM mit der vorhandenen Template-Engine ausgeführt und für den Client in JavaScript transponiert werden.



Neben der Einführung einer neuen Sprache mit einer hohen Einstiegsschwelle stellte sich heraus, dass Kotlin nur unzureichend entwickelte Tools für die Arbeit mit JavaScript hatte und viel mehr unabhängig entwickelt werden müssten.

Daher musste dieses Konzept leider aufgegeben werden.

Node.js


Eine andere Option ist, Node.js oder eine andere Laufzeitdatei, z. B. Dart, zu platzieren. Aber was passiert

Es gibt zwei Möglichkeiten, Node.js zu verwenden.
Die erste Möglichkeit besteht darin, das Rendern der Komponente an den Server auf Node.js zu delegieren, der auf demselben Server wie die Java-Anwendung ausgeführt wird. Daher speichern wir die Anwendung in Java und rufen gerade beim Rendern von HTML den Dienst auf, der lokal auf Node.js ausgeführt wird.

Bei diesem Ansatz gibt es jedoch mehrere Probleme:

  1. Bei einem Remote-Aufruf von Node.js wird die Eingabe serialisiert / deserialisiert. Diese Daten können beispielsweise dann sehr umfangreich sein, wenn eine neue Komponente in JS ein Wrapper um eine in Java implementierte alte Komponente ist.
  2. Ein Remote-Anruf, auch auf einem lokalen Computer, ist alles andere als kostenlos und führt auch zu einer zusätzlichen Verzögerung. Wenn sich auf der Seite Dutzende oder Hunderte solcher Komponenten befinden, auch sehr einfache, erhöhen wir den Overhead und die Verzögerung bei der Verarbeitung der Benutzeranforderung erheblich.
  3. Darüber hinaus ist der Betrieb eines solchen Systems erheblich kompliziert, da wir anstelle eines einzelnen Prozesses einen Java-Prozess und mehrere Prozesse in Node.js benötigen. Dementsprechend werden alle Vorgänge sehr viel komplizierter, z. B. Bereitstellung, Erfassung von Betriebsindikatoren, Protokollanalyse, Fehlerüberwachung usw.

Die zweite Möglichkeit, Node.js zu verwenden, besteht darin, es in Java vor den Webserver zu stellen und für die Nachbearbeitung von HTML zu verwenden. Mit anderen Worten, es ist ein Proxy, der HTML analysiert, Komponenten in JS findet, diese zeichnet und das fertige HTML an den Benutzer zurückgibt. Eine interessante Option, die universell zu sein scheint und durchaus funktioniert. Die Nachteile dieses Ansatzes sind, dass er eine gründliche Änderung der gesamten Infrastruktur erfordert, die Gemeinkosten erheblich erhöht und ernsthafte Risiken birgt - jede Anfrage muss über Node.js erfolgen, das heißt, wir werden beginnen, uns vollständig darauf zu verlassen. Es scheint eine zu teure Lösung zu sein, um unser Problem zu lösen.





Es stellt sich heraus, dass Node.js aus folgenden Gründen nicht verwendet werden kann:

  • Serialisierung / Deserialisierung bedeutet zusätzliche Arbeitsbelastung und Verzögerungen
  • Node.js ist eine weitere Komponente im riesigen verteilten System von Odnoklassniki

Wir haben bereits viele Spezialisten, die wissen, wie man Java "kocht", und jetzt müssen wir ein Personal einstellen, das Node.js betreibt und zusätzlich zu der bestehenden Infrastruktur eine weitere Infrastruktur schafft.

JavaScript in der JVM


Was aber, wenn Sie versuchen, JavaScript in der JVM auszuführen? Es stellt sich heraus, dass der Java- und JavaScript-Code in einem Prozess ausgeführt werden und mit einem Minimum an Aufwand interagieren.

Dadurch werden Java-Teile im aktuellen WEB problemlos durch JavaScript ersetzt.
JS-Komponenten empfangen Daten von Java und generieren HTML. Sie können sowohl auf dem Client als auch auf dem Server isomorph arbeiten.

Aber wie läuft JS in der JVM?
Sie können die V8 nach dem Vorbild von Cloudflare verwenden . Dies ist jedoch Binärcode von Drittanbietern für Java. Daher können in der JVM keine Fehler in der V8 abgefangen werden. Jeder V8-Absturz zerstört den gesamten Prozess. Infolgedessen erhöht der Einsatz von V8 die operationellen Risiken, und dies sollte nicht zugelassen werden.

Es gibt mehrere JS-Laufzeiten für die JVM: zwei Nashorn- und Rhino-Laufzeiten (eine von Oracle, die andere von Mozilla) und frische GraalVM-Laufzeiten.



Vorteile von JS Runtimes für JVM:

  • Alles funktioniert in der JVM, und wir haben viel Fachwissen in diesem Bereich.
  • Kostenlose Java- und JavaScript-Interaktion
  • Sichere Laufzeit
  • Java-Compiler im Falle von GraalVM

Weiters genügte es, diese Laufzeiten in der Geschwindigkeit zu vergleichen. Es stellte sich heraus, dass GraalVM allen mit großem Abstand voraus ist:



Was ist GraalVM?


GraalVM ist eine leistungsstarke Laufzeitumgebung, die Programme in verschiedenen Sprachen unterstützt. Es verfügt über ein Framework zum Schreiben von Sprachcompilern für die JVM. Dadurch wird die Ausführung von Programmen in Java, Kotlin, JS, Python und anderen Sprachen innerhalb derselben JVM unterstützt.

Weitere Informationen zu GraalVM-Funktionen finden Sie in einem Bericht von Oleg Shelaev , der bei Oracle Labs arbeitet, wo GraalVM entwickelt wird. Empfohlen für das Anschauen von Back-End und Front-End.

Mit GraalVM können wir JS ausführen, um die Benutzeroberfläche auf dem Server zu rendern. Als Bibliothek verwenden wir React .

Die Vorteile eines solchen Bundles:

  • Keine neuen Sprachen hinzugefügt: noch Java und JavaScript
  • Große Community: Jeder kennt React
  • Niedrige Eintrittsschwelle
  • Suchen Sie ganz einfach nach Kollegen in einem Team
  • Die Bedienung ist nicht kompliziert

Ausführen von React in GraalVM


In GraalVM können Sie einen Kontext erstellen - einen isolierten Container, in dem das Programm in der Gastsprache ausgeführt wird. In unserem Fall ist die Gastsprache JS:

Context context = Context.create("js"); //  global   Value js = context.getBindings("js"); 

Um mit dem Kontext zu interagieren, wird sein globales Objekt verwendet:

 //    global js.putMember("serverProxy", serverProxy); //    global Value app = js.getMember("app"); 

Sie können den Modulcode in den Kontext laden:

 //     Value load = js.getMember("load"); //     load.execute(pathToModule); 

Oder "zap-eval-it" ist ein beliebiger Code:

 context.eval("js", someCode); 



JS Server Rendering: Konzept


Erstellen Sie einen JavaScript-Kontext in der JVM und laden Sie den Code des React-Anwendungsmoduls darin. Wir werfen von Java zu JS die notwendigen Funktionen und Methoden. Dann extrahieren wir aus diesem Kontext die Verknüpfung zur JS-Funktion render () dieses Moduls, damit wir sie später von Java aus aufrufen können.



Wenn der Benutzer die Seite anfordert, startet die Servervorlagen-Engine, er ruft die render () -Funktion der erforderlichen Komponenten mit den erforderlichen Daten auf, empfängt von ihnen HTML-Code und gibt ihn zusammen mit dem HTML der gesamten Seite an den Benutzer weiter.



JS Server Rendering: Implementierung


In der Servervorlagen-Engine von Odnoklassniki wird das Layout in Form eines HTML-Markups geschrieben. Um JS-Anwendungen von den üblichen Markups zu unterscheiden, verwenden wir benutzerdefinierte Tags.
Wenn die Vorlagen-Engine auf ein benutzerdefiniertes Tag stößt, wird eine Aufgabe erstellt, um das entsprechende Modul zu rendern. Es wird an den Thread-Pool gesendet, von denen jeder seinen eigenen JS-Kontext hat, auf einem freien Thread ausgeführt, die darin enthaltene Komponente gerendert und dem Client übergeben.



Warum brauche ich einen Kontextpool?


Die Komponente wird synchron in einem Thread gerendert. Zu diesem Zeitpunkt ist der JS-Renderkontext ausgelastet. Durch das Erstellen mehrerer unabhängiger Kontexte können Sie das Rendern von Komponenten mithilfe von Java-Multithreading-Funktionen parallelisieren.



Java-Datenerfassungsfunktionen werden unter Bezugnahme auf jeden Kontext übergeben. Das Ergebnis ist cooles Multithread-JavaScript in einem einzigen Prozess.

Wie die Implementierung des Client-Teils des neuen Frontends auf diesem Konzept aufbaut, wird im nächsten Artikel beschrieben.

Fortsetzung folgt

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


All Articles