← Назад к вопросам

Что происходило с сайтом на последнем проекте при перезапуске сервера?

2.0 Middle🔥 171 комментариев
#JavaScript Core

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Анализ поведения сайта при перезапуске сервера на последнем проекте

На последнем крупном проекте — высоконагруженной SPA-платформе для электронной коммерции — перезапуск сервера (бэкенда) был тщательно спланированным процессом, так как напрямую влиял на пользовательский опыт. Архитектура была следующей:

  • Frontend: React-приложение, раздаваемое через CDN (CloudFront) и nginx на сервере статики.
  • Backend: Микросервисы на Node.js и Python (Django), балансировка через Kubernetes.
  • База данных: PostgreSQL с репликацией.
  • Кэш: Redis-кластер.

Что происходило с пользовательской сессией?

При инициированном администратором "мягком" перезапуске (rolling restart) поведение было следующим:

  1. Для новых пользователей или пользователей на статических страницах: Никаких изменений. Статика (HTML, CSS, JS, изображения) продолжала отдаваться с CDN и серверов статики, которые не перезапускались.
  2. Для пользователей в активном сеансе с открытыми AJAX–/WebSocket–соединениями:
    *   **Запросы к перезапускаемому в данный момент инстансу бэкенда** начинали получать ошибки **5xx** (502 Bad Gateway, 503 Service Unavailable). Это обрабатывалось на фронтенде.

// Пример реализации на фронтенде (интерцептор axios)
axios.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response && error.response.status >= 500) {
      // Показываем пользователю понятное уведомление, а не "Internal Server Error"
      showToast('Сервис временно недоступен, попробуйте через мгновение');
      // И автоматически повторяем запрос с экспоненциальной задержкой
      return retryRequest(error.config);
    }
    return Promise.reject(error);
  }
);
  • Балансировщик нагрузки (Ingress в Kubernetes) перенаправлял новые запросы на здоровые инстансы сервиса. Однако уже установленные WebSocket-соединения обрывались.
// Обработка обрыва WebSocket на клиенте
const socket = new WebSocket('wss://api.example.com/ws');

socket.onclose = (event) => {
  if (!event.wasClean) {
    // Нечистое закрытие (например, сервер умер)
    console.log('Соединение прервано. Пытаемся переподключиться...');
    setTimeout(() => {
      establishNewWebSocketConnection(); // Функция повторного подключения
    }, 2000);
  }
};
  1. Состояние авторизации: Мы использовали JWT-токены, хранимые в HttpOnly куках и localStorage (для доступа API). Так как сервер сессий (stateful) отсутствовал, факт аутентификации не терялся. Однако любой запрос, отправленный в момент недоступности сервиса авторизации, мог fail.

Критические проблемы и их решения

Основные вызовы и реализованные стратегии их смягчения:

  • Потеря "горячих" данных в памяти инстанса. Например, кэшированные на уровне одного сервера результаты тяжелых запросов. Решение: Мы отказались от in-memory кэша на бэкенде в пользу распределенного Redis. Все инстансы читали и писали в него.
  • "Висящие" (pending) запросы. Клиенты могли ждать ответа на запрос, который уже никогда не придет. Решение: Выставляли корректные таймауты на стороне фронтенда и использовали health checks на бэкенде. Перед перезапуском Kubernetes удалял умирающий под из списка здоровых эндпоинтов.
# Пример конфигурации health check в upstream nginx (упрощенно)
upstream backend {
    server backend1:3000 max_fails=3 fail_timeout=30s;
    server backend2:3000 max_fails=3 fail_timeout=30s;
    check interval=5000 rise=2 fall=3 timeout=1000 type=http;
    check_http_send "GET /health HTTP/1.0\r\n\r\n";
    check_http_expect_alive http_2xx http_3xx;
}
  • Согласованность данных. Чтобы избежать ситуации, когда часть сервисов работает с новой версией API, а часть — со старой, мы внедрили систему версионирования API (/api/v1/, /api/v2/), а также стратегию "выключить старую версию после перевода всех клиентов".

Процесс деплоя и перезапуска

Чтобы минимизировать влияние, процесс был автоматизирован через CI/CD (GitLab CI):

  1. Сборка нового Docker-образа фронтенда и бэкенда.
  2. Постепенное (rolling update) обновление подов в Kubernetes: новый под запускался, проходил health check, и только потом старый удалялся.
  3. Для фронтенда: Мы использовали A/B-раздачу. Новая версия JS-файлов загружалась с другим хэшем в имени, поэтому пользователи, уже работающие со старым приложением, не получали разрывов. Они получали обновление при следующей загрузке страницы.

Итог: Благодаря архитектуре, разделяющей статику и API, использованию stateless-авторизации, распределенному кэшу и корректным health checks, перезапуск сервера (бэкенда) для большинства пользователей проходил либо незаметно (простое отображение статики), либо с кратковременными задержками и автоматическими повторами запросов. Основная задача фронтенда в этом сценарии — корректно обрабатывать ошибки связи и давать пользователю понятную обратную связь, а не паниковать.