Node.js qanday ishlaydi — V8 Engine, libuv, C++ bindings

~20 daq · Node.js · Kirish va Asoslar

Muammodan boshlaylik

Siz node server.js yozasiz. Terminal bir narsalar qiladi va server ishga tushadi. Lekin ichida nima bo'lyapti? JavaScript qanday qilib fayllarni o'qiy oladi? Tarmoqqa qanday ulanadi? Brauzer bu ishlarni qila olmaydi — Node.js qila oladi. Nima farq qiladi?

Bu savolga javob bermasdan Node.js ni chuqur tushunib bo'lmaydi. Ayniqsa Event Loop, asinxron kod, performance — bularni tushunish uchun ichki tuzilishni bilish shart.


Node.js ning uch qatlami

Node.js uch katta qismdan iborat:

┌─────────────────────────────────────────┐
│           Sizning JavaScript kodingiz   │  ← Siz yozasiz
├─────────────────────────────────────────┤
│              Node.js API                │  ← fs, http, path...
├─────────────────────────────────────────┤
│    V8 Engine    │       libuv           │  ← C++ kutubxonalar
└─────────────────────────────────────────┘

Har bir qatlam o'z ishini qiladi. Siz faqat yuqori qatlamda ishlaysiz — qolganini Node.js o'zi hal qiladi.


V8 Engine — JavaScript ni tushunuvchi dvigatel

V8 — Google tomonidan yozilgan, C++ da qurilgan JavaScript dvigateli. Chrome brauzerida ham, Node.js da ham aynan shu V8 ishlaydi.

V8 ning ishi oddiy: JavaScript kodini o'qiydi va protsessor tushuna oladigan mashina kodiga aylantiradi.

V8 qanday ishlaydi?

Sizning JS kodingiz
        │
        ▼
   [Parser] — kodni o'qiydi, sintaksisni tekshiradi
        │
        ▼
   [AST] — Abstract Syntax Tree — kod tuzilmasi
        │
        ▼
   [Ignition] — bytecode ga aylantiradi (tez ishga tushish)
        │
        ▼
   [TurboFan] — tez-tez ishlatiladigan kodni optimizatsiya qiladi
        │
        ▼
   Mashina kodi — protsessor to'g'ridan-to'g'ri bajaradi

JIT — Just-In-Time Compilation

V8 ning eng kuchli tomoni — JIT kompilyatsiya. Bu nima?

Oddiy interpretatorlar kodni qator-qator o'qib bajaradi — sekin. Oddiy kompilyatorlar kodni oldindan mashina kodiga aylantiradi — tez, lekin moslashuvchan emas.

V8 ikkalasini birlashtiradi. Avval kodni tez ishga tushiradi (bytecode). Keyin qaysi qismlar ko'p ishlatilayotganini kuzatadi. O'sha qismlarni mashina kodiga aylantiradi — keyingi safar juda tez ishlaydi.

function qoshish(a, b) {
  return a + b;
}

// Birinchi marta — bytecode bilan ishlaydi
qoshish(1, 2);

// Ko'p marta chaqirilsa — V8 optimizatsiya qiladi
for (let i = 0; i < 1000000; i++) {
  qoshish(i, i + 1); // endi mashina kodi bilan — juda tez
}

V8 nima qila olmaydi?

V8 faqat JavaScript ni bajaradi. Fayl o'qish, tarmoqqa ulanish, operatsion tizim bilan gaplashish — bu V8 ning ishi emas. Bu yerda libuv kiradi.


libuv — asinxronlikning yuragi

libuv — C da yozilgan kutubxona. Node.js ning eng muhim qismi. Fayl tizimi, tarmoq, taymer, signal — bularning hammasi libuv orqali ishlaydi.

libuv ning eng katta kuchi — asinxron I/O va Event Loop.

Muammo: I/O sekin

Protsessor juda tez ishlaydi — nanosaniyalarda. Lekin fayl o'qish, tarmoqdan ma'lumot olish — bu millisekundlar, ba'zan sekundlar. Bu vaqtda protsessor nima qiladi?

Blocking (to'suvchi) yondashuv — kutadi. Fayl o'qilguncha hech narsa qilmaydi. Bu vaqt isrof.

Non-blocking (to'smaydigan) yondashuv — kutmaydi. "Fayl o'qilganda xabar ber" deydi va boshqa ishga o'tadi. Fayl tayyor bo'lganda qaytib keladi.

libuv aynan shu non-blocking yondashuvni amalga oshiradi.

libuv qanday ishlaydi?

Node.js kodi
    │
    ├── fs.readFile('fayl.txt', callback)  ← so'rov
    │         │
    │         ▼
    │    [libuv] — operatsion tizimga topshiradi
    │         │
    │         ▼
    │    [OS] — faylni o'qiydi (Node.js kutmaydi!)
    │
    ├── boshqa kod ishlaydi...
    │
    │    [OS] — fayl tayyor, libuv ga xabar beradi
    │         │
    │         ▼
    │    [Event Loop] — callback ni navbatga qo'yadi
    │         │
    │         ▼
    └── callback(null, data) — chaqiriladi

Thread Pool — og'ir ishlar uchun

Ba'zi operatsiyalar operatsion tizim darajasida asinxron bo'lmaydi — masalan, fayl tizimi ba'zi operatsiyalari, DNS lookup, kriptografiya. Bular uchun libuv thread pool ishlatadi.

Thread pool — bu libuv ning "ishchilar guruhi". Default 4 ta thread. Og'ir ish kelsa — bitta threadga topshiriladi, qolgan 3 ta boshqa ishlarni bajaradi.

libuv Thread Pool (default: 4 thread)
┌──────────┬──────────┬──────────┬──────────┐
│ Thread 1 │ Thread 2 │ Thread 3 │ Thread 4 │
│ DNS      │ fs.stat  │ crypto   │ bo'sh    │
└──────────┴──────────┴──────────┴──────────┘

Thread pool hajmini o'zgartirish mumkin:

UV_THREADPOOL_SIZE=8 node server.js

C++ Bindings — ko'prik

JavaScript va C++ — ikki xil til. Ular to'g'ridan-to'g'ri gaplasha olmaydi. C++ Bindings — bu ikki til orasidagi ko'prik.

Node.js da fs, http, crypto, path kabi modullar JavaScript da yozilgan, lekin ichida C++ funksiyalarini chaqiradi.

// Siz yozasiz — JavaScript
const fs = require('fs');
fs.readFile('fayl.txt', callback);

// Ichida nima bo'ladi:
// JavaScript → C++ Binding → libuv → OS

Bu ko'prik V8 API orqali qurilgan. Node.js ning o'zi ham, ba'zi npm paketlar ham (masalan, bcrypt, sharp) C++ bindings ishlatadi — shuning uchun ular juda tez ishlaydi.


Hammasi birgalikda — to'liq rasm

┌─────────────────────────────────────────────────────┐
│                  Sizning kodingiz                   │
│  const fs = require('fs')                           │
│  fs.readFile('a.txt', (err, data) => { ... })       │
└──────────────────────┬──────────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────────┐
│                   Node.js API                       │
│  (JavaScript da yozilgan — lib/ papkasi)            │
└──────────────────────┬──────────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────────┐
│              C++ Bindings (src/ papkasi)            │
│  JavaScript ↔ C++ ko'prigi                          │
└──────────┬───────────────────────┬──────────────────┘
           │                       │
┌──────────▼──────────┐  ┌─────────▼──────────────────┐
│      V8 Engine      │  │          libuv              │
│  JavaScript → kode  │  │  Event Loop, Thread Pool    │
│  JIT optimizatsiya  │  │  Fayl, Tarmoq, Taymer       │
└─────────────────────┘  └────────────────────────────┘

Amalda ko'rish

Node.js ning ichki tuzilishini amalda his qilish uchun:

// process.versions — barcha versiyalarni ko'rsatadi
console.log(process.versions);

Natija:

{
  node: '20.11.0',
  v8: '11.3.244.8-node.17',      // V8 versiyasi
  uv: '1.46.0',                  // libuv versiyasi
  zlib: '1.2.13',
  brotli: '1.0.9',
  ares: '1.19.1',                // DNS uchun
  modules: '115',
  nghttp2: '1.58.0',             // HTTP/2 uchun
  napi: '9',
  llhttp: '8.1.1',               // HTTP parser
  openssl: '3.1.4',              // kriptografiya
  ...
}

Har bir kutubxona o'z ishini qiladi. Node.js ularni birlashtiradi.

// libuv thread pool hajmini ko'rish
const os = require('os');
console.log('CPU yadrolari:', os.cpus().length);
// Thread pool hajmini CPU ga moslashtirish tavsiya etiladi

Xulosa

Node.js uch qatlamdan iborat:

V8 — JavaScript ni o'qiydi, JIT bilan tez mashina kodiga aylantiradi. Faqat JavaScript bajaradi, I/O bilan ishlamaydi.

libuv — asinxron I/O ning yuragi. Event Loop va Thread Pool shu yerda. Fayl, tarmoq, taymer — bularni operatsion tizimga topshiradi, kutmaydi, natija kelganda xabar beradi.

C++ Bindings — JavaScript va C++ orasidagi ko'prik. fs, http, crypto kabi modullar shu ko'prik orqali libuv bilan gaplashadi.

Bu tuzilish tufayli Node.js bitta thread bilan minglab ulanishni boshqara oladi — chunki u kutmaydi, balki "xabar ber" deydi va boshqa ishga o'tadi.


Keyingi dars

4-dars: CommonJS va ES Modules — require, import, export, farqlari