Как не заблокировать обслуживание других пользователей, обрабатывая запрос от одного из них?nodejs-94

Основная философия Node.js - "не блокировать цикл событий" (Don't Block the Event Loop). Вот ключевые подходы для обеспечения этого:

1. Понимание цикла событий

Node.js использует однопоточную модель с циклом событий. Долгие синхронные операции блокируют весь процесс.

// ПЛОХО: синхронная операция блокирует цикл событий
app.get('/blocking', (req, res) => {
  const result = heavySyncComputation(); // Блокирует все!
  res.send(result);
});

2. Использование асинхронных операций

Все I/O операции в Node.js по умолчанию асинхронны:

// ХОРОШО: асинхронная операция не блокирует
app.get('/non-blocking', async (req, res) => {
  const result = await heavyAsyncComputation(); // Не блокирует
  res.send(result);
});

3. Разделение тяжелых вычислений

Для CPU-интенсивных задач используйте:

a) Worker Threads

const { Worker } = require('worker_threads');

app.get('/compute', (req, res) => {
  const worker = new Worker('./heavy-computation.js');
  worker.on('message', (result) => {
    res.send(result);
  });
});

b) Дочерние процессы

const { fork } = require('child_process');

app.get('/fork', (req, res) => {
  const compute = fork('./compute.js');
  compute.send('start');
  compute.on('message', (result) => {
    res.send(result);
  });
});

4. Ограничение времени выполнения

Устанавливайте таймауты для операций:

app.get('/with-timeout', (req, res) => {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Timeout')), 5000);
  });

  Promise.race([longOperation(), timeoutPromise])
    .then(result => res.send(result))
    .catch(err => res.status(500).send(err.message));
});

5. Кластеризация

Используйте все ядра CPU:

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

if (cluster.isMaster) {
  os.cpus().forEach(() => cluster.fork());
} else {
  // Worker process
  require('./server');
}

6. Оптимизация кода

  • Избегайте синхронных версий методов (например, fs.readFileSync)
  • Разбивайте большие задачи на мелкие (nextTick)
  • Используйте потоковую обработку данных
// Потоковая обработка файла
const fs = require('fs');
app.get('/stream', (req, res) => {
  const stream = fs.createReadStream('./large-file.txt');
  stream.pipe(res); // Не блокирует цикл событий
});

7. Использование очередей

Для действительно тяжелых задач используйте очереди (RabbitMQ, Bull):

const Queue = require('bull');

const queue = new Queue('heavyTasks');

app.post('/task', (req, res) => {
  queue.add(req.body); // Обработается в фоне
  res.send('Task queued');
});

Резюмируем:

Чтобы не блокировать обработку запросов других пользователей, используйте асинхронные операции, выносите тяжелые вычисления в отдельные потоки/процессы, применяйте кластеризацию и очереди. Главное правило - никогда не блокируйте цикл событий синхронными операциями.