Mischen von OpenJDK und NodeJS: Sprachübergreifende Interaktionen und vertikale Architektur

Hallo habr

Lange Zeit hatte ich die Idee, GraalVM mit Ihnen zu diskutieren, es aufzuschieben , bis schließlich der heutige Artikel gefunden wurde, dessen Thema den Rahmen einer bestimmten virtuellen Maschine sprengt . Der Autor Mike Hearn skizziert das gesamte Paradigma der mehrsprachigen Interaktion und der mehrsprachigen Programmierung (Polyglot-Programmierung). Als nächstes sehen Sie das berühmte Beispiel für vertikale Skalierung und einen sehr langen Artikel unter dem Schnitt.



In diesem Artikel geht es um eine innovative Art, Software zu schreiben, die in Zukunft populär werden könnte, aber wahrscheinlich nicht jetzt. Der Artikel hat einen Code, ehrlich!

In der Antike, also im Jahr 2015, schrieb ich, warum Kotlin meine nächste Programmiersprache sein wird , und im Jahr 2016 schrieb ich über Graal und Truffle : zwei radikale Forschungsprojekte im Zusammenhang mit Compilern, die die Arbeit von Sprachen wie Ruby nicht nur erheblich beschleunigen , sondern verkörpern auch in der Realität nahtlose interlanguage Interaktionen. In diesen Projekten wird der dynamische (JIT) Compiler oder OpenJDK durch einen neuen ersetzt, der die Möglichkeit bietet, mit Anmerkungen versehene Interpreter automatisch in hochmoderne JIT-Compiler zu verwandeln.

Um auf diese Themen im Jahr 2019 zurückzukommen, möchte ich Ihnen drei Dinge zeigen:

  1. Wie man die kleine Bibliothek benutzt, die ich geschrieben habe, um NPM-Module aus dem Code von Programmen, die in Java oder Kotlin geschrieben sind, fast nahtlos zu verwenden.
  2. Erklären Sie alle guten Gründe, warum Sie es benötigen, auch wenn Sie denken, dass JavaScript / Java das Schlimmste auf der Welt ist, ohne Fischöl.
  3. Erläutern Sie kurz das Konzept der vertikalen Architektur, das mit dem auf Mikroservices ausgerichteten Design konkurriert. Es befindet sich an der Schnittstelle der neuesten Versionen von GraalVM und OpenJDK und erfordert modernste Hardware.

Verwendung von NPMs aus Java und Kotlin


Wir werden nur drei einfache Schritte unternehmen:

  1. Nehmen Sie GraalVM . Hierbei handelt es sich um eine Reihe von Patches, die auf OpenJDK basieren und gerade rechtzeitig erschienen sind: Sie können den gesamten JVM-Bytecode ausführen, den Sie haben.
  2. Wir nehmen mein NodeJVM- Toolkit aus Github und fügen es unserem Pfad hinzu.
  3. Ersetzen Sie java in der Befehlszeile durch nodejvm . Das ist alles!

Okay, okay. Ich gebe zu, hier zeichne ich ein wenig und übertreibe, bis zum Ende des Artikels wirst du einen solchen Stil ertragen müssen. Natürlich ist alles gar nicht so einfach: Sie müssen das Modul trotzdem nehmen und verwenden.

Überlegen Sie, wie es aussieht:



Beispielcode mit NodeJVM

Schauen Sie sich dieses Bild genau an. Ja, genau so sieht es aus: Kotlin mit einer integrierten mehrzeiligen Zeichenfolge, in der die automatische Vervollständigung von JavaScript erfolgt, nach der eine statische JavaScript-Analyse durchgeführt und die Syntax korrekt hervorgehoben wird. Für die JVM funktionieren die gleichen Vorgänge von Java oder anderen Sprachen aus, die IntelliJ versteht. Um solche Möglichkeiten zu erhalten, müssen Sie in den IDE-Einstellungen auf den Schalter klicken (lesen Sie hierzu die Readme-Datei für NodeJVM). Diese Funktion wird jedoch später automatisch ausgeführt. Wenn es IntelliJ gelingt, durch Analyse des Datenstroms herauszufinden, dass Ihre Zeichenfolge schließlich an die run oder eval werden soll, wird sie als eingebettetes JS behandelt.

Hier werde ich über die API für Kotlin sprechen, da diese in normalem Java etwas hübscher und praktischer ist als die API, aber alles, was ich unten beschreibe, kann auch in Java erfolgen.

Achten Sie im obigen Code auf einige der folgenden Funktionen:

  • Um auf JavaScript zuzugreifen, müssen Sie den nodejs {} -Block verwenden. Fakt ist, dass JavaScript Single-Threaded ist und dass Sie zum Ausführen von NPM-Modulen „den Node-Stream eingeben“ müssen. Der nodejs {} -Block führt eine solche Synchronisation für uns durch, egal in welchem ​​Thread wir uns befinden. Sie müssen sich also ständig daran erinnern: Um einen JS-Code auszuführen, müssen Sie sich im Prinzip in einem solchen Block befinden. Sie können es so oft wie Sie möchten erneut eingeben, sodass es sicher ist, einen solchen Block an jedem Ort zu verwenden, an dem wir ihn benötigen. Alle JavaScript-Rückrufe werden im nodejs ausgeführt, und daher wird allen anderen Threads der Zugriff auf den Block nodejs verweigert. Wenn Sie sich also Gedanken über die Leistung oder das reibungslose Rendern der nodejs machen, vermeiden Sie die Ausführung lang laufender Vorgänge in Rückrufen.
  • Die Syntax var x by bind(SomeObject()) ist nur im nodejs Block verfügbar und ermöglicht es Ihnen, im globalen JavaScript-Bereich eine Verbindung zu derselben Variablen nodejs . Wenn sich x von Kotlin ändert, ändert sich dies in JS und umgekehrt. Hier füge ich der JS-Welt ein reguläres Java- File Objekt hinzu.
  • Die eval Methode gibt zurück ... was wir von ihr verlangen, jedoch in der Art der statischen Typisierung. Dies ist eine generische Funktion. Durch Angabe des Entitätstyps, den wir ihr zuweisen, wird sichergestellt, dass das JavaScript-Objekt von eval automatisch in eine statisch typisierte Klasse oder eine Java / Kotlin / Scala / etc-Schnittstelle umgewandelt wird. Obwohl dies oben nicht explizit angegeben wurde, ist MemoryUsage ein einfacher Schnittstellentyp, den ich definiert habe, und er verfügt über die Funktionen rss() und heapTotal() . Sie werden den gleichnamigen JavaScript-Eigenschaften zugeordnet und auf das angewendet, was Sie von der Node-API process.memoryUsage() . Die meisten JS-Typen können daher in „normale“ Java-Typen konvertiert werden. Eine ausführliche Dokumentation der Funktionsweise finden Sie auf der GraalVM-Website. Die resultierenden Objekte können überall gespeichert werden, aber die Methodenaufrufe in ihnen müssen natürlich im nodejs Block erfolgen.
  • JavaScript-Objekte können auch als einfache Zuordnungen eines Strings zu einem Objekt betrachtet werden, was in vielerlei Hinsicht ihrer Natur entspricht. Solche Zuordnungen einer Zeichenfolge zu einem Objekt können wiederum auf etwas Stärkeres zurückgeführt werden, was im Rückruf deutlich zu sehen ist. Verwenden Sie die Präsentation, die Sie bevorzugen.
  • Sie können require und es wird auf die übliche Weise in den node_modules Verzeichnissen nach Modulen node_modules .

Das obige Code-Snippet verwendet das DAT- Protokoll, mit dem Sie eine Remote-Verbindung zu einem Peer-to-Peer-Netzwerk herstellen können, das BitTorrent ähnelt, und nach Peers suchen, die die gewünschte Datei haben. Ich verwende DAT als Beispiel, da es (a) dezentral und daher einzigartig schick ist und (b) zum Guten oder Schlechten, ist seine Referenzimplementierung in JavaScript geschrieben. Dies ist kein Programm, das ich vollständig schreiben könnte, ohne JS in einer angemessenen Zeit zu verwenden.

Dies kann auch von Java aus erfolgen:

 import net.plan99.nodejs.NodeJS; public class Demo { public static void main(String[] args) { int result = NodeJS.runJS(() -> NodeJS.eval("return 2 + 3 + 4").asInt() ); System.out.println(result); } } 

Die Java-API bietet Ihnen keine so angenehme Variablenbindung und automatische Umwandlung wie die Kotlin-API, ist jedoch recht einfach zu verwenden. Hier zeigen wir, wie wir das Ergebnis in einen Java ( int ) vom Integer-Typ konvertieren und "aus" dem Node-Stream zurückgeben: In diesem Fall ist der Haupt-Java-Stream nicht mit dem NodeJS-Stream identisch, aber wir wechseln nahtlos zwischen diesen Flows.

NodeJVM ist ein sehr, sehr kleiner Wrapper auf GraalVM . Es fügt eine unbedeutende Menge Code hinzu. Machen Sie sich also keine Sorgen, dass der Code möglicherweise nicht mehr unterstützt wird oder verschwindet. 99,99% der in diesem Fall geleisteten Arbeit wird vom GraalVM-Team ausgeführt.

Hier einige offensichtliche Vorschläge für Verbesserungen:

  • Ermöglichen Sie JS-Modulen, Java-Module nach Maven-Koordinaten zu importieren.
  • Formulierung einiger „Best Practices“ für die Java-Erstellung von NPM-Modulen. Kann eine JAR-Datei beispielsweise ein node_modules-Verzeichnis enthalten (kurz gesagt: Nein, da NodeJS die Datei-E / A-Vorgänge immer noch auf seine eigene Weise organisiert und nichts über Reißverschlüsse weiß, lange gesagt: Ja, wenn Sie sich anstrengen).
  • Weitere Sprachen: Python und Ruby benötigen für die Thread-Synchronisierung nicht den in NodeJS erforderlichen „Kleber“. Sie können also einfach die reguläre GraalVM Polyglot-API verwenden . Kotlin-Benutzer werden jedoch feststellen, dass Cast- / Erweiterungsmethoden und APIs zum Binden von Variablen in jeder Sprache nützlich sind.
  • Windows-Unterstützung.
  • Gradle-Plugin, damit Programme Abhängigkeitslisten in verschiedenen Sprachen haben können
  • Integration mit dem native-image Tool, dem sogenannten SubstrateVM; Wenn Sie also zur Laufzeit nicht die volle Leistung von HotSpot benötigen, können Sie kleine statisch verknüpfte Binärdateien im Golang-Stil bereitstellen.
  • Vielleicht eine Art Konverter zum Konvertieren von TypeScript in Java, damit Sie DefinitelyTyped verwenden und schnell in die statische Welt eintauchen können.

Patches sind willkommen.

Warum brauchst du das?


Vielleicht denken Sie schon: "Wow, JavaScript, das wir entwickelt haben, kann jetzt in gegenseitiger Liebe, gegenseitigem Respekt und Harmonie sein!"



Idealisierte begeisterte Reaktion

Es ist sehr wahrscheinlich, dass Sie diesem Standpunkt näher kommen:



JavaScript und Java sind nicht nur Sprachen. Dies sind Kulturen, und nichts ist Entwicklern so sympathisch wie CULTURAL WARS!

Aus diesem Grund sollten Sie diese Seite zumindest als Lesezeichen für zukünftige Referenz verwenden, auch wenn Sie nur mit dem Gedanken daran, dass $ OTHER_LANG in Ihr kostbares Ökosystem eindringt, die Waffe greifen möchten:

  • Wenn Sie in erster Linie Java- Entwickler sind, haben Sie jetzt Zugriff auf eindeutige JavaScript-Module, die möglicherweise keine Entsprechung in der JVM haben (z. B. das DAT-Protokoll). Sie können es lieben oder hassen, aber die Tatsache bleibt: Viele Leute schreiben Open-Source-NPM-Module, und einige dieser Module sind sehr gut. Sie können auch Code wiederverwenden, der auf Ihren Web-Frontends ausgeführt wird, ohne Sprachtransporter zu benötigen. Und wenn Sie mit einer geerbten Codebasis in NodeJS arbeiten, die Sie nach und nach nach Java portieren möchten, wird diese Arbeit plötzlich erheblich vereinfacht.
  • Wenn Sie in erster Linie JavaScript- Entwickler sind, haben Sie jetzt einfachen Zugriff auf eindeutige JVM-Bibliotheken, die in JavaScript möglicherweise keine direkten Entsprechungen aufweisen (z. B. Lucene , Chronicle Map ) oder nur schlecht dokumentierte, unreife oder weniger produktive Analoga bieten . Wenn Sie in Ihrem nächsten Projekt auf HTML verzichten möchten, können Sie das GUI-Framework für eine weiße Person untersuchen . Sie können auch auf viele andere Sprachen zugreifen, z. B. Ruby- und R. JVM-Objekte können von NodeJS-Mitarbeitern gemeinsam genutzt werden, wobei das Multithreading im gemeinsam genutzten Speicher genutzt wird , wenn diese Möglichkeit Ihrem Profiler zufolge genutzt werden kann. Und wenn Sie mit einer geerbten Java-Codebasis arbeiten, die Sie nach und nach auf NodeJS portieren möchten, wird diese Arbeit plötzlich erheblich vereinfacht.
  • Wenn Sie alle Sprachen gleichzeitig lernen , können Sie mehrsprachig programmieren. Vielsprachige Programmierer sind keine Hasser, im Gegenteil, sie können sich mit dem besten verfügbaren Code anfreunden, egal aus welcher Kultur er stammt. Sie sind wie Studenten der Renaissance, die sofort Englisch, Französisch und Latein gelernt haben ... all diese Sprachen sind eine für sie. Sie mischen die Java-, Kotlin-, JavaScript-, Scala-, Python-, Ruby-, Lisp-, R-, Rust-, Smalltalk-, C / C ++ - und sogar FORTRAN-Bibliotheken und setzen eine saubere Ganzzahl auf GraalVM.
  • Wenn Sie ein zufriedener NodeJS-Benutzer sind und andere Sprachen Sie überhaupt nicht stören, möchten Sie möglicherweise trotzdem mit GraalVM experimentieren.

NodeJS basiert auf V8, einer virtuellen Maschine, die kurzlebige Single-Thread-Skripts verwendet, die auf PCs und Smartphones ausgeführt werden. Genau das wird von Google finanziert, aber V8 wird auch auf Servern verwendet. OpenJDK ist seit Jahrzehnten auf Servern optimiert. Die neuesten Versionen enthalten ZGC und Shenandoah , zwei Garbage Collectors, die eine minimale Latenz ermöglichen. Mit diesen Tools können Sie Terabytes an Speicher mit nur wenigen Millisekunden Pausen belegen. Daher können Sie möglicherweise sogar die Kosten senken, indem Sie die hervorragende GraalVM-Infrastruktur und -Tools verwenden , ohne auf Einsprachigkeit zu verzichten.



Zeigen Sie den Heap mit Ruby-Objekten an



Über HTTP verfügbare CPU-Metriken



Extrem fundierte Expertendiagnose, die zeigt, wie der Code optimiert wurde

Vertikale Architektur


Wir kommen zum letzten Thema, das ich in diesem Artikel diskutieren möchte.

Manchmal erzähle ich jemandem all das, aber sie antworten mir: „ Das ist großartig, aber geben uns all diese Microservices das nicht alles? Woran liegt die ganze Aufregung? Es ist schwer zu verstehen, warum ich mehrsprachiges Programmieren so sehr mag, aber es scheint mir, dass Microservice-Architekturen einen gesunden Wettbewerb brauchen.

Erstens, ja, manchmal müssen Sie viele Dienste auf einer Vielzahl von Servern betreiben, die interagieren müssen. Ich habe mehr als 7 Jahre bei Google gearbeitet und mich fast täglich mit deren Container-Orchestrator Borg auseinandergesetzt. Ich habe "microservices" geschrieben, obwohl wir sie nicht einmal angerufen haben, und sie konsumiert. Ansonsten gab es keine Möglichkeit, damit umzugehen, da für unsere Arbeitsbelastung Tausende von Maschinen erforderlich waren!

Für solche Architekturen muss man allerdings einen hohen Preis zahlen:

  1. Serialisierung Dies führt sofort zu einer Verringerung der Produktivität, erfordert jedoch vor allem, dass Sie Ihre zumindest teilweise typisierten und optimierten Datenstrukturen ständig ausrichten und in einfache Bäume verwandeln. Wenn Sie auf JSON zurückgreifen, verlieren Sie die Fähigkeit, einfache Dinge zu tun, z. B. viele kleine Objekte, die auf mehrere große Objekte verweisen (um Wiederholungen zu vermeiden, müssen Sie Ihre eigenen Indizes verwenden).
  2. Versionierung Das ist schwer. Die Universitäten unterrichten diese schwierige, aber alltägliche Disziplin im Bereich Software-Engineering oft nicht, und selbst wenn Sie der Meinung sind, dass Sie den Unterschied zwischen direkter und rückwärtiger Kompatibilität gründlich verstanden haben, können Sie garantieren, dass Sie wissen, was mehrstufiges Rollen ist dass all dies von der Person verstanden wird, die Sie ersetzen wird? Führen Sie die Integrationstests für verschiedene Versionskombinationen korrekt durch, die während des Nicht-Atom-Rollouts entstehen können? In verteilten Architekturen habe ich mehrere echte Katastrophen gesehen, die darauf hinausliefen, dass die Versionen vom rechten Weg abgekommen sind.
  3. Kohärenz . Das Ausführen atomarer Operationen auf demselben Server ist recht einfach. Es ist viel schwieriger sicherzustellen, dass Benutzer in jeder Situation ein absolut konsistentes Bild sehen, wenn viele Computer an dem System beteiligt sind, insbesondere, wenn zwischen ihnen Datenverluste auftreten. Aus diesem Grund können relationale Datenbank-Engines historisch gesehen keine gute Skalierbarkeit aufweisen. Lassen Sie mich Ihnen sagen: Die besten Google-Ingenieure haben jahrzehntelang versucht, die verteilte Programmierung für ihre Teams zu vereinfachen und sie so zu gestalten, dass sie eher einer herkömmlichen ähnelt.
  4. Neuimplementierung . Da Remoteprozeduraufrufe teuer sind, werden Sie nicht viele solcher Anrufe tätigen, und es bleibt nichts zu tun, um einige Probleme zu lösen, sondern den Code erneut zu implementieren. Google hat einige Bibliotheken für die gleichzeitige Arbeit mit mehreren Sprachen erstellt, um Remoteprozeduren aufrufen zu können. Es gibt auch Situationen, in denen ein solcher Code von Grund auf neu geschrieben werden muss.

Also, was ist die Alternative?

Einfach gesagt, viel Eisen. Diese Methode mag absurd großartig erscheinen, aber denken Sie daran, dass die Hardwarekosten ständig sinken, viele Workloads nicht als „global global“ bezeichnet werden und Ihre Intuition darüber, wofür Sie ausgeben sollten, scheitern kann.

Hier ist eine relativ neue Preisliste eines kanadischen Herstellers:



Ein vierzigkerniger Rechner mit einem Terabyte RAM und fast einem Terabyte auf der Festplatte kostet heute etwa 6.000 US-Dollar. Stellen Sie sich vor, wie viel Zeit Ihr Team für die gesamte Projektlaufzeit benötigt, um Probleme mit verteilten Systemen zu lösen, und wie viel es Sie kosten wird.

Ja, aber sind nicht alle heutigen Unternehmen global webbasiert?

Kurz gesagt, nein.

Die Welt ist voll von Unternehmen, für die Folgendes zutrifft:

  • Sie operieren in stabilen Märkten.
  • Sie verdienen, indem sie Dinge verkaufen.
  • Folglich zählt ihr Kundenstamm mehrere Zehntausend bis Zehntausend Millionen Menschen, aber nicht Milliarden.
  • Ihre Datensätze sind normalerweise mit ihren eigenen Kunden und Produkten verknüpft.

Ein gutes Beispiel für ein solches Unternehmen ist eine Bank. Banken erleben kein "Hyperwachstum", werden nicht "viral". Ihr Wachstumsmodell ist moderat und vorhersehbar, wenn man davon ausgeht, dass sie in irgendeiner Form wachsen (Banken sind regional und operieren in der Regel in gesättigten Märkten). Der Kundenstamm der größten US-Bank beträgt rund 50 Millionen Nutzer und verdoppelt sich natürlich nicht alle sechs Monate. In diesem Fall ist die Situation überhaupt nicht die gleiche wie bei Instagram. Kein Wunder also, dass der Mainframe immer noch auf einem typischen Bankensystem basiert? Gleiches gilt natürlich auch für Logistikunternehmen, Fertigungsunternehmen usw. Das ist das A und O unserer Wirtschaft.

In solchen Unternehmen ist es durchaus möglich, dass die Anforderungen jeder spezifischen Anwendung immer mit den Ressourcen einer einzigen großen Maschine erfüllt werden können. Ja, sogar einige öffentliche Sites passen heute auf einen einzelnen Computer. Im Jahr 2015 hielt Maciej Tseglovsky einen sehr interessanten Vortrag zum Thema „ Die Krise mit der Fettleibigkeit von Websites “ und stellte fest, dass seine eigene Website mit Lesezeichen-Service rentabel war, sein Konkurrent jedoch dieselbe Website auf AWS veröffentlichte - und nur aufgrund unterschiedlicher Kosten verlor Ausrüstung und verschiedene Annahmen über die Komplexität. In einer Studie zum Vergleich der vertikalen und horizontalen Skalierung wurde festgestellt, dass PlentyOfFish auf ca. einem Megaserver ausgeführt wird (der Artikel stammt aus dem Jahr 2009, sodass Sie die dort aufgeführten Gerätepreise ignorieren können). Der Autor führt einige Berechnungen durch und zeigt, dass ein Server nicht so dumm ist, wie es scheint. Wenn Sie sich für Hadoop und Big Data entscheiden, lesen Sie diesen Microsoft-Forschungsartikel aus dem Jahr 2013, in dem gezeigt wird, dass viele der Hadoop-Workloads von Microsoft, Yahoo und Facebook auf einem einzelnen großen Computer wesentlich schneller und effizienter ausgeführt werden als auf einem Cluster. Und so war es vor 6 Jahren! Es ist wahrscheinlich, dass seitdem die Betonung der vertikalen Skalierung noch deutlicher geworden ist.

Die wirklichen Einsparungen sind jedoch keineswegs mit der Ausrüstung verbunden, sondern mit der Optimierung der extrem kostspieligen Arbeitszeit der Ingenieure, die für die Erstellung einer Reihe winziger Mikrodienste aufgewendet wird, die mit einem elastischen Nachfragemanagement horizontal skaliert werden müssen. Solch eine Herangehensweise an das Engineering ist riskant und zeitaufwändig, selbst wenn Sie die neuesten Spielzeuge verwenden, die in der Cloud verfügbar sind. Sie können SQL, SOLID-Transaktionen und Unified Profiling verlieren, und Sie werden definitiv Informationen wie die systemübergreifende Stapelverfolgung verlieren. Die Typensicherheit verschwindet jedes Mal, wenn Sie den Server verlassen. Sie erhalten Aufrufe von Funktionen, bei denen möglicherweise eine Zeitüberschreitung eintritt, ein übermäßiger Overhead im Zusammenhang mit der dynamischen Kompilierung, unerwartete Rückstauausfälle, komplexe Orchestrierungs-Engines mit ausgefallenen Konfigurationsformaten und ... oh, die Erinnerungen sind wirklich überfüllt. Es war interessant, mit all dem zu arbeiten, als mir die proprietäre Google-Architektur und ein umfangreiches Engineering-Budget zur Verfügung standen, aber heute würde ich es wagen, dies nur zu wiederholen, wenn ich keine andere Wahl hätte.

Erfahrungsgemäß ist es nicht möglich, mit sehr großen Servern zu arbeiten, die mit Garbage Collection arbeiten - die Garbage Collection selbst war eine schlecht entwickelte Technologie, und daher ist dieses Thema lange Zeit rein akademisch geblieben. In jedem Fall mussten Sie mehrere Server gleichzeitig fahren. , ZGC Shenandoah, , 80 – . , -, – , .

, ? – , ? – : , , … .

Fazit


NodeJVM – , GraalVM. NPM Java/Kotlin, JS , Kotlin JavaScript, JS , V8.

, JS Java, Java JS .

, 4 , – – , . .

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


All Articles