Маршрутизация трафика к stateful-приложениям: стратегии для баз данных и очередей сообщений | AdminWiki
Timeweb Cloud — сервера, Kubernetes, S3, Terraform. Лучшие цены IaaS.
Попробовать

Маршрутизация трафика к stateful-приложениям: стратегии для баз данных и очередей сообщений

12 июня 2026 8 мин. чтения

Почему обычная балансировка нагрузки ломает stateful-приложения

Балансировка нагрузки по алгоритмам round-robin или least connections работает для stateless-сервисов, где любой экземпляр обработает запрос одинаково. Для stateful-приложений эта логика приводит к потере данных и нарушению консистентности. Запись на реплику PostgreSQL вместо мастера создает рассинхронизацию. Чтение с отставшего брокера Kafka возвращает устаревшие сообщения. Подключение к узлу RabbitMQ, не владеющему очередью, завершается ошибкой.

Ключевое отличие stateful-сервисов - привязка данных или состояния к конкретному узлу. Маршрутизация должна учитывать роль узла в кластере: мастер или реплика, лидер партиции или фолловер. Неправильная маршрутизация вызывает split-brain, когда две части кластера считают себя активными и принимают изменения независимо. Результат - поврежденные данные, которые сложно восстановить.

Ключевые отличия stateful-сервисов: PostgreSQL, Redis, Kafka, RabbitMQ

  • PostgreSQL: строгая архитектура мастер-реплика. Все операции записи (INSERT, UPDATE, DELETE) выполняются только на мастер-узле. Реплики работают в режиме read-only для масштабирования чтения.
  • Redis (Sentinel/Cluster): мастер обрабатывает запись, реплики обслуживают чтение. Роли узлов определяет Sentinel или внутренний протокол кластера. Клиенты должны знать текущего мастера.
  • Kafka: данные разделены на партиции. Каждая партиция имеет лидера для чтения и записи. Брокер может быть лидером для одних партиций и фолловером для других.
  • RabbitMQ (кластер): очередь привязана к одному узлу-владельцу. Клиенты подключаются к этому узлу для операций с очередью. Зеркалирование реплицирует данные, но запись идет через лидера.

Единого решения маршрутизации для всех технологий нет. Каждая требует своей стратегии проверки состояния и определения роли.

Практические рецепты настройки health check для разных технологий

Проверка доступности порта (TCP health check) недостаточна для stateful-сервисов. Health check должен определять реальную роль узла в кластере и его готовность принимать трафик определенного типа. Используйте специализированные запросы к API или выполнение команд через клиентские утилиты.

Для PostgreSQL: проверка pg_is_in_recovery() через HAProxy или custom script

Настройте HTTP health check в HAProxy с endpoint, который выполняет SQL-запрос для определения роли узла. Создайте скрипт или используйте встроенные возможности pgpool.

# Конфигурация HAProxy для PostgreSQL
backend pg_backend
    mode tcp
    balance source
    option httpchk GET /health
    http-check expect status 200
    server pg-master 192.168.1.10:5432 check port 8000
    server pg-replica 192.168.1.11:5432 check port 8000 backup

# Пример endpoint /health в приложении или через pg_isready
# Возвращает 200, если SELECT pg_is_in_recovery() = false (мастер)
# Возвращает 503, если узел реплика или недоступен

Альтернатива - bash-скрипт для TCP health check, который подключается к базе и проверяет статус. Учитывайте таймауты подключения и лаг репликации. Для реплик можно добавить проверку pg_last_wal_receive_lsn() для контроля отставания.

Для Redis: интеграция с Sentinel и проверка роли

Балансировщик должен взаимодействовать с Redis Sentinel для получения актуальной информации о мастере. Не кэшируйте IP-адрес мастера, так как он меняется при failover.

# Конфигурация HAProxy с внешним скриптом для Sentinel
backend redis_master
    mode tcp
    balance roundrobin
    option tcp-check
    tcp-check send "PING\r\n"
    tcp-check expect string +PONG
    tcp-check send "INFO replication\r\n"
    tcp-check expect string role:master
    server redis1 192.168.1.20:6379 check
    server redis2 192.168.1.21:6379 check

# Скрипт для опроса Sentinel
#!/bin/bash
MASTER=$(redis-cli -h sentinel-host -p 26379 SENTINEL get-master-addr-by-name mymaster | head -1)
if [ "$MASTER" = "192.168.1.20" ]; then
    exit 0
else
    exit 1
fi

Для Redis Cluster проверяйте, что узел обслуживает слоты через команды CLUSTER INFO и CLUSTER SLOTS. Узел в состоянии fail или без назначенных слотов должен исключаться из балансировки.

Для Kafka: определение лидера партиции и состояния брокера

TCP-проверки порта 9092 недостаточно. Брокер может быть здоров, но не являться лидером для партиций, нужных клиенту. Используйте JMX-метрики или Kafka Admin API.

# Python-скрипт для проверки через kafka-python
from kafka import KafkaAdminClient
from kafka.errors import KafkaError

try:
    admin = KafkaAdminClient(bootstrap_servers='localhost:9092')
    cluster_metadata = admin.describe_cluster()
    # Проверяем, что брокер является лидером хотя бы для одной партиции
    partitions = admin.list_topics()
    if partitions:
        exit(0)
    else:
        exit(1)
except KafkaError:
    exit(1)

# JMX-метрика для мониторинга
# kafka.server:type=ReplicaManager,name=LeaderCount

Настройте health check, который проверяет метрику LeaderCount через JMX или HTTP endpoint JMX exporter. Значение больше 0 указывает на активную роль брокера.

Для RabbitMQ: проверка готовности узла и владения очередями

Используйте HTTP API management plugin, но не просто endpoint /api/overview, который возвращает общую информацию. Проверяйте состояние узла через /api/health/checks/node.

# Конфигурация HAProxy с HTTP health check
backend rabbit_backend
    mode http
    balance roundrobin
    option httpchk GET /api/health/checks/node
    http-check expect status 200
    server rabbit1 192.168.1.30:15672 check ssl verify none
    server rabbit2 192.168.1.31:15672 check ssl verify none backup

# Критерии готовности из ответа API:
# - "status": "ok"
# - отсутствие alarm по памяти или диску
# - доступность файловых дескрипторов

Для зеркалированных очередей убедитесь, что health check учитывает привязку клиента к узлу-лидеру. Некоторые плагины, например, рассмотренные в руководстве по выбору брокера, предоставляют дополнительные endpoint для проверки владения конкретной очередью.

Предотвращение split-brain и потери данных: настройка кворума и фенси-запросов

Сетевое разделение (split-brain) возникает, когда кластер делится на изолированные части, и каждая считает себя активной. Два мастера в PostgreSQL начинают независимо принимать записи. Данные расходятся, восстановление требует ручного вмешательства и может привести к потере транзакций.

Стратегия предотвращения: обеспечить, что только одна часть кластера остается активной. Используйте фенси-запросы (fencing) для принудительной остановки устаревшего мастера. Настройте кворумные решения, требующие согласия большинства узлов для выбора лидера.

Реализация фенси-запросов в связке с Pacemaker/Corosync или скриптами

Фенсинг через STONITH (Shoot The Other Node In The Head) останавливает или изолирует узел, который может представлять угрозу целостности данных. Реализуйте скрипт, который выполняется при потере связи с партнером.

#!/bin/bash
# Пример скрипта фенсинга для PostgreSQL
TARGET_NODE="192.168.1.10"

# Проверяем, доступен ли узел по сети
ping -c 3 $TARGET_NODE > /dev/null 2>&1
if [ $? -eq 0 ]; then
    # Останавливаем PostgreSQL на целевом узле через SSH
    ssh postgres@$TARGET_NODE "sudo systemctl stop postgresql-16"
    # Дополнительно: отключаем сетевой интерфейс
    ssh postgres@$TARGET_NODE "sudo ip link set eth0 down"
    echo "Fencing executed on $TARGET_NODE"
else
    echo "Target node is already unreachable"
fi

Интегрируйте скрипт в Pacemaker/Corosync как ресурс stonith. Настройте таймауты: операция фенсинга должна завершиться быстрее, чем кластер попытается перезапустить сервис. Операции должны быть идемпотентными - повторный вызов не должен навредить.

Для Redis Sentinel кворум автоматически встроен в алгоритм выбора мастера. Требуется согласие большинства sentinel-инстансов. Убедитесь, что количество sentinel-узлов нечетное (3, 5, 7) и распределено по разным физическим серверам или зонам доступности.

Практическое правило: лучше временная недоступность, чем повреждение данных. Настройте таймауты и пороги так, чтобы система ждала восстановления сети, а не сразу запускала опасный failover.

Диагностика проблем маршрутизации: лог балансировщика и метрики

Когда трафик идет не на нужный узел, начните диагностику с логов балансировщика. Проверьте статусы серверов в бэкенде, срабатывания health check и коды ответов.

Типичные ошибки в конфигурации и их симптомы

  • Health check проходит, но узел не принимает данные: проверка настроена на неправильный порт (например, 5432 для PostgreSQL, но health check идет на 8000). Узел считается здоровым, но основная служба не работает. Решение: убедитесь, что health check тестирует тот же порт и протокол, что и клиентские подключения.
  • Частые флаппинги статусов (UP/DOWN): сетевые задержки или нагрузка на узел приводят к таймаутам health check. Симптом - в логах HAProxy постоянные изменения состояния. Решение: увеличьте параметры inter (интервал проверки) и rise (количество успешных проверок для перевода в UP). Например, inter 5s rise 3.
  • Балансировщик не видит смены мастера: кэширование DNS или статический IP из Sentinel. После failover трафик продолжает идти на старый, уже неактивный мастер. Решение: используйте динамическое разрешение имен через интеграцию с Consul etcd или скрипты, которые обновляют конфигурацию балансировщика.

Чек-лист для расследования:

  1. Проверьте логи HAProxy/Nginx: tail -f /var/log/haproxy.log. Ищите строки с Server pg-backend/pg-master is DOWN или изменения статуса.
  2. Убедитесь, что скрипты health check возвращают корректные коды HTTP: 2xx или 3xx для UP, 5xx для DOWN. Проверьте вывод скрипта вручную.
  3. Сравните таймауты сетевых запросов в скриптах с интервалами проверки балансировщика. Таймаут скрипта должен быть меньше, чем timeout check в конфигурации.
  4. Анализируйте метрики Prometheus, если используется экспортер для HAProxy: haproxy_server_up, haproxy_backend_response_time. Резкий рост времени отклика health check указывает на проблемы узла или сети.

Для комплексного мониторинга инфраструктуры используйте подходы из руководства по архитектуре высоконагруженных систем, где разбираются инструменты диагностики узких мест.

Обеспечение высокой доступности без компромиссов: баланс RTO и RPO

Настройки маршрутизации напрямую влияют на два ключевых показателя: RTO (Recovery Time Objective) и RPO (Recovery Point Objective). RTO определяет максимально допустимое время простоя после сбоя. RPO определяет максимальный объем данных, который можно потерять.

Частые health check (интервал 2-3 секунды) уменьшают RTO - система быстрее обнаруживает сбой и переключает трафик. Но они создают нагрузку на узлы и сеть. Увеличьте интервал до 10-15 секунд для снижения нагрузки, если бизнес допускает более длительное восстановление.

Агрессивный фенсинг и строгий кворум защищают данные (низкий RPO), но могут увеличить простой (высокий RTO). Например, при сетевом разделении кластер ждет восстановления связи или требует ручного вмедшательства, вместо автоматического failover.

Определите SLA вашего сервиса. Для финансовых транзакций RPO близок к нулю - потеря данных недопустима. Для кэша или аналитики можно допустить некоторую потерю данных ради скорости восстановления. Настройте таймауты, интервалы проверок и логику failover исходя из этих требований.

Используйте многоуровневую стратегию: быстрый health check для обнаружения сбоя, но дополнительная проверка консистентности перед переключением на новую ноду. Для сложных сценариев миграции, включающих stateful-сервисы, обратитесь к руководству по поэтапной миграции больших систем.

Помните: универсальных настроек не существует. Тестируйте конфигурации в staging-среде, имитируйте сетевые разделения и отказы узлов. Документируйте принятые решения и их влияние на RTO/RPO. Регулярно проводите учебные аварийные восстановления (disaster recovery drills), чтобы убедиться, что система ведет себя предсказуемо в реальных инцидентах.

Поделиться:
Сохранить гайд? В закладки браузера