Как Node.js обрабатывает большое количество одновременных запросов?

«Как Node.js обрабатывает большое количество одновременных запросов?» — вопрос из категории Node.js Core, который задают на 26% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Node.js справляется с высокой нагрузкой благодаря своей однопоточной, неблокирующей архитектуре на основе событийного цикла (Event Loop). Вместо создания отдельного потока для каждого соединения (как в традиционных серверах), он использует асинхронный ввод-вывод и обратные вызовы (callbacks), что позволяет одному процессу эффективно обслуживать тысячи одновременных подключений.

Ключевые компоненты архитектуры:

  1. Event Loop (libuv): Обрабатывает асинхронные операции (сетевые запросы, чтение файлов). Пока одна операция ожидает ответа (например, от БД), цикл может обрабатывать другие запросы.
  2. Worker Pool: Для блокирующих, ресурсоемких задач (например, хэширование паролей, работа с файловой системой) используется пул рабочих потоков, чтобы не блокировать основной цикл событий.
  3. Кластеризация (Cluster module): Для использования всех ядер процессора можно запустить несколько процессов Node.js. Главный процесс (master) распределяет входящие соединения между рабочими процессами (workers).

Пример кластеризации:

const cluster = require('cluster');
const os = require('os');
const http = require('http');

if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);
  // Запускаем worker'ов по количеству CPU
  for (let i = 0; i < os.cpus().length; i++) {
    cluster.fork();
  }
} else {
  // Workers создают HTTP-сервер
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('Hello from worker ' + process.pid);
  }).listen(8000);
  console.log(`Worker ${process.pid} started`);
}

Дополнительные стратегии для масштабирования:

  • Балансировщик нагрузки (Nginx): Распределение запросов между несколькими экземплярами Node.js.
  • Кеширование (Redis/Memcached): Хранение часто запрашиваемых данных в памяти для снижения нагрузки на БД.
  • Оптимизация асинхронного кода: Использование async/await и избегание блокирующих синхронных операций в основном потоке.