Halo, Habr!
Untuk waktu yang lama saya punya ide untuk mendiskusikan
GraalVM dengan Anda, ditunda sampai akhirnya menemukan artikel hari ini, subjek yang serius melampaui lingkup analisis mesin virtual tertentu. Penulis Mike Hearn menguraikan seluruh paradigma interaksi multibahasa dan pemrograman multibahasa (pemrograman polyglot). Berikutnya adalah contoh terkenal tentang penskalaan vertikal dan artikel yang sangat panjang di bawah potongan.

Artikel ini adalah tentang cara inovatif menulis perangkat lunak yang mungkin menjadi populer di masa depan, tetapi mungkin tidak sekarang. Artikel punya kode, jujur!
Di zaman kuno, yaitu, pada tahun 2015, saya menulis
mengapa Kotlin akan menjadi bahasa pemrograman saya berikutnya , dan pada tahun 2016 saya menulis tentang
Graal dan Truffle : dua proyek penelitian radikal terkait dengan kompiler yang tidak hanya secara signifikan mempercepat pekerjaan bahasa seperti Ruby , tetapi juga mewujudkan interaksi antarbahasa yang mulus. Dalam proyek-proyek ini, kompiler dinamis (JIT) atau OpenJDK diganti dengan yang baru yang memiliki kemampuan untuk mengubah penerjemah beranotasi menjadi kompiler JIT canggih ... secara otomatis.
Kembali ke topik ini pada tahun 2019, saya ingin menunjukkan kepada Anda tiga hal:
- Bagaimana cara menggunakan perpustakaan kecil yang saya tulis untuk hampir secara mulus menggunakan modul NPM dari kode program yang ditulis dalam Java atau Kotlin.
- Jelaskan semua alasan bagus mengapa Anda mungkin membutuhkannya, bahkan jika Anda menganggap JavaScript / Java adalah hal terburuk di dunia, tidak termasuk minyak ikan.
- Jelajahi konsep arsitektur vertikal secara singkat yang bersaing dengan desain berorientasi layanan-mikro. Itu terletak di persimpangan versi terbaru GraalVM dan OpenJDK, dan membutuhkan perangkat keras paling canggih.
Menggunakan NPM dari Jawa dan Kotlin
Kami hanya akan mengambil tiga langkah sederhana:
- Ambil GraalVM . Ini adalah satu set tambalan, dibangun di atas OpenJDK, yang muncul tepat pada waktunya: ia dapat menjalankan semua kode byte JVM yang Anda miliki.
- Kami mengambil toolkit NodeJVM saya dari github dan menambahkannya ke jalur kami.
- Ganti
java
pada baris perintah dengan nodejvm
. Itu saja!
Baik, baik. Saya akui, di sini saya menggambar sedikit dan membesar-besarkan, sampai akhir artikel Anda harus menanggung gaya seperti itu. Tentu saja, semuanya tidak begitu sederhana sama sekali: Anda masih harus mengambil modul dan menggunakannya.
Pertimbangkan seperti apa tampilannya:

Kode Sampel Menggunakan NodeJVM
Perhatikan gambar ini dengan seksama. Ya, seperti itulah tampilannya: Kotlin dengan string multi-baris bawaan tempat pelengkapan otomatis JavaScript terjadi, setelah itu dilakukan analisis JavaScript statis, dan sintaksinya disorot dengan benar. Operasi yang sama berfungsi dari Jawa atau bahasa lain untuk JVM yang dipahami IntelliJ. Untuk mendapatkan peluang seperti itu, Anda perlu mengklik sakelar di pengaturan IDE (baca readme untuk NodeJVM untuk cara melakukannya), tetapi nanti fungsi ini akan bekerja secara otomatis. Jika IntelliJ berhasil mencari tahu dengan menganalisis aliran data yang pada akhirnya string Anda akan diteruskan ke metode
run
atau
eval
, maka itu akan diperlakukan sebagai JS yang disematkan.
Di sini saya akan berbicara tentang API untuk Kotlin, karena ini sedikit lebih cantik dan lebih nyaman daripada API di Jawa normal, tetapi semua yang saya jelaskan di bawah ini juga dapat dilakukan dari Jawa.
Dalam kode di atas, perhatikan beberapa fitur berikut:
- Untuk mengakses JavaScript, Anda harus menggunakan blok
nodejs {}
. Faktanya adalah bahwa JavaScript adalah single-threaded dan, karenanya, untuk menjalankan modul NPM Anda perlu "memasuki aliran Node". nodejs {}
melakukan sinkronisasi seperti itu untuk kami, apa pun nodejs {}
. Jadi, Anda harus selalu ingat: untuk menjalankan kode JS, pada prinsipnya, Anda harus berada di dalam blok tersebut. Anda dapat memasukkannya kembali sesering yang Anda mau, sehingga akan aman untuk menggunakan blok seperti itu di tempat mana pun kami membutuhkannya. Setiap panggilan balik JavaScript akan dieksekusi di utas Node, dan dengan demikian, semua utas lainnya akan ditolak aksesnya ke blok nodejs
, jadi jika Anda khawatir tentang kinerja atau kelancaran rendering GUI, maka hindari melakukan operasi jangka panjang dalam callback. - Sintaks
var x by bind(SomeObject())
hanya tersedia di blok nodejs
dan memungkinkan Anda untuk terhubung ke variabel yang sama dalam lingkup JavaScript global. Ketika x berubah dari Kotlin, itu akan berubah di JS dan sebaliknya. Di sini saya melampirkan objek Java File
biasa ke dunia JS. - Metode
eval
mengembalikan ... apa yang kita minta untuk dikembalikan, dengan cara mengetik statis. Ini adalah fungsi generik dan, hanya dengan menentukan jenis entitas yang kami tetapkan untuk itu, kami akan memastikan bahwa eval secara otomatis melemparkan objek JavaScript ke kelas yang diketik secara statis atau antarmuka Java / Kotlin / Scala / etc. Meskipun ini tidak secara eksplisit dinyatakan di atas, MemoryUsage
adalah tipe antarmuka sederhana yang saya definisikan, dan memiliki fungsi rss()
dan heapTotal()
. Mereka memetakan ke properti JavaScript dengan nama yang sama, menerapkannya pada apa yang Anda dapatkan dari proses Node process.memoryUsage()
. Sebagian besar tipe JS dengan demikian dapat dikonversi ke tipe Java "normal"; dokumentasi terperinci tentang cara kerjanya tersedia di situs web GraalVM. Objek yang dihasilkan dapat disimpan di mana saja, tetapi panggilan metode di dalamnya, tentu saja, harus dilakukan di blok nodejs
. - Objek JavaScript juga dapat dianggap pemetaan sederhana dari string ke objek, yang dalam banyak hal benar-benar cocok dengan sifatnya. Pada gilirannya, pemetaan string ke suatu objek dapat dibawa kembali ke beberapa hal yang diketik lebih kuat, yang dapat dilihat dengan jelas di callback. Gunakan presentasi yang Anda sukai.
- Anda dapat menggunakan
node_modules
dan itu akan mencari modul di direktori node_modules
dengan cara biasa.
Cuplikan kode di atas menggunakan protokol
DAT , yang memungkinkan Anda untuk terhubung ke jaringan peer-to-peer yang mirip BitTorrent, dan kemudian mencari rekan yang memiliki file yang diinginkan. Saya menggunakan DAT sebagai contoh, karena ini adalah (a) terdesentralisasi dan oleh karena itu sangat unik dan (b) baik atau buruk, implementasi rujukannya ditulis dalam JavaScript. Ini bukan program yang bisa saya tulis sepenuhnya tanpa menggunakan JS pada waktu yang wajar.
Ini juga dapat dilakukan dari Jawa:
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); } }
Java API tidak memberi Anda pengikatan variabel dan auto-casting yang menyenangkan seperti API Kotlin, tetapi cukup mudah digunakan. Di sini kami menunjukkan bagaimana kami mengonversi hasilnya menjadi tipe integer Java (
int
) dan mengembalikannya "dari" aliran Node: dalam hal ini, aliran Java utama
tidak sama dengan aliran NodeJS, tetapi kami beralih di antara aliran ini sepenuhnya dengan mulus.
NodeJVM adalah pembungkus yang sangat, sangat kecil di atas
GraalVM . Itu menambah jumlah kode yang tidak signifikan, jadi jangan khawatir bahwa kode tersebut akan berhenti didukung atau dihilangkan: 99,99% dari semua kerja keras dalam hal ini dilakukan oleh tim GraalVM.
Berikut adalah beberapa ide yang jelas untuk menyarankan peningkatan:
- Izinkan modul JS mengimpor modul Java dengan koordinat Maven.
- Untuk merumuskan beberapa "praktik terbaik" untuk penyatuan modul NPM. Misalnya, dapat file JAR berisi direktori node_modules (singkatnya: tidak, karena NodeJS masih mengatur file I / O dengan caranya sendiri dan tidak tahu apa-apa tentang ritsleting, lama: ya, jika Anda berusaha keras).
- Lebih banyak bahasa: Python dan Ruby tidak memerlukan "lem" untuk sinkronisasi utas yang diperlukan di NodeJS, jadi Anda bisa menggunakan GraalVM Polyglot API biasa . Tetapi pengguna Kotlin akan menemukan bahwa metode cor / ekstensi dan API untuk variabel pengikatan akan menyenangkan untuk dimiliki dalam bahasa apa pun.
- Dukungan Windows.
- Plugin Gradle sehingga program dapat memiliki daftar ketergantungan dalam serangkaian bahasa campuran
- Integrasi dengan alat
native-image
, yang disebut SubstrateVM; jadi jika Anda tidak memerlukan kinerja penuh HotSpot saat runtime, Anda dapat menyediakan binari kecil yang terhubung secara statis dalam gaya Golang. - Mungkin semacam konverter untuk mengubah TypeScript ke Java, sehingga Anda dapat menggunakan DefinitelyTyped dan dengan cepat terjun ke dunia statis.
Tambalan dipersilakan.
Mengapa Anda membutuhkan ini?
Mungkin Anda sudah berpikir: "Wow, JavaScript, kami, maju, sekarang bisa saling mencintai, saling menghormati, dan harmoni!"

Reaksi Antusias yang Diidealkan
Sangat mungkin bahwa Anda akan lebih dekat ke sudut pandang ini:

JavaScript dan Java bukan hanya bahasa. Ini adalah budaya, dan tidak ada yang manis bagi pengembang seperti PERANG BUDAYA!
Itulah mengapa Anda setidaknya harus membookmark halaman ini untuk referensi di masa mendatang, bahkan jika Anda hanya ingin mengambil senjata dengan pertimbangan $ OTHER_LANG menginvasi ekosistem berharga Anda:
- Jika Anda terutama pengembang Java , sekarang Anda memiliki akses ke modul JavaScript unik yang mungkin tidak memiliki yang setara di JVM (misalnya, protokol DAT). Anda dapat memujanya atau membencinya, tetapi faktanya tetap ada: banyak orang menulis modul NPM open source, dan beberapa modul ini sangat bagus. Anda juga dapat menggunakan kembali kode yang berjalan di frontend web Anda tanpa perlu pengangkut bahasa. Dan jika Anda bekerja dengan basis kode yang diwarisi di NodeJS, yang Anda ingin secara bertahap port ke Jawa, maka tiba-tiba pekerjaan ini sangat disederhanakan.
- Jika Anda terutama adalah pengembang JavaScript , sekarang Anda memiliki akses mudah ke pustaka JVM unik, yang dalam JavaScript mungkin tidak memiliki padanan langsung (mis. Lucene , Chronicle Map ) atau mungkin menawarkan hanya analog yang didokumentasikan dengan buruk, tidak matang atau kurang produktif. . Jika Anda ingin melakukannya tanpa HTML di proyek berikutnya, maka Anda dapat menjelajahi kerangka kerja GUI untuk orang kulit putih . Anda juga mendapatkan akses ke banyak bahasa lain , misalnya, objek Ruby dan R. JVM dapat dibagi antara karyawan NodeJS, mengambil keuntungan dari multithreading dalam memori bersama , jika, menurut profiler Anda, peluang ini dapat digunakan. Dan jika Anda bekerja dengan basis kode Java yang diwarisi yang Anda ingin secara bertahap port ke NodeJS, maka tiba-tiba pekerjaan ini sangat disederhanakan.
- Jika Anda mempelajari semua bahasa sekaligus , maka Anda dapat melakukan pemrograman multibahasa. Pemrogram Polyglot bukan pembenci, sebaliknya, mereka dapat berteman dengan kode terbaik yang tersedia, tidak peduli dari mana budaya itu berasal. Mereka seperti mahasiswa kebangkitan yang segera belajar bahasa Inggris, Perancis Latin ... semua bahasa ini adalah satu untuk mereka. Mereka mencampur Java, Kotlin, JavaScript, Scala, Python, Ruby, Lisp, R, Rust, Smalltalk, C / C ++, dan bahkan pustaka FORTRAN, murni menjahit integer yang rapi di atas GraalVM.
- Akhirnya, jika Anda adalah pengguna NodeJS yang bahagia, dan bahasa lain tidak mengganggu Anda sama sekali, maka Anda mungkin masih ingin bereksperimen dengan GraalVM.
NodeJS didasarkan pada V8, mesin virtual yang dirancang untuk menggunakan skrip single-threaded berumur pendek yang berjalan pada PC dan smartphone. Ini persis seperti apa yang didanai oleh Google, tetapi V8 juga digunakan di server. OpenJDK telah dioptimalkan selama beberapa dekade di server. Versi terbaru berisi
ZGC dan
Shenandoah , dua pengumpul sampah yang memungkinkan latensi minimal, alat yang memungkinkan Anda untuk mengkonsumsi memori terabyte, dengan hanya beberapa milidetik jeda. Karena itu, Anda bahkan dapat mengurangi biaya dengan menggunakan
infrastruktur dan alat GraalVM yang sangat baik , tanpa meninggalkan monolingualisme.

Lihat tumpukan yang berisi objek Ruby

Metrik CPU Tersedia Melalui HTTP

Diagnostik pakar yang sangat mendalam menunjukkan bagaimana kode dioptimalkan
Arsitektur vertikal
Kita sampai pada topik terakhir yang ingin saya bahas dalam artikel ini.
Kadang-kadang saya memberi tahu seseorang semua hal di atas, tetapi mereka menjawab saya: “
Ini bagus, tetapi tidak semua layanan mikro ini memberi kita semua ini? Untuk apa semua ini? Sulit untuk menghargai mengapa saya sangat menyukai pemrograman multibahasa, tetapi masalahnya, menurut saya arsitektur layanan-mikro memerlukan persaingan yang sehat.
Pertama, ya, kadang-kadang Anda perlu mengarahkan banyak layanan di berbagai server yang perlu berinteraksi. Saya bekerja selama lebih dari 7 tahun di Google, hampir setiap hari berurusan dengan orkestra kontainer mereka, Borg. Saya menulis "layanan mikro", meskipun kami bahkan tidak memanggilnya, dan saya mengkonsumsinya. Kalau tidak, tidak ada cara untuk mengatasinya, karena beban kerja kami membutuhkan partisipasi ribuan mesin!
Namun, untuk arsitektur seperti itu Anda harus membayar mahal:
- Serialisasi Ini segera menghasilkan penurunan kinerja, tetapi, yang lebih penting, mengharuskan Anda untuk terus menyelaraskan setidaknya Anda mengetik sebagian dan struktur data yang dioptimalkan, mengubahnya menjadi pohon sederhana. Saat beralih ke JSON, Anda kehilangan kemampuan untuk melakukan hal-hal sederhana, misalnya, memiliki banyak objek kecil yang mengarah ke beberapa objek besar (untuk menghindari pengulangan, Anda harus menggunakan indeks Anda sendiri).
- Versi Ini rumit. Universitas sering tidak mengajarkan ini sulit tetapi disiplin sehari-hari di bidang rekayasa perangkat lunak dan bahkan jika Anda berpikir bahwa Anda telah benar-benar memahami perbedaan antara kompatibilitas langsung dan mundur, bahkan jika Anda yakin bahwa Anda memahami apa itu multi-stage rolling, dapatkah Anda menjamin bahwa semua ini akan dipahami oleh orang yang akan menggantikanmu? Apakah Anda melakukan pengujian integrasi dengan benar untuk berbagai kombinasi versi yang dapat berkembang selama peluncuran non-atom? Dalam arsitektur terdistribusi, saya melihat beberapa bencana asli yang bermuara pada fakta bahwa versi tersesat.
- Koherensi Melakukan operasi atom dalam server yang sama cukup mudah. Adalah jauh lebih sulit untuk memastikan bahwa pengguna dalam situasi apa pun melihat gambaran yang benar-benar konsisten ketika banyak mesin terlibat dalam sistem, terutama jika terjadi sharding data di antara mereka. Inilah sebabnya mengapa secara historis, mesin basis data relasional tidak dapat membanggakan skalabilitas yang baik. Izinkan saya memberi tahu Anda: para insinyur Google terbaik telah menghabiskan waktu puluhan tahun mencoba menyederhanakan pemrograman terdistribusi untuk tim mereka, mencoba membangunnya sehingga lebih mirip dengan yang tradisional.
- Implementasi ulang . Karena panggilan prosedur jarak jauh mahal, Anda tidak akan melakukan banyak panggilan seperti itu, dan tidak ada yang tersisa untuk dilakukan untuk menyelesaikan beberapa masalah selain menerapkan kembali kode. Google telah membuat beberapa perpustakaan untuk bekerja dengan beberapa bahasa sekaligus, yang dirancang untuk melakukan panggilan ke prosedur jarak jauh; Ada juga situasi di mana kode tersebut harus ditulis ulang dari awal.
Jadi apa alternatifnya?
Sederhananya, banyak zat besi. Metode ini mungkin tampak seperti kakek yang tidak masuk akal, tetapi perlu diingat bahwa biaya perangkat keras terus menurun, banyak beban kerja tidak disebut "global global", dan intuisi Anda tentang apa yang harus Anda belanjakan dapat membuat Anda gagal.
Berikut adalah
daftar harga yang relatif baru dari satu pabrikan Kanada:

Mesin empat puluh nuklir dengan terabyte RAM dan hampir satu terabyte pada hard drive harganya sekitar $ 6k hari ini. Bayangkan berapa banyak waktu untuk seluruh masa proyek tim Anda harus menyelesaikan masalah dengan sistem terdistribusi, dan berapa banyak biaya yang harus Anda keluarkan.
Ya, tetapi tidak semua perusahaan saat ini berbasis web global?
Singkatnya, tidak.
Dunia ini penuh dengan perusahaan-perusahaan yang hal-hal berikut ini benar:
- Mereka beroperasi di pasar yang stabil.
- Mereka menghasilkan dengan menjual barang.
- Akibatnya, basis pelanggan mereka adalah dari beberapa puluh ribu hingga puluhan juta orang, tetapi tidak dalam miliaran.
- Kumpulan data mereka biasanya dikaitkan dengan pelanggan dan produk mereka sendiri.
Contoh yang baik dari perusahaan semacam itu adalah bank. Bank tidak mengalami "hiper pertumbuhan", jangan menjadi "viral". Model pertumbuhan mereka moderat dan dapat diprediksi, jika seseorang berasumsi bahwa mereka memiliki pertumbuhan (bank bersifat regional dan biasanya beroperasi di pasar jenuh). Basis pelanggan dari bank AS terbesar adalah sekitar 50 juta pengguna dan, tentu saja, tidak berlipat ganda setiap enam bulan. Dalam hal ini, situasinya sama sekali tidak sama dengan Instagram. Oleh karena itu, tidak mengherankan bahwa mainframe masih didasarkan pada sistem bank yang khas? Tentu saja, hal yang sama berlaku untuk perusahaan logistik, perusahaan manufaktur, dll. Ini adalah roti dan mentega ekonomi kita.
Dalam bisnis seperti itu, sangat mungkin bahwa kebutuhan setiap aplikasi spesifik yang terkait dengannya selalu dapat dipenuhi dengan sumber daya hanya dari satu mesin besar. Ya di sana, bahkan beberapa situs publik saat ini muat pada satu mesin. Pada 2015, Maciej Tseglovsky memberikan ceramah yang sangat menarik tentang "
Krisis dengan obesitas situs " dan mencatat bahwa situsnya sendiri dengan layanan bookmark menguntungkan, tetapi kompetitornya memposting situs yang sama di AWS - dan hilang, hanya karena biaya yang berbeda peralatan dan berbagai asumsi tentang kompleksitas. Dalam sebuah studi tentang membandingkan
penskalaan vertikal dan horizontal, ditemukan bahwa PlentyOfFish berjalan sekitar ~ satu megaserver (artikel ini berasal dari 2009, sehingga Anda dapat mengabaikan harga peralatan yang tercantum di sana). Penulis melakukan beberapa perhitungan dan menunjukkan bahwa satu server tidak sebodoh kelihatannya. Akhirnya, jika Anda mempertimbangkan Hadoop dan Big Data, bacalah
artikel penelitian Microsoft 2013 ini, yang menunjukkan bahwa banyak beban kerja Hadoop dari Microsoft, Yahoo, dan Facebook benar-benar berjalan jauh lebih cepat dan lebih efisien pada satu mesin besar, daripada pada sebuah cluster. Dan itu 6 tahun yang lalu! Sangat mungkin bahwa sejak saat itu penekanan pada penskalaan vertikal menjadi lebih jelas.
Namun, penghematan nyata tidak terkait dengan peralatan sama sekali, tetapi dengan optimalisasi waktu kerja para insinyur yang sangat mahal, yang dihabiskan untuk menciptakan sekelompok layanan mikro kecil yang harus ditingkatkan secara horizontal dengan manajemen permintaan yang elastis. Pendekatan teknik semacam itu berisiko dan menghabiskan waktu, bahkan jika Anda menggunakan mainan terbaru yang tersedia di cloud. Anda dapat kehilangan SQL, transaksi SOLID, profil terpadu, dan Anda pasti akan kehilangan informasi seperti pelacakan tumpukan sistem silang. Jenis keamanan akan hilang setiap kali Anda melampaui server. Anda akan menerima panggilan ke fungsi yang mungkin time out, overhead berlebihan yang terkait dengan kompilasi dinamis, kegagalan tekanan balik yang tidak terduga, mesin orkestra kompleks dengan format konfigurasi mewah, dan ... oh, memori benar-benar membanjiri. Sangat menarik untuk bekerja dengan semua ini ketika saya memiliki arsitektur Google berpemilik dan anggaran teknik yang tebal yang saya miliki, tetapi hari ini saya berani mengulangi ini hanya jika saya tidak punya pilihan lain.
Menurut pengalaman, tidak mungkin untuk bekerja dengan server yang sangat besar yang beroperasi pada pengumpulan sampah - masalahnya adalah pengumpulan sampah itu sendiri adalah teknologi yang kurang berkembang, dan karena itu topik ini telah lama tetap murni bersifat akademis. Bagaimanapun, Anda harus mengarahkan beberapa server sekaligus. , ZGC Shenandoah, , 80 – . , -, – , .
, ? – , ? – : , , … .
Kesimpulan
NodeJVM – , GraalVM. NPM Java/Kotlin, JS , Kotlin JavaScript, JS , V8.
, JS Java, Java JS .
, 4 , – – , . .