Node.js mengguna pakai pendekatan I/O dipacu peristiwa dan tak segerak, mencapai persekitaran masa jalan JavaScript berutas tunggal dan sangat serentak. Memandangkan satu utas bermakna hanya satu perkara boleh dilakukan pada satu masa, bagaimanakah Node.js mencapai konkurensi tinggi dan I/O tak segerak dengan hanya satu utas? Artikel ini akan meneroka model satu benang Node.js di sekitar soalan ini.
Secara amnya, penyelesaian untuk konkurensi tinggi adalah dengan menyediakan model berbilang benang. Pelayan memberikan satu utas kepada setiap permintaan pelanggan dan menggunakan I/O segerak. Sistem ini menebus kos masa panggilan I/O segerak melalui penukaran benang. Sebagai contoh, Apache menggunakan strategi ini. Memandangkan operasi I/O biasanya memakan masa, sukar untuk mencapai prestasi tinggi dengan pendekatan ini. Walau bagaimanapun, ia sangat mudah dan boleh melaksanakan logik interaksi yang kompleks.
Malah, kebanyakan bahagian pelayan web tidak melakukan banyak pengiraan. Selepas menerima permintaan, mereka menghantar permintaan kepada perkhidmatan lain (seperti membaca pangkalan data), kemudian menunggu keputusan kembali, dan akhirnya menghantar hasilnya kepada pelanggan. Oleh itu, Node.js menggunakan model satu benang untuk mengendalikan situasi ini. Daripada memberikan urutan kepada setiap permintaan masuk, ia menggunakan urutan utama untuk mengendalikan semua permintaan dan kemudian memproses operasi I/O secara tidak segerak, mengelakkan overhed dan kerumitan mencipta, memusnahkan utas dan bertukar antara utas.
Node.js mengekalkan baris gilir acara dalam urutan utama. Apabila permintaan diterima, ia ditambahkan pada baris gilir ini sebagai acara, dan kemudian ia terus menerima permintaan lain. Apabila utas utama melahu (tiada permintaan masuk), ia mula menggelung melalui baris gilir acara untuk menyemak sama ada terdapat acara yang perlu diproses. Terdapat dua kes: untuk tugasan bukan I/O, utas utama akan mengendalikannya secara terus dan kembali ke lapisan atas melalui fungsi panggil balik; untuk tugasan I/O, ia akan mengambil utas daripada kumpulan utas untuk mengendalikan acara, menentukan fungsi panggil balik, dan kemudian terus menggelung acara lain dalam baris gilir.
Setelah tugas I/O dalam urutan selesai, fungsi panggil balik yang ditentukan akan dilaksanakan dan acara yang telah selesai diletakkan pada penghujung baris gilir acara, menunggu gelung acara. Apabila benang utama bergelung ke acara ini sekali lagi, ia memprosesnya secara langsung dan mengembalikannya ke lapisan atas. Proses ini dipanggil Gelung Peristiwa, dan prinsip operasinya ditunjukkan dalam rajah di bawah:
Angka ini menunjukkan prinsip operasi keseluruhan Node.js. Dari kiri ke kanan dan dari atas ke bawah, Node.js dibahagikan kepada empat lapisan: lapisan aplikasi, lapisan enjin V8, lapisan API Node dan lapisan LIBUV.
Sama ada pada platform Linux atau platform Windows, Node.js secara dalaman menggunakan kumpulan benang untuk menyelesaikan operasi I/O tak segerak dan LIBUV menyatukan panggilan untuk perbezaan platform yang berbeza. Jadi, utas tunggal dalam Node.js hanya bermakna JavaScript berjalan dalam satu utas, bukannya Node.js secara keseluruhannya berutas tunggal.
Inti Node.js mencapai tak segerak terletak pada acara. Iaitu, ia menganggap setiap tugas sebagai peristiwa dan kemudian mensimulasikan kesan tak segerak melalui Gelung Acara. Untuk memahami dan menerima fakta ini dengan lebih konkrit dan jelas, kami menggunakan pseudokod untuk menerangkan prinsip kerjanya di bawah.
Memandangkan ia adalah baris gilir, ia adalah struktur data masuk dahulu, keluar dahulu (FIFO). Kami menggunakan tatasusunan JS untuk menerangkannya seperti berikut:
/** * Define the event queue * Enqueue: push() * Dequeue: shift() * Empty queue: length === 0 */ let globalEventQueue = [];
Kami menggunakan tatasusunan untuk mensimulasikan struktur baris gilir: elemen pertama tatasusunan ialah ketua baris gilir, dan elemen terakhir ialah ekor. push() memasukkan elemen pada penghujung baris gilir, dan shift() mengalih keluar elemen daripada kepala baris gilir. Oleh itu, baris gilir acara mudah dicapai.
Setiap permintaan akan dipintas dan memasuki fungsi pemprosesan, seperti yang ditunjukkan di bawah:
/** * Receive user requests * Every request will enter this function * Pass parameters request and response */ function processHttpRequest(request, response) { // Define an event object let event = createEvent({ params: request.params, // Pass request parameters result: null, // Store request results callback: function() {} // Specify a callback function }); // Add the event to the end of the queue globalEventQueue.push(event); }
Fungsi ini hanya membungkus permintaan pengguna sebagai acara dan memasukkannya ke dalam baris gilir, kemudian terus menerima permintaan lain.
Apabila utas utama melahu, ia mula berpusing melalui baris gilir acara. Jadi kita perlu menentukan fungsi untuk menggelung melalui baris gilir acara:
/** * The main body of the event loop, executed by the main thread when appropriate * Loop through the event queue * Handle non-IO tasks * Handle IO tasks * Execute callbacks and return to the upper layer */ function eventLoop() { // If the queue is not empty, continue to loop while (this.globalEventQueue.length > 0) { // Take an event from the head of the queue let event = this.globalEventQueue.shift(); // If it's a time-consuming task if (isIOTask(event)) { // Take a thread from the thread pool let thread = getThreadFromThreadPool(); // Hand it over to the thread to handle thread.handleIOTask(event); } else { // After handling non-time-consuming tasks, directly return the result let result = handleEvent(event); // Finally, return to V8 through the callback function, and then V8 returns to the application event.callback.call(null, result); } } }
Urut utama sentiasa memantau baris gilir acara. Untuk tugasan I/O, ia menyerahkannya kepada kumpulan benang untuk dikendalikan, dan untuk tugasan bukan I/O, tugas itu mengendalikannya sendiri dan kembali.
Selepas kumpulan benang menerima tugas, ia secara langsung memproses operasi I/O, seperti membaca pangkalan data:
/** * Define the event queue * Enqueue: push() * Dequeue: shift() * Empty queue: length === 0 */ let globalEventQueue = [];
Apabila tugas I/O selesai, panggilan balik dilaksanakan, hasil permintaan disimpan dalam acara dan acara dimasukkan semula ke dalam baris gilir, menunggu gelung. Akhirnya, benang semasa dikeluarkan. Apabila utas utama bergelung ke acara ini sekali lagi, ia akan memprosesnya secara langsung.
Merumuskan proses di atas, kami mendapati bahawa Node.js hanya menggunakan satu urutan utama untuk menerima permintaan. Selepas menerima permintaan, ia tidak memprosesnya secara langsung tetapi meletakkannya ke dalam baris gilir acara dan kemudian terus menerima permintaan lain. Apabila ia melahu, ia memproses peristiwa ini melalui Gelung Acara, sekali gus mencapai kesan tak segerak. Sudah tentu, untuk tugasan I/O, ia masih perlu bergantung pada kumpulan benang di peringkat sistem untuk dikendalikan.
Oleh itu, kita hanya boleh memahami bahawa Node.js sendiri ialah platform berbilang benang, tetapi ia memproses tugas pada peringkat JavaScript dalam satu urutan.
Pada masa ini, kita sepatutnya mempunyai pemahaman yang mudah dan jelas tentang model satu benang Node.js. Ia mencapai konkurensi tinggi dan I/O tak segerak melalui model dipacu peristiwa. Walau bagaimanapun, terdapat juga perkara yang Node.js tidak mahir.
Seperti yang dinyatakan di atas, untuk tugasan I/O, Node.js menyerahkannya kepada kumpulan benang untuk pemprosesan tak segerak, yang cekap dan mudah. Jadi, Node.js sesuai untuk mengendalikan tugas intensif I/O. Tetapi tidak semua tugas adalah I/O-intensif. Apabila menghadapi tugas intensif CPU, iaitu, operasi yang hanya bergantung pada pengiraan CPU, seperti penyulitan dan penyahsulitan data (node.bcrypt.js), pemampatan dan penyahmampatan data (node-tar), Node.js akan mengendalikannya satu demi satu. satu. Jika tugasan sebelumnya tidak selesai, tugasan seterusnya hanya boleh menunggu. Seperti yang ditunjukkan dalam rajah di bawah:
Dalam baris gilir acara, jika tugas pengiraan CPU sebelumnya tidak diselesaikan, tugasan berikutnya akan disekat, mengakibatkan tindak balas yang perlahan. Jika sistem pengendalian adalah teras tunggal, ia mungkin boleh diterima. Tetapi kini kebanyakan pelayan adalah berbilang CPU atau berbilang teras, dan Node.js hanya mempunyai satu EventLoop, bermakna ia hanya menduduki satu teras CPU. Apabila Node.js diduduki oleh tugas intensif CPU, menyebabkan tugas lain disekat, masih terdapat teras CPU terbiar, mengakibatkan pembaziran sumber.
Jadi, Node.js tidak sesuai untuk tugas intensif CPU.
Akhir sekali, izinkan saya memperkenalkan platform yang paling sesuai untuk menggunakan perkhidmatan Node.js: Leapcell.
Teroka lebih lanjut dalam dokumentasi!
Twitter Leapcell: https://x.com/LeapcellHQ
Atas ialah kandungan terperinci Di dalam Gelung Peristiwa Node.js: Penyelaman Dalam. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!