О насБлогКонтакты
Backend25 октября 2011 г. 4 мин 11

Революция Node.js (2011): как JavaScript завоевал сервер

AunimedaAunimeda
📋 Содержание

Революция Node.js (2011): как JavaScript завоевал сервер

Райан Даль представил Node.js на JSConf EU в ноябре 2009. Демо показывало простой веб-сервер, обрабатывающий несколько загрузок файлов одновременно без блокировки. Аплодисменты в записи слышны и искренни.

Решаемая проблема: Apache порождал поток или процесс на каждое соединение. При 1000 одновременных соединений Apache порождал 1000 потоков. Каждый поток использовал 2-8МБ стека. 1000 соединений могли съесть 8ГБ RAM только на накладные расходы потоков, не обработав ни байта бизнес-логики.

Node.js использовал один поток с event loop. 1000 соединений обрабатывал один и тот же процесс. Память измерялась мегабайтами, а не гигабайтами. I/O не блокировал поток — он регистрировал колбек и поток двигался дальше.

Мы начали экспериментировать с Node.js в начале 2011. К середине 2011 использовали в production для определённых нагрузок. К 2013 — стандарт для новых бэкенд-сервисов.


Event Loop на самом деле

// Концептуальный event loop (не реальная реализация)
while (true) {
  event = eventQueue.pop();
  if (event) {
    event.callback(event.data);
  }
}

Критическое ограничение: колбеки выполняются до завершения перед следующим. Node.js однопоточный. Колбек, работающий 500мс, блокирует все остальные колбеки на 500мс.

// ХОРОШО: I/O-bound — колбек запускает I/O, возвращается немедленно
app.get('/user/:id', function(req, res) {
  db.query('SELECT * FROM users WHERE id = ?', [req.params.id], function(err, rows) {
    res.json(rows[0]); // Вызывается когда БД вернула — event loop был свободен
  });
});

// ПЛОХО: CPU-bound — блокирует event loop
app.get('/fibonacci/:n', function(req, res) {
  var result = fibonacci(parseInt(req.params.n)); // fibonacci(40) = ~1.5 сек блокировки
  res.json({ result: result });
});

Пример с fibonacci — реальная ошибка, которую мы допустили в 2012. Клиент хотел эндпоинт вычислений. Мы поместили его в Node.js. При нагрузочном тестировании сервер переставал отвечать на все другие запросы во время вычисления. PHP справился бы нормально — каждый PHP-запрос работал в своём процессе.


npm: революция зависимостей

Node.js поставлялся с npm по умолчанию. К 2013 в npm было 50 000 пакетов. К 2014 — больше, чем в любой другой экосистеме языков.

{
  "name": "aunimeda-api",
  "dependencies": {
    "express": "~3.4.0",
    "mongoose": "~3.8.0",
    "async": "~0.2.0",
    "moment": "~2.0.0"
  }
}

npm install загружал и устанавливал всё. Обратная связь «нужна эта утилита» → «опубликована для 50 000 разработчиков» занимала часы.


Express.js и callback hell

// Пирамида судьбы — реальный код 2012 года
app.post('/order', function(req, res) {
  User.findById(req.user.id, function(err, user) {
    if (err) return res.send(500, err);
    Product.findById(req.body.productId, function(err, product) {
      if (err) return res.send(500, err);
      if (!product.inStock) return res.send(400, 'Нет в наличии');
      Order.create({ userId: user._id, productId: product._id }, function(err, order) {
        if (err) return res.send(500, err);
        // 3 уровня вложенности и 4 повторения if(err)...
        res.send(201, order);
      });
    });
  });
});

Библиотека async была решением 2012 года для callback hell:

async.waterfall([
  function(cb) { User.findById(req.user.id, cb); },
  function(user, cb) { 
    Product.findById(req.body.productId, function(err, p) { cb(err, user, p); });
  },
  function(user, product, cb) {
    if (!product.inStock) return cb(new Error('Нет в наличии'));
    Order.create({ userId: user._id, productId: product._id }, function(err, o) {
      cb(err, user, o);
    });
  },
], function(err, order) {
  if (err) return res.send(err.message === 'Нет в наличии' ? 400 : 500, err);
  res.send(201, order);
});

Промисы (2013, с Bluebird как популярной библиотекой) и async/await (Node.js 7.6, 2017) в конечном счёте сделали это чистым. Но async.waterfall был прагматичным решением 2012 года.


Проблема «нанять один раз»

Самый глубокий эффект Node.js был не техническим. Он был организационным.

До Node.js: веб-проект требовал PHP или Ruby разработчиков для бэкенда И JavaScript разработчиков для фронтенда. Два набора навыков, два найма.

После Node.js: один JavaScript разработчик мог написать Express бэкенд, React фронтенд, CLI-инструменты сборки и WebSocket слой реального времени. Всё на одном языке. Всё с одним npm-инструментарием.

Это изменило работу небольших команд. Изменило найм в стартапах. Изменило значение «full-stack разработчик» — больше не «кто-то, знающий PHP и jQuery», а «кто-то, глубоко знающий JavaScript, фронтенд и бэкенд».

Node.js — не лучшая серверная среда для каждого случая (Python для ML, Go для высоконагруженных систем). Но он был первым убедительным аргументом, что один язык может быть правильным ответом для большей части веб-стека. Этот аргумент победил.

Читайте также

Реальное время на Node.js: WebSockets и MongoDB вместо бесконечного pollingaunimeda
Backend

Реальное время на Node.js: WebSockets и MongoDB вместо бесконечного polling

Как мы заменили AJAX-опрос каждые 5 секунд на WebSocket-соединение с Socket.io - и почему событийная модель Node.js навсегда изменила наш подход к серверной разработке.

Почему мы выбрали Node.js в 2011 годуaunimeda
Backend

Почему мы выбрали Node.js в 2011 году

В 2011 году Node.js было 2 года, у него не было LTS, и большинство PHP-разработчиков считали его игрушкой. Мы всё равно перенесли на него реал-тайм дашборд. Вот точное рассуждение, принятые риски и что получилось.

Оптимизация MySQL: переход с MyISAM на InnoDB и кэширование через Memcachedaunimeda
Backend

Оптимизация MySQL: переход с MyISAM на InnoDB и кэширование через Memcached

Как мы спасли продакшен-базу от деградации под нагрузкой: миграция с MyISAM на InnoDB, настройка пула буферов и внедрение Memcached - реальный кейс 2011 года.

Нужна IT-разработка для вашего бизнеса?

Разрабатываем сайты, мобильные приложения и AI-решения для бизнеса в Кыргызстане. Бесплатная консультация.

Получить консультацию Все статьи