Semua yang perlu Anda ketahui tentang Node.js

Halo, Habr! Saya sajikan kepada Anda terjemahan artikel "Semua yang perlu Anda ketahui tentang Node.js" oleh Jorge Ramón.



Saat ini, platform Node.js adalah salah satu platform paling populer untuk membangun REST API yang efisien dan skalabel. Ini juga cocok untuk membangun aplikasi mobile hybrid, program desktop, dan bahkan untuk IoT.


Saya telah bekerja dengan platform Node.js selama lebih dari 6 tahun dan saya benar-benar menyukainya. Posting ini terutama mencoba untuk menjadi panduan tentang bagaimana sebenarnya Node.js bekerja.


Ayo mulai !!


Apa yang akan dibahas:




Dunia sebelum Node.js


Server multithreaded


Aplikasi web yang ditulis mengikuti arsitektur klien / server berfungsi sebagai berikut - klien meminta sumber daya yang diperlukan dari server dan server mengirimkan sumber daya sebagai tanggapan. Dalam skema ini, server merespons permintaan dan memutuskan koneksi.


Model ini efektif karena setiap permintaan ke server menghabiskan sumber daya (memori, waktu prosesor, dll.). Untuk memproses setiap permintaan berikutnya dari klien, server harus menyelesaikan pemrosesan permintaan sebelumnya.


Apakah ini berarti bahwa server hanya dapat memproses satu permintaan pada satu waktu? Tidak juga! Ketika server menerima permintaan baru, itu membuat utas terpisah untuk memprosesnya.


Alurnya , dengan kata sederhana, adalah waktu dan sumber daya yang dialokasikan CPU untuk mengeksekusi blok kecil instruksi. Dengan demikian, server dapat memproses beberapa permintaan sekaligus, tetapi hanya satu per utas. Model seperti ini juga disebut model utas per permintaan .



Untuk memproses permintaan N, server membutuhkan N utas. Jika server menerima permintaan N + 1, maka server harus menunggu hingga salah satu utas tersedia.


Pada gambar di atas, server dapat memproses hingga 4 permintaan (utas) sekaligus dan ketika menerima 3 permintaan berikutnya, permintaan ini harus menunggu hingga 4 utas ini tersedia.


Salah satu cara untuk menghilangkan pembatasan adalah menambahkan lebih banyak sumber daya (memori, inti prosesor, dll.) Ke server, tetapi ini bukan solusi terbaik ....



Dan, tentu saja, jangan lupakan keterbatasan teknologi.


Memblokir input / output


Terbatasnya jumlah utas di server bukan satu-satunya masalah. Mungkin Anda bertanya-tanya mengapa satu utas tidak dapat memproses beberapa permintaan sekaligus? semua karena memblokir operasi I / O.



Misalkan Anda sedang mengembangkan toko online dan Anda memerlukan halaman di mana pengguna dapat melihat daftar semua produk.


Pengguna mengetuk http://yourstore.com/products dan server membuat file HTML dengan semua produk dari database sebagai tanggapan. Sama sekali tidak rumit, bukan?


Tapi apa yang terjadi di balik layar?


  • Ketika seorang pengguna mengetuk /products metode atau fungsi tertentu harus dijalankan untuk memproses permintaan. Sepotong kecil kode (milik Anda atau kerangka kerja Anda) mem-parsing URL permintaan dan mencari metode atau fungsi yang sesuai. Streaming sedang berjalan .
  • Sekarang metode atau fungsi yang diinginkan dijalankan, seperti pada paragraf pertama, utas berfungsi.
  • Karena Anda adalah pengembang yang baik, Anda menyimpan semua log sistem ke file, dan tentu saja, untuk memastikan bahwa router melakukan metode / fungsi yang diinginkan - Anda juga mencatat baris "Metode X mengeksekusi !!". Tetapi semua ini memblokir operasi input / output stream sedang menunggu .
  • Semua log disimpan dan garis fungsi berikut dijalankan. Utas berfungsi lagi .
  • Saatnya mengakses database dan mendapatkan semua produk - kueri sederhana seperti SELECT * FROM products melakukan tugasnya, tapi coba tebak? Ya, ini adalah operasi I / O pemblokiran. Arus sedang menunggu .
  • Anda telah menerima larik atau daftar semua produk, tetapi pastikan Anda telah menjaminkan semua ini. Arus sedang menunggu .
  • Sekarang Anda memiliki semua produk dan inilah saatnya untuk merender template untuk halaman selanjutnya, tetapi sebelum itu Anda harus membacanya. Arus sedang menunggu .
  • Mesin rendering melakukan tugasnya dan mengirimkan respons ke klien. Utas berfungsi lagi .
  • Alurnya gratis, seperti burung di langit.

Seberapa lambat operasi I / O? Yah itu tergantung spesifik. Mari kita lihat tabelnya:


OperasiSiklus CPU
Register CPU3 ukuran
L1 cache8 langkah
L2 cache12 langkah
RAM150 langkah
Disk30.000.000 tindakan
Jaringan250.000.000 tindakan

Operasi membaca jaringan dan disk terlalu lambat. Bayangkan berapa banyak permintaan atau panggilan ke API eksternal yang dapat ditangani sistem Anda selama waktu ini.


Untuk meringkas: operasi I / O membuat thread menunggu dan menyia-nyiakan sumber daya.




Masalah C10K


Masalah


C10k (ind. C10k; 10k koneksi - 10 ribu masalah koneksi)


Pada awal 2000-an, server dan mesin klien lambat. Masalah muncul ketika memproses 10.000 koneksi klien ke mesin yang sama secara paralel.


Tetapi mengapa model tradisional per permintaan tidak dapat (utas berdasarkan permintaan) menyelesaikan masalah ini? Baiklah, mari kita gunakan sedikit matematika.


Implementasi asli dari thread mengalokasikan lebih dari 1 MB memori per stream, meninggalkan ini - untuk 10 ribu thread, diperlukan 10 GB RAM dan ini hanya untuk stream stack. Ya, dan jangan lupa, kita berada di awal tahun 2000-an !!



Saat ini, komputer server dan klien bekerja lebih cepat dan lebih efisien dan hampir semua bahasa pemrograman atau kerangka kerja dapat mengatasi masalah ini. Namun faktanya, masalahnya tidak diselesaikan. Untuk 10 juta koneksi klien ke satu mesin, masalahnya kembali lagi (tetapi sekarang adalah Masalah C10M ).


Penyelamatan JavaScript?


Peringatan Spoiler !!!
Node.js sebenarnya memecahkan masalah C10K ... tapi bagaimana?!


JavaScript sisi server bukanlah sesuatu yang baru dan tidak biasa pada awal 2000-an, pada waktu itu sudah ada implementasi di atas JVM (mesin virtual java) - RingoJS dan AppEngineJS, yang bekerja pada model thread-per-request.


Tetapi jika mereka tidak bisa menyelesaikan masalah, lalu bagaimana mungkin Node.js ?! Semua karena JavaScript adalah utas tunggal .




Node.js dan loop acara


Node.js


Node.js adalah platform server yang berjalan di mesin Google Chrome - V8, yang dapat mengkompilasi kode JavaScript ke dalam kode mesin.


Node.js menggunakan model event-driven dan arsitektur I / O non-blocking , yang membuatnya ringan dan efisien. Ini bukan kerangka kerja, atau perpustakaan, ini adalah runtime JavaScript.


Mari kita tulis contoh kecil:


 // Importing native http module const http = require('http'); // Creating a server instance where every call // the message 'Hello World' is responded to the client const server = http.createServer(function(request, response) { response.write('Hello World'); response.end(); }); // Listening port 8080 server.listen(8080); 

Non-blocking i / o


Node.js menggunakan operasi input / output yang tidak menghalangi, apa artinya ini:


  • Utas utama tidak akan diblokir oleh operasi I / O.
  • Server akan terus melayani permintaan.
  • Kami harus bekerja dengan kode asinkron .

Mari kita menulis contoh di mana server mengirim halaman HTML sebagai tanggapan atas permintaan ke /home , dan untuk semua permintaan lainnya - 'Hello World'. Untuk mengirim halaman HTML, Anda harus terlebih dahulu membacanya dari file.


home.html


 <html> <body> <h1>This is home page</h1> </body> </html> 

index.js


 const http = require('http'); const fs = require('fs'); const server = http.createServer(function(request, response) { if (request.url === '/home') { fs.readFile(`${ __dirname }/home.html`, function (err, content) { if (!err) { response.setHeader('Content-Type', 'text/html'); response.write(content); } else { response.statusCode = 500; response.write('An error has ocurred'); } response.end(); }); } else { response.write('Hello World'); response.end(); } }); server.listen(8080); 

Jika url yang diminta adalah /home , maka modul asli fs digunakan untuk membaca file home.html .


Fungsi yang termasuk dalam http.createServer dan fs.readFile sebagai argumen adalah panggilan balik . Fungsi-fungsi ini akan dilakukan di beberapa titik di masa depan (yang pertama, segera setelah server menerima permintaan, dan yang kedua, ketika file dibaca dari disk dan ditempatkan di buffer).


Saat file sedang dibaca dari disk, Node.js dapat memproses permintaan lain dan bahkan membaca file lagi dan semua ini dalam satu aliran ... tapi bagaimana caranya ?!


Perulangan acara


Perulangan acara adalah keajaiban yang terjadi di dalam Node.js. Ini secara harfiah adalah loop tanpa akhir dan sebenarnya satu utas.



Libuv adalah pustaka C yang mengimplementasikan pola ini dan merupakan bagian dari kernel Node.js. Anda dapat mempelajari lebih lanjut tentang libuv di sini .


Sebuah siklus peristiwa memiliki 6 fase, setiap eksekusi semua 6 fase disebut tanda centang .



  • timer : dalam fase ini, panggilan balik yang dijadwalkan oleh metode setTimeout() dan setInterval() dijalankan;
  • panggilan balik yang tertunda : hampir semua panggilan balik dijalankan, kecuali untuk aktivitas close , penghitung waktu, dan setImmediate() ;
  • menganggur, siapkan : digunakan hanya untuk keperluan internal;
  • polling : bertanggung jawab untuk menerima acara I / O baru. Node.js dapat memblokir pada saat ini;
  • check : callback yang disebabkan oleh metode setImmediate() dijalankan pada tahap ini;
  • tutup panggilan balik : misalnya socket.on('close', ...) ;

Nah, hanya ada satu utas, dan utas ini adalah perulangan acara, tetapi lalu siapa yang melakukan semua I / O?


Perhatikan !!!
Ketika sebuah loop peristiwa perlu melakukan operasi I / O, ia menggunakan utas OS dari kumpulan utas, dan ketika tugas selesai, panggilan balik akan diantrekan selama fase panggilan balik yang tertunda .



Bukankah itu keren?




Masalah tugas intensif CPU


Node.js tampaknya sempurna! Anda dapat membuat apa pun yang Anda inginkan.


Mari kita menulis API untuk menghitung bilangan prima.


Bilangan prima adalah bilangan bulat (alami) yang lebih besar dari satu dan hanya dapat dibagi dengan 1 dan dengan sendirinya.



Dengan diberi nomor N, API harus menghitung dan mengembalikan N primes pertama dalam daftar (atau larik).


primes.js


 function isPrime(n) { for(let i = 2, s = Math.sqrt(n); i <= s; i++) { if(n % i === 0) return false; } return n > 1; } function nthPrime(n) { let counter = n; let iterator = 2; let result = []; while(counter > 0) { isPrime(iterator) && result.push(iterator) && counter--; iterator++; } return result; } module.exports = { isPrime, nthPrime }; 

index.js


 const http = require('http'); const url = require('url'); const primes = require('./primes'); const server = http.createServer(function (request, response) { const { pathname, query } = url.parse(request.url, true); if (pathname === '/primes') { const result = primes.nthPrime(query.n || 0); response.setHeader('Content-Type', 'application/json'); response.write(JSON.stringify(result)); response.end(); } else { response.statusCode = 404; response.write('Not Found'); response.end(); } }); server.listen(8080); 

prime.js adalah implementasi perhitungan yang diperlukan: fungsi isPrime memeriksa apakah nomornya prima, dan nthPrime mengembalikan N angka-angka tersebut.


File index.js bertanggung jawab untuk membuat server dan menggunakan modul prime.js untuk memproses setiap permintaan untuk /primes . Angka N dilemparkan melalui string kueri di URL.


Untuk mendapatkan 20 primes pertama kita perlu membuat permintaan ke http://localhost:8080/primes?n=20 .


Misalkan kita memiliki 3 klien yang mengetuk kita dan mencoba mengakses API I / O yang tidak menghalangi kita:


  • Pertanyaan pertama 5 bilangan prima setiap detik.
  • Yang kedua meminta 1000 bilangan prima setiap detik
  • Yang ketiga meminta 10.000.000.000 bilangan prima, tetapi ...


Ketika klien ketiga mengirim permintaan, utas utama diblokir dan ini adalah gejala utama masalah tugas-tugas yang intensif CPU . Ketika utas utama sibuk melakukan tugas "berat", itu menjadi tidak dapat diakses untuk tugas-tugas lain.


Tapi bagaimana dengan libuv? Jika Anda ingat, pustaka ini membantu Node.js melakukan operasi input / output menggunakan utas OS menghindari pemblokiran utas utama dan Anda memang benar, ini adalah solusi untuk masalah kami, tetapi untuk memungkinkan, modul kami harus ditulis dalam bahasa C ++ jadi libuv dapat bekerja dengannya.


Untungnya, dimulai dengan v10.5, modul Worker Threads asli telah ditambahkan ke Node.js.



Pekerja dan arus mereka


Seperti yang dijelaskan dalam dokumentasi :


Pekerja bermanfaat untuk melakukan operasi JavaScript intensif CPU; jangan menggunakannya untuk operasi input / output, mekanisme yang sudah dibangun ke dalam Node.js lebih efisien mengatasi tugas-tugas seperti itu daripada utas Pekerja.

Memperbaiki kode


Saatnya untuk menulis ulang kode kita:


primes-workerthreads.js


 const { workerData, parentPort } = require('worker_threads'); function isPrime(n) { for(let i = 2, s = Math.sqrt(n); i <= s; i++) if(n % i === 0) return false; return n > 1; } function nthPrime(n) { let counter = n; let iterator = 2; let result = []; while(counter > 0) { isPrime(iterator) && result.push(iterator) && counter--; iterator++; } return result; } parentPort.postMessage(nthPrime(workerData.n)); 

index-workerthreads.js


 const http = require('http'); const url = require('url'); const { Worker } = require('worker_threads'); const server = http.createServer(function (request, response) { const { pathname, query } = url.parse(request.url, true); if (pathname === '/primes') { const worker = new Worker('./primes-workerthreads.js', { workerData: { n: query.n || 0 } }); worker.on('error', function () { response.statusCode = 500; response.write('Oops there was an error...'); response.end(); }); let result; worker.on('message', function (message) { result = message; }); worker.on('exit', function () { response.setHeader('Content-Type', 'application/json'); response.write(JSON.stringify(result)); response.end(); }); } else { response.statusCode = 404; response.write('Not Found'); response.end(); } }); server.listen(8080); 

Dalam file index-workerthreads.js , setiap permintaan ke /primes membuat instance dari kelas Worker (dari modul native worker_threads ) untuk mengunggah dan mengeksekusi file primes-workerthreads.js ke thread pekerja. Ketika daftar bilangan prima dihitung dan siap, peristiwa message dipicu - hasilnya jatuh ke aliran utama karena pekerja tidak memiliki pekerjaan yang tersisa, ia juga memicu acara exit , yang memungkinkan aliran utama untuk mengirim data ke klien.


primes-workerthreads.js berubah sedikit. Ini mengimpor data workerData (ini adalah salinan dari parameter yang diteruskan dari utas utama) dan parentPort mana hasil pekerjaan pekerja dilewatkan kembali ke utas utama.


Sekarang mari kita coba contoh kita lagi dan lihat apa yang terjadi:



Utas utama tidak lagi diblokir !!!!!



Sekarang semuanya berjalan sebagaimana mestinya, tetapi menghasilkan pekerja tanpa alasan masih bukan praktik yang baik, membuat benang bukanlah kesenangan yang murah. Pastikan untuk membuat kumpulan utas sebelum ini.


Kesimpulan


Node.js adalah teknologi canggih yang harus dieksplorasi jika memungkinkan.
Rekomendasi pribadi saya - selalu ingin tahu! Jika Anda tahu cara kerja sesuatu dari dalam, Anda bisa bekerja dengan lebih efisien.


Itu saja untuk hari ini kawan. Saya harap posting ini bermanfaat bagi Anda dan Anda mempelajari sesuatu yang baru tentang Node.js.


Terima kasih telah membaca dan melihat Anda di posting selanjutnya. .

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


All Articles