Node.js qanday ishlaydi — V8 Engine, libuv, C++ bindings
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