Балансировка нагрузки и отказоустойчивости

Когда ваш проект растёт, один сервер перестаёт справляться. Даже самый мощный процессор и гигабайты памяти не спасут, если количество запросов растёт лавинообразно. А если этот сервер упадёт — проект умрёт вместе с ним. Решение — балансировка нагрузки и отказоустойчивость.

В этой статье мы подробно разберём:

  • Что такое балансировка нагрузки и зачем она нужна.
  • Уровни балансировки (L4, L7).
  • Алгоритмы распределения трафика.
  • Nginx как балансировщик: настройка upstream, health checks, методы распределения.
  • HAProxy — более мощный балансировщик.
  • Отказоустойчивость: репликация, кластеризация, keepalived, Pacemaker.
  • Балансировка баз данных: репликация master-slave, master-master.
  • Docker Swarm и Kubernetes для оркестрации.
  • Практические примеры и частые ошибки.

1. Что такое балансировка нагрузки

Балансировка нагрузки — это распределение входящего трафика между несколькими серверами, чтобы:

  • Увеличить производительность (запросы обрабатываются параллельно).
  • Обеспечить отказоустойчивость (если один сервер упал, трафик идёт на другие).
  • Упростить масштабирование (можно добавлять серверы по мере роста).
  • Проводить обслуживание без простоя (выводить серверы из пула по очереди).

Типы балансировки:

  • Аппаратная — специальные устройства (F5, Citrix NetScaler). Дорого, но очень производительно.
  • Программная — Nginx, HAProxy, Traefik, Envoy. Гибко, дешево, популярно.
  • DNS-балансировка — несколько A-записей для домена. Просто, но нет контроля состояния.
  • Облачная — AWS ELB, Yandex Cloud Load Balancer, DigitalOcean Load Balancer.

Мы сосредоточимся на программной балансировке, так как она наиболее доступна и гибка.


2. Уровни балансировки

2.1 L4 (транспортный уровень)

Балансировка на уровне TCP/UDP. Балансировщик видит только IP-адреса и порты, не заглядывая в содержимое пакетов.

Плюсы:

  • Очень высокая производительность.
  • Не зависит от протокола приложения (HTTP, MySQL, WebSocket).

Минусы:

  • Нет возможности маршрутизировать по URL, заголовкам, cookie.
  • Нет кеширования, сжатия, SSL-терминации (если не настроено отдельно).

Примеры: HAProxy (в режиме TCP), Nginx (stream), AWS NLB.

2.2 L7 (прикладной уровень)

Балансировка на уровне HTTP/HTTPS. Балансировщик анализирует содержимое запроса: URL, заголовки, cookie.

Плюсы:

  • Гибкая маршрутизация (например, /api на одни серверы, /static на другие).
  • SSL-терминация, кеширование, сжатие.
  • Health checks на уровне HTTP.

Минусы:

  • Меньше производительность, чем L4.
  • Требует больше ресурсов.

Примеры: Nginx, HAProxy (HTTP-режим), Traefik, Envoy, AWS ALB.

Выбор: для веб-приложений обычно используют L7 (Nginx или HAProxy). Для баз данных и других TCP-сервисов — L4.


3. Алгоритмы распределения трафика

АлгоритмОписаниеКогда использовать
Round RobinПо очереди: первый запрос — на сервер 1, второй — на сервер 2, и т.д.Равномерная нагрузка, серверы одинаковой мощности.
Weighted Round RobinТо же, но с весами: сервер с весом 3 получает в 3 раза больше запросов.Серверы разной мощности.
Least ConnectionsЗапрос идёт на сервер с наименьшим числом активных соединений.Долгие соединения, разная нагрузка.
Weighted Least ConnectionsКомбинация весов и наименьших соединений.Серверы разной мощности + долгие соединения.
IP HashСервер выбирается по хешу IP клиента. Один клиент всегда попадает на один сервер.Нужны «липкие сессии» без cookie.
URL HashСервер выбирается по хешу URL.Кеширование на серверах.
RandomСлучайный выбор.Простота, но менее предсказуемо.

«Липкие сессии» (sticky sessions): когда нужно, чтобы клиент всегда попадал на один и тот же сервер (например, для сессий в памяти). Реализуется через cookie или IP hash.


4. Nginx как балансировщик нагрузки

Nginx — самый популярный инструмент для балансировки. Он может работать как L4 (stream) и L7 (http).

4.1 Базовая настройка upstream

Nginx
http {
    upstream backend {
        server backend1.example.com;
        server backend2.example.com;
        server backend3.example.com;
    }

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

4.2 Веса и параметры серверов

Nginx
upstream backend {
    server backend1.example.com weight=3;
    server backend2.example.com weight=1;
    server backend3.example.com backup;   # резервный, включается только если основные недоступны
}

Параметры:

  • weight — вес (по умолчанию 1).
  • max_fails — количество неудачных попыток до пометки сервера как недоступного (по умолчанию 1).
  • fail_timeout — время, на которое сервер исключается (по умолчанию 10 секунд).
  • backup — резервный сервер.
  • down — пометить сервер как выключенный.

4.3 Методы распределения

Nginx
upstream backend {
    least_conn;   # Least Connections
    # или
    ip_hash;      # IP Hash
    # или
    # random;     # Random (требует модуль)
    
    server backend1.example.com;
    server backend2.example.com;
}

4.4 Health checks (активные)

В open-source Nginx активные health checks отсутствуют (только пассивные через max_fails). В Nginx Plus есть активные проверки.

ВАЖНО! В современных сборках (OpenResty, Tengine) есть модуль nginx_upstream_check_module, который как раз и даёт активные проверки. Если нельзя поставить Nginx Plus, можно использовать OpenResty с этим модулем.

Пассивные health checks: если сервер возвращает ошибку (5xx, timeout), он временно исключается.

Активные health checks через сторонние модули:

  • nginx_upstream_check_module (Tengine, openresty).
  • Или использовать HAProxy, у которого активные проверки встроены.

4.5 Кеширование и сжатие на балансировщике

Nginx
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m;

server {
    location / {
        proxy_cache my_cache;
        proxy_cache_valid 200 302 10m;
        proxy_cache_valid 404 1m;
        proxy_pass http://backend;
    }
}

4.6 SSL-терминация

Nginx может принимать HTTPS и передавать трафик на бэкенды по HTTP (внутри доверенной сети).

Nginx
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        proxy_pass http://backend;
        proxy_set_header X-Forwarded-Proto https;
    }
}

4.7 L4-балансировка (stream)

Nginx
stream {
    upstream mysql_backend {
        server db1.example.com:3306;
        server db2.example.com:3306;
    }

    server {
        listen 3306;
        proxy_pass mysql_backend;
    }
}

5. HAProxy — мощный балансировщик

HAProxy — это специализированный балансировщик, который часто используется для высоконагруженных систем. Он поддерживает L4 и L7, активные health checks, подробную статистику.

5.1 Установка

Bash
sudo apt install haproxy

5.2 Базовая конфигурация

Файл /etc/haproxy/haproxy.cfg:

Объяснение:

  • frontend — точка входа (порт 80).
  • backend — пул серверов.
  • balance roundrobin — алгоритм.
  • option httpchk GET /health — активная проверка: HAProxy отправляет GET /health каждые несколько секунд.
  • check — включить проверку.
  • backup — резервный сервер.

5.3 Статистика

HAProxy имеет встроенную страницу статистики.

Теперь http://your-server:8404/stats покажет состояние всех серверов, количество запросов, ошибки и т.д.

5.4 HAProxy vs Nginx

КритерийNginxHAProxy
Основное назначениеВеб-сервер + балансировщикСпециализированный балансировщик
L4/L7ДаДа
Активные health checksНет (в open-source)Да
СтатистикаБазоваяПодробная
ПроизводительностьОчень высокаяОчень высокая
Гибкость настройкиВысокаяОчень высокая
КешированиеДаНет (только балансировка)

Выбор: если нужен только балансировщик с активными проверками — HAProxy. Если совмещать с веб-сервером — Nginx.


6. Отказоустойчивость

Балансировка — это половина дела. Вторая половина — сделать так, чтобы отказ сервера не приводил к падению всего проекта.

6.1 Резервирование балансировщика (keepalived)

Если у вас один балансировщик, он становится единой точкой отказа. Решение — два балансировщика с виртуальным IP (VIP), который переключается при отказе.

keepalived реализует протокол VRRP: один сервер — мастер, второй — бэкup. Если мастер падает, backup забирает VIP.

Установка:

Bash
sudo apt install keepalived

Конфигурация на мастере (/etc/keepalived/keepalived.conf):

На бэкенде:

Теперь 192.168.1.100 — это VIP, который всегда указывает на активный балансировщик. Если мастер падает, backup забирает VIP и продолжает работу.

6.2 Кластеризация баз данных

Репликация master-slave:

  • Один сервер — мастер (принимает запись и чтение).
  • Один или несколько слейвов (только чтение).
  • Если мастер падает, слейв можно повысить до мастера.

MySQL репликация:

На мастере в my.cnf:

Создание пользователя для репликации:

SQL
CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;

На слейве:

SQL
CHANGE MASTER TO
    MASTER_HOST='master_ip',
    MASTER_USER='repl',
    MASTER_PASSWORD='password',
    MASTER_LOG_FILE='mysql-bin.000001',
    MASTER_LOG_POS=123;
START SLAVE;  <---- ВАЖНО! Синтаксис зависит от версии

PostgreSQL репликация:

На мастере в postgresql.conf:

На слейве:

Bash
pg_basebackup -h master_ip -D /var/lib/postgresql/15/main -U replicator -P -v

Master-master репликация: оба сервера принимают запись, но требует разрешения конфликтов. Сложнее и используется реже. ВАЖНО! На реплике ещё нужен recovery.conf (до 12 версии) или primary_conninfo в postgresql.auto.conf (12+).

6.3 Кластеризация приложений

Приложения должны быть stateless (не хранить состояние на сервере). Сессии — в Redis или БД. Файлы — в S3 или общем хранилище. Тогда можно запускать сколько угодно серверов, и любой из них обработает любой запрос.

6.4 Docker Swarm

Docker Swarm — встроенная оркестрация контейнеров.

Bash
# Инициализация кластера
docker swarm init

# Добавление рабочих узлов
docker swarm join --token <token> <manager-ip>:2377

# Развёртывание стека
docker stack deploy -c docker-compose.yml myapp

# Масштабирование сервиса
docker service scale myapp_web=5

Swarm автоматически распределяет контейнеры по узлам, перезапускает упавшие, балансирует нагрузку.

6.5 Kubernetes

Kubernetes — более мощный оркестратор. Он умеет:

  • Автоматически масштабировать поды (Horizontal Pod Autoscaler).
  • Самовосстанавливаться (перезапускать упавшие поды).
  • Балансировать нагрузку через Service.
  • Управлять конфигурациями и секретами.

Пример Deployment:

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web
        image: myapp:latest
        ports:
        - containerPort: 80

Service для балансировки:

YAML
apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 80
  type: LoadBalancer

Kubernetes сам создаст балансировщик и распределит трафик между подами.

7. Практические примеры

7.1 Схема отказоустойчивого веб-проекта

ВАЖНО! MHA — это один из вариантов, а ещё бывают Orchestrator, Patroni (для PostgreSQL) и т.п.

Как это работает:

  1. DNS указывает на VIP (192.168.1.100).
  2. Keepalived направляет трафик на активный Nginx.
  3. Nginx балансирует запросы между App Server 1 и 2.
  4. App Server’ы читают данные с MySQL Slave, пишут в MySQL Master.
  5. Если Master падает, Slave повышается до Master (вручную или через MHA).
  6. Если Nginx Master падает, backup забирает VIP.

7.2 Настройка health check для приложения

Приложение должно иметь эндпоинт /health, который возвращает 200 OK, если всё хорошо.

PHP
<?php
// health.php
$host = getenv('DB_HOST') ?: 'localhost';
$db   = getenv('DB_NAME') ?: 'mydb';
$user = getenv('DB_USER') ?: 'user';
$pass = getenv('DB_PASS') ?: 'REPLACE_WITH_PASSWORD';

try {
    $dsn = "mysql:host=$host;dbname=$db;charset=utf8mb4";
    $pdo = new PDO($dsn, $user, $pass, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_TIMEOUT => 2,
    ]);
    $pdo->query('SELECT 1');
    http_response_code(200);
    echo json_encode(['status' => 'ok']);
} catch (Exception $e) {
    http_response_code(500);
    echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}

Балансировщик будет проверять этот эндпоинт и исключать серверы, которые возвращают ошибку.

7.3 Автоматическое масштабирование в Kubernetes

YAML
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

ВАЖНО! Для работы метрик в кластере должен быть установлен Metrics Server.

8. Частые ошибки и лучшие практики

ОшибкаПоследствияРешение
Один балансировщикЕдиная точка отказаИспользуйте keepalived + два балансировщика.
Нет health checksТрафик идёт на упавшие серверыНастройте активные или пассивные проверки.
Сессии в памяти приложенияПользователь «теряет» сессиюХраните сессии в Redis или БД.
Файлы на локальном дискеПри переключении сервера файлы недоступныИспользуйте S3 или общее хранилище.
Нет репликации БДПотеря данных при отказеНастройте master-slave репликацию.
Игнорирование мониторингаПроблемы остаются незамеченнымиМониторьте балансировщик, серверы, БД.
Нет плана восстановленияПростой затягиваетсяДокументируйте процедуры восстановления.

Лучшие практики:

  • Делайте приложения stateless.
  • Используйте несколько зон доступности (если облако).
  • Регулярно тестируйте отказоустойчивость (отключайте серверы, смотрите, что происходит).
  • Автоматизируйте развёртывание и масштабирование.
  • Мониторьте всё: балансировщик, серверы, БД, сеть.
  • Документируйте архитектуру и процедуры.