Mélange d'OpenJDK et de NodeJS: interactions inter-langages et architecture verticale

Bonjour, Habr!

Pendant longtemps, j'ai eu l'idée de discuter de GraalVM avec vous, reportée jusqu'à ce que l'on trouve enfin l'article d'aujourd'hui, dont le sujet dépasse sérieusement le cadre de l'analyse d'une machine virtuelle spécifique. L'auteur Mike Hearn décrit tout le paradigme de l'interaction interlangue et de la programmation multilingue (programmation polyglotte). Vient ensuite le fameux exemple de mise à l'échelle verticale et un très long article sous la coupe.



Cet article concerne une façon innovante d'écrire des logiciels qui pourraient devenir populaires à l'avenir, mais probablement pas maintenant. L'article a un code, honnêtement!

Dans les temps anciens, c'est en 2015, j'ai écrit pourquoi Kotlin sera mon prochain langage de programmation , et en 2016 j'ai écrit sur Graal et Truffle : deux projets de recherche radicaux liés aux compilateurs qui accélèrent non seulement de manière significative le travail de langages tels que Ruby , mais incarnent également en réalité des interactions interlangues sans faille. Dans ces projets, le compilateur dynamique (JIT) ou OpenJDK est remplacé par un nouveau qui a la capacité de transformer automatiquement les interprètes annotés en compilateurs JIT de pointe ...

Revenant sur ces sujets en 2019, je voudrais vous montrer trois choses:

  1. Comment utiliser la petite bibliothèque que j'ai écrite pour utiliser de façon presque transparente les modules NPM à partir du code des programmes écrits en Java ou Kotlin.
  2. Expliquez toutes les bonnes raisons pour lesquelles vous pourriez en avoir besoin, même si vous pensez que JavaScript / Java est la pire chose au monde, sans compter l'huile de poisson.
  3. Explorez brièvement le concept d'architecture verticale qui rivalise avec la conception orientée microservices. Il est situé à l'intersection des dernières versions de GraalVM et OpenJDK, et nécessite le matériel le plus avancé.

Utilisation des NPM de Java et Kotlin


Nous prendrons seulement trois étapes simples:

  1. Prenez GraalVM . Il s'agit d'un ensemble de correctifs, construit au-dessus d'OpenJDK, qui est apparu juste à temps: il peut exécuter tout le code d'octet JVM dont vous disposez.
  2. Nous prenons ma boîte à outils NodeJVM de github et l'ajoutons à notre chemin.
  3. Remplacez java sur la ligne de commande par nodejvm . C'est tout!

D'accord, d'accord. J'avoue, ici je dessine un peu et exagère, jusqu'à la fin de l'article vous devrez endurer un tel style. Bien sûr, tout n'est pas si simple du tout: vous devez toujours prendre le module et l'utiliser.

Considérez à quoi il ressemble:



Exemple de code à l'aide de NodeJVM

Regardez bien cette photo. Oui, c'est exactement ce à quoi cela ressemble: Kotlin avec une chaîne multi-lignes intégrée dans laquelle l'auto-complétion JavaScript se produit, après quoi une analyse JavaScript statique est effectuée et la syntaxe est correctement mise en évidence. Les mêmes opérations fonctionnent à partir de Java ou d'autres langages pour la machine virtuelle Java qu'IntelliJ comprend. Pour obtenir de telles opportunités, vous devez cliquer sur le commutateur dans les paramètres IDE (lisez le fichier Lisezmoi de NodeJVM pour savoir comment procéder), mais plus tard, cette fonction fonctionnera automatiquement. Si IntelliJ parvient à comprendre en analysant le flux de données que votre chaîne devrait finalement être transmise aux méthodes run ou eval , elle sera alors traitée comme JS intégrée.

Ici, je vais parler de l'API pour Kotlin, car elle est un peu plus jolie et plus pratique que l'API en Java normal, mais tout ce que je décris ci-dessous peut également être fait à partir de Java.

Dans le code ci-dessus, faites attention à plusieurs des fonctionnalités suivantes:

  • Pour accéder à JavaScript, vous devez utiliser le bloc nodejs {} . Le fait est que JavaScript est monothread et, par conséquent, pour exécuter des modules NPM, vous devez «entrer dans le flux de nœuds». Le bloc nodejs {} effectue une telle synchronisation pour nous, quel que soit le thread dans lequel nous nous trouvons. Donc, vous devez vous rappeler constamment: pour exécuter n'importe quel code JS, vous devez en principe être à l'intérieur d'un tel bloc. Vous pouvez le ressaisir autant de fois que vous le souhaitez, il sera donc sûr d'utiliser un tel bloc partout où nous en avons besoin. Tous les rappels JavaScript seront exécutés dans le thread Node, et donc, tous les autres threads se verront refuser l'accès au bloc nodejs , donc si vous êtes préoccupé par les performances ou le rendu de l'interface graphique, évitez d'effectuer des opérations de longue durée dans les rappels.
  • La syntaxe var x by bind(SomeObject()) est disponible uniquement dans le bloc nodejs et vous permet de vous connecter à la même variable dans la portée JavaScript globale. Lorsque x change de Kotlin, il changera dans JS et vice versa. Ici, j'attache un objet Java File ordinaire au monde JS.
  • La méthode eval renvoie ... ce que nous lui demandons de renvoyer, cependant, à la manière d'un typage statique. Il s'agit d'une fonction générique et, en spécifiant simplement le type d'entité que nous lui attribuons, nous nous assurerons que eval convertit automatiquement l'objet JavaScript en une classe typée statiquement ou en interface Java / Kotlin / Scala / etc. Bien que cela n'ait pas été explicitement indiqué ci-dessus, MemoryUsage est un type d'interface simple que j'ai défini, et il a les fonctions rss() et heapTotal() . Ils sont mappés aux propriétés JavaScript du même nom, en les appliquant à ce que vous obtenez à partir de l'API Node process.memoryUsage() . La plupart des types JS peuvent ainsi être convertis en types Java «normaux»; une documentation détaillée sur son fonctionnement est disponible sur le site Web de GraalVM. Les objets résultants peuvent être stockés n'importe où, mais la méthode les appelle, bien sûr, doit être effectuée dans le bloc nodejs .
  • Les objets JavaScript peuvent également être considérés comme de simples mappages d'une chaîne à un objet, ce qui correspond à bien des égards à leur nature. À leur tour, de tels mappages d'une chaîne à un objet peuvent être ramenés à quelque chose de plus fort, qui peut être clairement vu dans le rappel. Utilisez la présentation que vous préférez.
  • Vous pouvez utiliser require et il recherchera les modules dans les répertoires node_modules de la manière habituelle.

L'extrait de code ci-dessus utilise le protocole DAT , qui vous permet de vous connecter à un réseau homologue à distance ressemblant à BitTorrent, puis de rechercher des homologues qui ont le fichier souhaité. J'utilise DAT comme exemple, car il est (a) décentralisé et donc uniquement chic et (b) pour le meilleur ou pour le pire, son implémentation de référence est écrite en JavaScript. Ce n'est pas un programme que je pourrais écrire complètement sans utiliser JS dans un délai raisonnable.

Cela peut également être fait à partir de Java:

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

L'API Java ne vous fournit pas une liaison variable et une conversion automatique aussi agréables que l'API Kotlin, mais elle est assez facile à utiliser. Ici, nous montrons comment nous convertissons le résultat en un type entier Java ( int ) et le renvoyons «à partir» du flux Node: dans ce cas, le flux Java principal n'est pas le même que le flux NodeJS, mais nous basculons entre ces flux de manière totalement transparente.

NodeJVM est un très, très petit wrapper au-dessus de GraalVM . Il ajoute une quantité insignifiante de code, alors ne vous inquiétez pas qu'il puisse cesser d'être pris en charge ou disparaître: 99,99% de tout le travail acharné dans ce cas est effectué par l'équipe GraalVM.

Voici quelques idées évidentes pour suggérer des améliorations:

  • Autoriser les modules JS à importer des modules Java par coordonnées Maven.
  • Formuler quelques «meilleures pratiques» pour la javisation des modules NPM. Par exemple, un fichier JAR peut-il contenir un répertoire node_modules (en bref: non, puisque NodeJS organise toujours les E / S de fichiers à sa manière et ne sait rien des zips, long: oui, si vous essayez dur).
  • Plus de langages: Python et Ruby n'ont pas besoin de la «colle» pour la synchronisation des threads qui est nécessaire dans NodeJS, vous pouvez donc simplement utiliser l' API GraalVM Polyglot régulière. Mais les utilisateurs de Kotlin trouveront que les méthodes de cast / extension et les API pour les variables de liaison seraient bien d'avoir dans n'importe quel langage.
  • Prise en charge de Windows.
  • Plugin Gradle pour que les programmes puissent avoir des listes de dépendances dans un ensemble mixte de langues
  • Intégration avec l'outil d' native-image , le soi-disant SubstrateVM; donc si vous n'avez pas besoin des performances complètes de HotSpot au moment de l'exécution, vous pouvez fournir de petits binaires liés statiquement dans le style de Golang.
  • Peut-être une sorte de convertisseur pour convertir TypeScript en Java, afin que vous puissiez utiliser DefinitelyTyped et plonger rapidement dans le monde statique.

Les correctifs sont les bienvenus.

Pourquoi en auriez-vous besoin?


Peut-être que vous pensez déjà: "Wow, JavaScript, nous, développés, pouvons maintenant être dans l'amour mutuel, le respect mutuel et l'harmonie!"



Réaction enthousiaste idéalisée

Il est très possible que vous soyez plus proche de ce point de vue:



JavaScript et Java ne sont pas seulement des langages. Ce sont des cultures, et rien n'est aussi doux pour les développeurs que CULTURAL WARS!

C'est pourquoi vous devriez au moins mettre cette page en signet pour référence future, même si vous voulez juste saisir l'arme avec la simple pensée que $ OTHER_LANG envahit votre précieux écosystème:

  • Si vous êtes principalement un développeur Java , vous avez maintenant accès à des modules JavaScript uniques qui peuvent ne pas avoir d'équivalent dans la JVM (par exemple, le protocole DAT). Vous pouvez l'adorer ou le détester, mais le fait demeure: beaucoup de gens écrivent des modules NPM open source, et certains de ces modules sont très bons. Vous pouvez également réutiliser du code qui s'exécute sur vos interfaces Web sans avoir besoin de transporteurs de langue. Et si vous travaillez avec une base de code héritée dans NodeJS, que vous aimeriez progressivement porter sur Java, alors tout à coup, ce travail est grandement simplifié.
  • Si vous êtes principalement un développeur JavaScript , vous avez maintenant un accès facile à des bibliothèques JVM uniques, qui en JavaScript peuvent ne pas avoir d'équivalent direct (par exemple Lucene , Chronicle Map ) ou peuvent proposer uniquement des analogues mal documentés, immatures ou moins productifs . Si vous voulez vous passer de HTML dans votre prochain projet, vous pouvez explorer le cadre GUI pour une personne blanche . Vous avez également accès à de nombreux autres langages , par exemple, les objets JVM Ruby et R. peuvent être partagés entre les employés de NodeJS, en profitant du multithreading dans la mémoire partagée , si, selon votre profileur, cette opportunité peut être utilisée. Et si vous travaillez avec une base héritée de code Java que vous aimeriez progressivement porter sur NodeJS, alors tout à coup, ce travail est grandement simplifié.
  • Si vous apprenez toutes les langues en même temps , vous pouvez faire une programmation multilingue. Les programmeurs polyglottes ne sont pas des haineux, au contraire, ils peuvent se faire des amis avec le meilleur code disponible, quelle que soit leur culture. Ils sont comme des étudiants de la renaissance qui ont immédiatement étudié l'anglais, le latin français ... toutes ces langues sont une pour eux. Ils mélangent les bibliothèques Java, Kotlin, JavaScript, Scala, Python, Ruby, Lisp, R, Rust, Smalltalk, C / C ++ et même FORTRAN, assemblant purement un entier soigné au-dessus de GraalVM.
  • Enfin, si vous êtes un utilisateur satisfait de NodeJS et que d'autres langues ne vous dérangent pas du tout, vous voudrez peut-être toujours expérimenter GraalVM.

NodeJS est basé sur V8, une machine virtuelle conçue pour utiliser des scripts monothreads de courte durée qui s'exécutent sur des PC et des smartphones. C'est exactement ce qui est financé par Google, mais V8 est également utilisé sur les serveurs. OpenJDK est optimisé depuis des décennies sur les serveurs. Les dernières versions contiennent ZGC et Shenandoah , deux garbage collector qui permettent une latence minimale, des outils qui vous permettent de consommer des téraoctets de mémoire, avec seulement quelques millisecondes de pauses. Par conséquent, vous pouvez même être en mesure de réduire les coûts en utilisant l' excellente infrastructure et les outils GraalVM , sans même abandonner le monolinguisme.



Afficher le tas contenant des objets Ruby



Mesures du processeur disponibles via HTTP



Diagnostic expert ultra-profond démontrant comment le code a été optimisé

Architecture verticale


Nous arrivons au dernier sujet que je voudrais aborder dans cet article.

Parfois, je dis à quelqu'un tout ce qui précède, mais ils me répondent: « C'est génial, mais tous ces microservices ne nous donnent-ils pas tout cela? Pourquoi tout ce bruit? Il est difficile de comprendre pourquoi j'aime tant la programmation multilingue, mais le fait est qu'il me semble que les architectures de microservices ont besoin d'une concurrence saine.

Tout d'abord, oui, vous devez parfois piloter de nombreux services sur une variété de serveurs qui doivent interagir. J'ai travaillé pendant plus de 7 ans chez Google, presque quotidiennement avec leur orchestrateur de conteneurs Borg. J'ai écrit des «microservices», même si nous ne les appelions même pas, et je les ai consommés. Sinon, il n'y avait aucun moyen de faire face, car nos charges de travail nécessitaient la participation de milliers de machines!

Cependant, pour de telles architectures, vous devez payer un prix élevé:

  1. Sérialisation Il en résulte immédiatement une diminution de la productivité, mais, plus important encore, vous oblige à aligner en permanence vos structures de données au moins partiellement typées et optimisées, en les transformant en arbres simples. Lorsque vous utilisez JSON, vous perdez la possibilité de faire des choses simples, par exemple, pour avoir de nombreux petits objets qui pointent vers plusieurs gros objets (pour éviter la répétition, vous devez utiliser vos propres index).
  2. Versioning C'est compliqué. Les universités n'enseignent souvent pas cette discipline difficile mais quotidienne dans le domaine du génie logiciel et même si vous pensez que vous avez bien compris la différence entre la compatibilité directe et la compatibilité descendante, même si vous êtes sûr de comprendre ce qu'est le roulement en plusieurs étapes, pouvez-vous garantir que tout cela sera compris par la personne qui vous remplacera? Effectuez-vous correctement les tests d'intégration pour diverses combinaisons de versions qui peuvent se développer lors du déploiement non atomique? Dans les architectures distribuées, j'ai vu plusieurs véritables catastrophes qui se résumaient au fait que les versions se sont égarées.
  3. Cohérence . La réalisation d'opérations atomiques sur le même serveur est assez simple. Il est beaucoup plus difficile de garantir que les utilisateurs dans n'importe quelle situation voient une image absolument cohérente lorsque de nombreuses machines sont impliquées dans le système, en particulier si le partage des données se produit entre elles. C'est pourquoi, historiquement, les moteurs de bases de données relationnelles ne peuvent pas se targuer d'une bonne évolutivité. Permettez-moi de vous dire: les meilleurs ingénieurs de Google ont passé des décennies à essayer de simplifier la programmation distribuée pour leurs équipes, à essayer de la construire pour qu'elle ressemble davantage à une programmation traditionnelle.
  4. Réimplémentation . Étant donné que les appels de procédure à distance sont coûteux, vous ne passerez pas beaucoup d'appels de ce type et il n'y a plus rien à faire pour résoudre certains problèmes, mais réimplémentez le code. Google a créé des bibliothèques pour travailler avec plusieurs langues à la fois, conçues pour appeler des procédures à distance; Il existe également des situations dans lesquelles ce code doit être réécrit à partir de zéro.

Alors, quelle est l'alternative?

Autrement dit, beaucoup de fer. Cette méthode peut sembler absurdement grand-père, mais gardez à l'esprit que le coût du matériel diminue constamment, de nombreuses charges de travail ne sont pas appelées «globales globales», et votre intuition sur ce que vous devez dépenser peut vous échouer.

Voici une liste de prix relativement récente d'un fabricant canadien:



Une machine de quarante-nucléaire avec un téraoctet de RAM et presque un téraoctet sur le disque dur coûte aujourd'hui environ 6 000 $. Imaginez combien de temps pendant toute la durée de vie du projet votre équipe devra résoudre les problèmes avec les systèmes distribués, et combien cela vous coûtera.

Oui, mais toutes les entreprises d'aujourd'hui ne sont-elles pas basées sur le Web?

Bref, non.

Le monde regorge d'entreprises pour lesquelles ce qui suit est vrai:

  • Ils opèrent sur des marchés stables.
  • Ils gagnent en vendant des choses.
  • Par conséquent, leur clientèle est de plusieurs dizaines de milliers à plusieurs dizaines de millions de personnes, mais pas par milliards.
  • Leurs ensembles de données sont généralement associés à leurs propres clients et produits.

Un bon exemple d'une telle entreprise est une banque. Les banques ne connaissent pas "d'hyper croissance", ne deviennent pas "virales". Leur modèle de croissance est modéré et prévisible, si l'on suppose qu'ils ont une croissance quelconque (les banques sont régionales et opèrent généralement sur des marchés saturés). La clientèle de la plus grande banque américaine est d'environ 50 millions d'utilisateurs et, bien sûr, ne double pas tous les six mois. Dans ce cas, la situation n'est pas du tout la même qu'avec Instagram. Faut-il donc s'étonner que le mainframe soit toujours basé sur un système bancaire typique? Bien sûr, il en va de même pour les entreprises de logistique, les entreprises manufacturières, etc. C'est le pain et le beurre de notre économie.

Dans ces entreprises, il est tout à fait possible que les besoins de chaque application spécifique qui leur est liée puissent toujours être satisfaits avec les ressources d'une seule grande machine. Oui, même aujourd'hui, certains sites publics tiennent sur une seule machine. En 2015, Maciej Tseglovsky a donné une conférence très intéressante sur « La crise de l'obésité des sites » et a noté que son propre site avec service de bookmarking était rentable, mais son concurrent a posté le même site sur AWS - et perdu, uniquement en raison de coûts différents équipement et diverses hypothèses sur la complexité. Dans une étude sur la comparaison de la mise à l'échelle verticale et horizontale, il a été constaté que PlentyOfFish fonctionne sur environ un mégaserveur (l'article date de 2009, vous pouvez donc ignorer les prix des équipements qui y sont répertoriés). L'auteur fait quelques calculs et montre qu'un serveur n'est pas aussi stupide que cela puisse paraître. Enfin, si vous envisagez Hadoop et le Big Data, consultez cet article de recherche Microsoft 2013, qui démontre que de nombreuses charges de travail Hadoop de Microsoft, Yahoo et Facebook s'exécutent en réalité beaucoup plus rapidement et plus efficacement sur une seule grande machine, plutôt que sur un cluster. Et c'était ainsi il y a 6 ans! Il est probable que, depuis lors, l'accent mis sur la mise à l'échelle verticale soit devenu encore plus prononcé.

Cependant, les économies réelles ne sont pas du tout liées à l'équipement, mais à l'optimisation du temps de travail extrêmement coûteux des ingénieurs, qui est consacré à la création d'un tas de microservices minuscules qui doivent être mis à l'échelle horizontalement avec une gestion de la demande élastique. Une telle approche de l'ingénierie est risquée et prend du temps, même si vous utilisez les derniers jouets disponibles dans le cloud. Vous pouvez perdre SQL, les transactions SOLIDES, le profilage unifié et vous perdrez certainement des informations telles que le traçage de pile intersystème. La sécurité de type disparaîtra chaque fois que vous irez au-delà du serveur. Vous recevrez des appels à des fonctions qui peuvent expirer, une surcharge excessive associée à une compilation dynamique, des échecs de contre-pression inattendus, des moteurs d'orchestration complexes avec des formats de configuration sophistiqués, et ... oh, les souvenirs sont vraiment inondés. C'était intéressant de travailler avec tout cela lorsque j'avais l'architecture propriétaire de Google et un budget d'ingénierie épais à ma disposition, mais aujourd'hui j'oserais répéter cela uniquement si je n'avais pas d'autre choix.

Selon l'expérience, il est impossible de travailler avec de très grands serveurs qui fonctionnent sur la collecte des ordures - le fait est que la collecte des ordures elle-même était une technologie peu développée, et donc ce sujet est resté longtemps purement académique. Dans tous les cas, vous deviez piloter plusieurs serveurs à la fois. , ZGC Shenandoah, , 80 – . , -, – , .

, ? – , ? – : , , … .

Conclusion


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

, JS Java, Java JS .

, 4 , – – , . .

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


All Articles