Что делать если обработка запроса привела к необходимости завершить процесс (ведь он обслуживает много запросов параллельно)?nodejs-95

Когда возникает необходимость завершить процесс Node.js, который обрабатывает множество запросов, важно сделать это корректно, чтобы:

  1. Завершить все текущие операции
  2. Не оборвать активные соединения
  3. Обеспечить бесперебойность работы системы

1. Graceful Shutdown - основной подход

Graceful Shutdown - это процесс корректного завершения работы приложения, который включает:

process.on('SIGTERM', () => {
  console.log('Получен SIGTERM. Инициирую Graceful Shutdown');

  // 1. Прекращаем принимать новые соединения
  server.close(() => {
    console.log('Все соединения закрыты');

    // 2. Закрываем подключения к БД, кэшу и т.д.
    database.disconnect().then(() => {

      // 3. Завершаем процесс
      process.exit(0);
    });
  });

  // Принудительное завершение по таймауту
  setTimeout(() => {
    console.error('Принудительное завершение по таймауту');
    process.exit(1);
  }, 5000);
});

2. Использование кластеризации

При работе с кластером нужно учитывать завершение всех воркеров:

const cluster = require('cluster');

if (cluster.isMaster) {
  // Обработка сигналов в мастер-процессе
  process.on('SIGTERM', () => {
    const workers = Object.values(cluster.workers);

    workers.forEach(worker => {
      worker.send('shutdown');
      worker.disconnect();

      setTimeout(() => {
        if (!worker.isDead()) worker.kill();
      }, 5000);
    });
  });
} else {
  // Обработка в воркере
  process.on('message', (msg) => {
    if (msg === 'shutdown') {
      gracefulShutdown();
    }
  });
}

3. Работа с HTTP-сервером

Для HTTP-сервера важно:

const server = app.listen(3000);

const gracefulShutdown = () => {
  // Закрываем сервер для новых соединений
  server.close(() => {
    // Дополнительная очистка
    redisClient.quit();
    dbConnection.end();

    process.exit(0);
  });

  // Закрываем все keep-alive соединения
  server.closeIdleConnections();
};

4. Обработка незавершенных операций

Для отслеживания активных запросов:

let activeRequests = 0;

app.use((req, res, next) => {
  activeRequests++;
  res.on('finish', () => activeRequests--);
  next();
});

const shutdown = () => {
  server.close(() => {
    const interval = setInterval(() => {
      if (activeRequests === 0) {
        clearInterval(interval);
        process.exit(0);
      }
    }, 100);
  });
};

5. Использование process managers

Профессиональные решения:

  • PM2 (с pm2 gracefulReload)
  • Kubernetes (с правильными настройками liveness/readiness проб)
  • Systemd (с правильными сигналами)
# Пример с PM2
pm2 reload app --update-env

6. Обработка критических ошибок

Для непредвиденных ошибок:

process.on('uncaughtException', (err) => {
  logger.fatal(err);

  // Даем время на логирование
  setTimeout(() => {
    process.exit(1);
  }, 1000);
});

7. Health checks и readiness probes

Важно для оркестраторов:

app.get('/health', (req, res) => {
  if (shuttingDown) {
    return res.status(503).end();
  }
  res.status(200).json({ status: 'OK' });
});

Резюмируем:

Для безопасного завершения процесса Node.js необходимо:

  1. Реализовать Graceful Shutdown
  2. Прекратить прием новых соединений
  3. Дождаться завершения текущих операций
  4. Закрыть все внешние подключения
  5. Использовать таймауты для принудительного завершения
  6. Интегрировать решение с используемым процесс-менеджером
  7. Обеспечить мониторинг состояния приложения