В 2009 году казахстанские веб-разработчики работали с PHP+Apache. Запрос пришёл - PHP-скрипт выполнился - ответ отдан. Простая, понятная модель. Она хорошо работала для большинства задач.
Но у неё была проблема: каждый запрос занимал поток. Поток блокировался на I/O - базе данных, файлах, внешних API. При 1000 одновременных соединениях нужны 1000 потоков. При 10 000 - проблема.
8 ноября 2009 года Райан Даль показал другой подход на JSConf EU в Берлине: один поток, event loop, всё I/O неблокирующее. JavaScript, работающий на сервере через V8.
Первый сервер: 10 строк
// Именно это Даль показал на JSConf EU 2009
var http = require('http');
var server = http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
res.end('Сервер работает\n');
});
server.listen(8000, '0.0.0.0');
console.log('Сервер запущен на порту 8000');
// Это - полноценный HTTP-сервер.
// Без Apache, без nginx конфигов, без XML дескрипторов.
// 10 строк JavaScript.
// Сравнение при 1000 одновременных соединений (2009):
// Apache + PHP (prefork): ~800 req/sec, ~200MB RAM
// Node.js 0.1: ~3500 req/sec, ~20MB RAM
Event loop: почему один поток эффективен
// Блокирующая модель (PHP/Apache):
// Поток 1: запрос → читает файл → ЖДЁТ → отвечает
// Поток 2: запрос → запрос к БД → ЖДЁТ → отвечает
// При ожидании поток держит ~1MB памяти и ничего не делает.
// Node.js - неблокирующая модель:
var fs = require('fs');
// Этот код НЕ блокирует выполнение:
fs.readFile('/etc/hosts', 'utf8', function(err, data) {
// Вызовется когда файл прочитан.
// Пока читается - Node обрабатывает другие запросы.
if (err) throw err;
console.log(data);
});
// Следующая строка выполняется НЕМЕДЛЕННО,
// не дожидаясь readFile:
console.log('Запрос к файлу отправлен, продолжаю работу...');
// Для казахстанских API с запросами к внешним сервисам
// (курсы валют НБРК, SMS-шлюзы, платёжные системы) -
// неблокирующий I/O давал значительный выигрыш при нагрузке.
Express: первый фреймворк
// Express 0.x - конец 2009, создан TJ Holowaychuk
// npm install express
var express = require('express');
var app = express.createServer();
// Маршруты REST API:
app.get('/api/products', function(req, res) {
var category = req.query.cat || 'all';
// Запрос к БД (неблокирующий):
db.query(
'SELECT * FROM products WHERE category = ?',
[category],
function(err, rows) {
if (err) { res.send(500, 'Ошибка'); return; }
res.json(rows);
}
);
});
app.get('/api/products/:id', function(req, res) {
var id = parseInt(req.params.id, 10);
db.query(
'SELECT * FROM products WHERE id = ?',
[id],
function(err, rows) {
if (err || rows.length === 0) {
res.send(404, 'Не найдено');
return;
}
res.json(rows[0]);
}
);
});
app.listen(3000);
// Весь REST API в одном файле.
// Для разработчика, привыкшего к PHP: структура непривычная,
// но результат - тот же функционал в меньшем коде.
npm: управление зависимостями
// package.json - появился одновременно с Node 0.1
{
"name": "kz-api-server",
"version": "0.1.0",
"description": "API-сервер для казахстанского интернет-магазина",
"dependencies": {
"express": "0.14.0",
"mysql": "0.1.0"
}
}
// npm install - устанавливает все зависимости в ./node_modules
// npm publish - публикует пакет в реестр
// Принципиальное отличие от PEAR (PHP) и CPAN (Perl):
// Зависимости локальны для проекта.
// Два проекта могут иметь разные версии одной библиотеки.
// "Dependency hell" PHP-мира здесь не существовал.
// К 2013 году npm стал крупнейшим реестром пакетов в мире.
Node.js в контексте Казнета
Node.js в 2009 году не был массовым инструментом для Казнета. Большинство казахстанских разработчиков работали с PHP, и Node.js оставался нишевым.
Но он определил будущее. К 2011-2012 году казахстанские стартапы, строившие real-time сервисы - чаты, уведомления, мобильные API - выбирали Node.js. Причина: неблокирующий I/O при высоком concurrency, и один язык на клиенте и сервере.
Самое важное что сделал Node.js в 2009: показал, что JavaScript - не «язык для браузера». Это язык. Достаточно серьёзный для продакшен-серверов. Это изменило как индустрия думала о JavaScript навсегда - включая казахстанскую.