Пятая статья серии «Архитектура и производительность». Ранее мы разобрали, когда код надо делить на сервисы (монолит против микросервисов), как они общаются (проектирование API) и как ускорять ответы (кеширование). Теперь посмотрим на систему со стороны сети: у вас есть адрес, по которому приходят пользователи, и группа серверов, которые выполняют работу. Кто-то должен решать, какой именно сервер получит каждый новый запрос. Этот «кто-то» — балансировщик нагрузки. Звучит как деталь больших дата-центров, но нужен даже проекту с двумя серверами. Разберём: зачем он, чем четвёртый уровень отличается от седьмого, какие бывают алгоритмы распределения, как балансер понимает, что сервер умер, и чем опасны «липкие сессии». Плюс готовые фрагменты конфигураций Nginx и HAProxy.
Часть I. Зачем нужен балансировщик: нагрузка и единая точка отказа
1. Проблема одного сервера
Магазин живёт на одном VPS: там и Nginx, и PHP-FPM, и MySQL. Хватает на блог или на магазин с парой тысяч посетителей в день. Но у схемы два недостатка, и оба вылезают, когда проект становится важнее, чем вы ожидали.
Производительность. Один сервер обрабатывает конечное число запросов в секунду: есть предел по CPU, памяти, числу соединений и пропускной способности. Когда магазин попадает в крупную подборку, трафик растёт в десять раз за час, страницы открываются за секунды, потом браузер показывает пустоту. Никакой настройкой одного сервера вы не получите больше, чем у него восемь ядер.
Надёжность. Если сервер один, его поломка — полная недоступность продукта. Упал PHP-FPM — сайт лежит. Кончилось место на диске — сайт лежит. Пользователь не различает «проблема на половине путей» и «нас нет вообще».
Здесь появляется балансировщик нагрузки — промежуточный сервер, который принимает все входящие запросы и сам решает, какому из внутренних передать каждый. Пользователь видит только адрес балансера. Внутри — пул бэкенд-серверов (backend servers, «бэкенды»), их называют также апстримами (upstream — так их зовёт Nginx).
браузеры --HTTP--> [ балансировщик ] --> бэкенд-1 (PHP-FPM)
--> бэкенд-2 (PHP-FPM)
--> бэкенд-3 (PHP-FPM)
При поломке любого бэкенда запросы идут на остальные: пользователь заметит разве что задержку в сотню миллисекунд, но сайт не исчезнет.
2. Единая точка отказа простыми словами
Единая точка отказа (single point of failure, SPOF) — элемент архитектуры, выход которого из строя останавливает всю систему. Каждая точка проверяется вопросом: «если это умрёт — всё ляжет?».
Коварство в том, что балансировщик, поставленный, чтобы избавиться от точки отказа, сам становится точкой отказа. Он один, на него приходят все запросы. Если он живёт на одной машине и на ней всё заканчивается, два бэкенда за ним не спасут: к ним никто не дотронется.
| Что у вас есть | Точка отказа | Стоимость |
|---|---|---|
| Один сервер | Он сам | минимально |
| Балансер + бэкенды | Балансер | немного |
| Балансер + база на одном хосте | База | немного |
| Пара балансеров, бэкенды, реплика базы | Провайдер, DNS | заметно |
| Два дата-центра в разных регионах | Авария региона | дорого |
Главная мысль: точек отказа становится меньше не от добавления серверов вообще, а от дублирования конкретного узкого места. Если узкое место — база, три новых веб-сервера не помогут: они все упадут на ту же мёртвую базу.
Ещё деталь: балансер добавляет сетевой прыжок — обработчик, который читает запрос и переотправляет дальше. Обычно это доли миллисекунды, но при плохом пути задержка складывается, поэтому балансировщик и бэкенды должны жить в одной сети, а не в разных городах.
3. Что балансер умеет, кроме пересылки
- Исключение умершего сервера из пула и возврат его, когда он снова начнёт отвечать.
- Завершение TLS (termination): балансер принимает HTTPS, расшифровывает и отправляет на бэкенды обычный HTTP. Сертификат лежит в одном месте, процессоры бэкендов не тратятся на криптографию.
- Маршрутизация по содержимому:
/api/на микросервисы, статика на файловый сервер, админка — только из внутренней сети. - Переиспользование соединений к бэкендам вместо открытия нового на каждый запрос.
- Сбор метрик: сколько запросов, какая задержка, сколько ошибок (статья Метрики и мониторинг).
Не путайте балансировщик с CDN (content delivery network, сеть доставки контента): CDN раздаёт статику из точек присутствия и не вызывает ваш код, а балансер работает с живыми серверами и динамическими ответами. Они дополняют друг друга: CDN отсекает пиковый трафик на картинках, балансер распределяет то, что дошло до исходного сервера (статья CDN и edge-вычисления).
Часть II. Уровень 4 против уровня 7: куда смотрит балансер
4. Два взгляда на один запрос
Модель OSI (семь уровней сети) нужна только для одного: понять слова «четвёртый уровень» и «седьмой уровень».
- Уровень 4 — транспортный, здесь живут TCP и UDP. Полезная информация: IP-адрес отправителя, IP-адрес получателя, номер порта. Запроса, URL и заголовков тут ещё нет — есть поток байтов в соединении.
- Уровень 7 — прикладной, это HTTP: метод (
GET,POST), путь (/api/orders/123), заголовки (Authorization,Cookie), тело запроса.
Балансировщик четвёртого уровня работает с соединением целиком: не разбирает HTTP, увидел пакет с адресами и портом — передал в нужное место. Клиент и бэкенд дальше общаются напрямую.
Балансировщик седьмого уровня заканчивает соединение сам: принимает HTTP-запрос клиента целиком, разбирает, решает, кому отправить, открывает отдельное соединение с бэкендом и передаёт запрос туда. Клиентское и серверное соединение — два независимых куска.
| Признак | L4 | L7 |
|---|---|---|
| Что видит | IP-адреса, порты | URL, заголовки, куки, тело |
| Скорость | выше: не разбирает HTTP | ниже: парсит каждый запрос |
Маршрутизация по /api/ | нет | да |
| Работа с TLS | пробрасывает | расшифровывает на себе |
| Health check | «порт открыт» | «страница /ready вернула 200» |
L4 имеет смысл, когда трафик очень большой и важнее скорость; когда протокол вообще не HTTP (сырой сокет, база данных); когда бэкенды сами разбирают TLS. Примеры: LVS, IPVS, облачный балансер в сетевом режиме, HAProxy в режиме tcp.
L7 нужен почти всегда в веб-разработке: отправить /api/ на PHP-FPM, а /images/ на раздачу файлов; прочитать Host, чтобы обслуживать несколько доменов с одного балансера; проверять живость не «порт слушает», а «страница отвечает 200»; корректно работать с WebSocket и HTTP/2. Классические реализации — Nginx и HAProxy (оба в режиме http), реже Traefik, Envoy, AWS ALB.
5. Где физически живёт балансер
Облачный управляемый (ALB в AWS, «Балансировщик нагрузок» в Yandex Cloud): платите за час, инфраструктуру обслуживает провайдер, метрики уже есть, внутри облака он сам дублирован. Минус — привязка к облаку и цена при большом трафике. Свой на отдельном VPS: полный контроль, дёшево, прозрачно; минус — вы сами отвечаете за обновления и за свою единственную точку отказа. Внутри того же Nginx, что раздаёт сайт — самый частый вариант для небольшой команды: тот же L7, только не выделенный в отдельный сервис; зато раздатчик статики и балансировщик становятся одним процессом, и его поломка валит всё.
DNS-балансировка (домен отдаёт несколько A-записей) настоящей балансировки не даёт: браузер не знает, жив ли сервер, а кеш DNS держит ответ часами. Как способ развести по геологии — годится, как замена балансировщику — нет.
Часть III. Алгоритмы распределения: round-robin, least-connections, IP-hash
Алгоритм (метод балансировки) — правило, по которому балансер выбирает бэкенд для следующего запроса.
6. Round-robin: по кругу
Самый простой метод: указатель сдвигается на каждый запрос — первый серверу 1, второй серверу 2, третий серверу 3, четвёртый снова серверу 1. В Nginx это метод по умолчанию, отдельной директивы не надо. weight (вес) задаёт долю запросов, если серверы разные по мощности:
upstream backend_rr {
# round-robin по умолчанию; weight — доля запросов на сервер
server 10.0.0.11:9000 weight=3; # новая машина: 3 запроса из 5
server 10.0.0.12:9000 weight=2; # старая: 2 запроса из 5
server 10.0.0.13:9000;
}
Когда хорош: запросы примерно одинаковые по стоимости, серверы примерно одинаковой мощности. Где ломается: round-robin не знает, сколько запрос будет выполняться, — он считает «переданную единицу», а не «нагрузку». Если один сервер из трёх отдаёт выгрузку отчёта за пять секунд, а остальные — страницы за пятьдесят миллисекунд, через минуту отчётный сервер задохнётся, а остальные будут простаивать.
7. Least-connections: у кого меньше активных соединений
Более честный метод. Балансер держит счётчик активных соединений для каждого бэкенда и отправляет запрос туда, где счётчик меньше; при равенстве выбор идёт по round-robin. В HAProxy это balance leastconn.
upstream backend_lc {
least_conn;
server 10.0.0.11:9000;
server 10.0.0.12:9000;
server 10.0.0.13:9000;
}
Когда хорош: время ответа различается от запроса к запросу. Метод сам «наказывает» медленный сервер: чем дольше он держит соединения, тем меньше новых запросов получает. Где ломается: при разной стоимости соединения счётчик вводит в заблуждение — скачивание файла засчитывается как короткий запрос. А если все ответы быстрые, метод просто дороже round-robin по процессору.
8. IP-hash и прочие методы
Вычисляется хеш от IP-адреса клиента: один и тот же адрес всегда попадает на один и тот же бэкенд. consistent включает консистентное хеширование — при добавлении или удалении сервера перераспределяется только малая часть клиентов, а не весь пул.
upstream backend_hash {
hash $remote_addr consistent;
server 10.0.0.11:9000;
server 10.0.0.12:9000;
server 10.0.0.13:9000;
}
Когда хорош: нужно привязать клиента к серверу без куки либо важен локальный кеш на бэкенде. Где ломается: адрес клиента — плохая единица. NAT (общий адрес на весь офис или на мобильного оператора) превращает «привязку клиента» в «привязку тысяч людей к одному серверу».
Что ещё бывает: случайный выбор (balance random) прост и при большом числе запросов даёт ровную картину; наименьшая задержка берёт сервер, быстрее всех ответивший в последнем замере (нужна активная проверка живости); привязка по пути полезна для локального кеша. Практический выбор: начинайте с round-robin, переходите на least-connections, когда запросы стали различаться по длительности, а IP-hash берите только осознанно.
Часть IV. Health checks: как балансер узнаёт, что сервер умер
9. Пассивные и активные проверки
Если балансер не проверяет бэкенды, он честно отправит запрос на сервер, который лежит и молчит, — а этого мы и хотели избежать.
Пассивные проверки. Балансер ничего не проверяет заранее, только считает реальные ошибки при обслуживании запросов: не удалось соединиться, сервер вернул таймаут или 5xx — неудача. После N подряд неудач бэкенд исключается на время fail_timeout.
upstream backend_passive {
least_conn;
server 10.0.0.11:9000 max_fails=3 fail_timeout=30s;
server 10.0.0.12:9000 max_fails=3 fail_timeout=30s;
server 10.0.0.13:9000 max_fails=2 fail_timeout=60s backup;
}
Расшифровка построчно: max_fails=3 — порог срабатывания, три неудачи за окно fail_timeout, и сервер объявляется мёртвым; fail_timeout=30s — окно подсчёта неудач и одновременно время изоляции до следующей попытки; backup — резерв, используется только когда все остальные исключены. Плюс пассивных проверок — ничего настраивать на бэкенде не надо. Минус — часть пользователей всегда успевает получить ошибку: три неудачи — это три упавших запроса.
Активные проверки. Балансер сам, по таймеру, ходит на специальный адрес каждого бэкенда: «ты жив?». Раз в interval секунд открывает соединение с http://бэкенд/ready и ждёт ответа; если код не тот, что ожидается, — неудача. После fails неудач подряд сервер исключается, после passes успешных — возвращается. Пользовательские запросы при этом не приносятся в жертву.
10. Что должен возвращать health-check эндпоинт
Главная ошибка — сделать /health, который просто возвращает 200. Такой ответ означает лишь «процесс жив», а процесс может быть жив и бесполезен: воркеры PHP зависли, пул соединений к базе исчерпан, место на диске кончилось. Обычно делают два эндпоинта: /health (liveness, «жив ли») — быстрый, по нему оркестратор перезапускает процесс; /ready (readiness, «готов ли принимать трафик») — проверяет базу, кеш и диск, и именно его должен опрашивать балансер. Правило: пока всё в порядке — HTTP 200, как только критичная проверка провалилась — HTTP 503 (Service Unavailable, «сервис временно недоступен»).
{
"status": "ok",
"checks": {
"database": { "status": "ok", "latency_ms": 3 },
"redis": { "status": "ok", "latency_ms": 1 },
"disk_free": { "status": "ok", "value": "124 GB" },
"queue_depth": { "status": "ok", "value": 42 }
},
"uptime_seconds": 3621,
"version": "1.7.3"
}
На PHP такой эндпоинт готовности выглядит примерно так:
<?php
// health.php — ответ для активных проверок балансировщика
header('Content-Type: application/json; charset=utf-8');
$checks = [];
$ok = true;
try { // 1. База: простой запрос без чтения данных
$pdo->query('SELECT 1')->fetchColumn();
$checks['database'] = ['status' => 'ok'];
} catch (Throwable $e) {
$checks['database'] = ['status' => 'fail'];
$ok = false;
}
$redisOk = $redis->ping(); // 2. Доступен ли кеш
$checks['redis'] = ['status' => $redisOk ? 'ok' : 'fail'];
$ok = $ok && $redisOk;
$limit = 500 * 1024 * 1024; // 3. Хватает ли места на диске
$free = disk_free_space('/var/lib/php/sessions');
$checks['disk_free'] = ['status' => $free > $limit ? 'ok' : 'fail'];
$ok = $ok && $free > $limit;
http_response_code($ok ? 200 : 503);
echo json_encode(['status' => $ok ? 'ok' : 'degraded', 'checks' => $checks],
JSON_UNESCAPED_UNICODE);11. Пороги срабатывания в обе стороны
В Nginx активные проверки описываются внутри upstream (синтаксис различается в открытом и коммерческом сборках; в открытом добавляют модуль nginx_upstream_check_module):
upstream backend_active {
least_conn;
server 10.0.0.11:9000;
server 10.0.0.12:9000;
server 10.0.0.13:9000;
health_check interval=5s fails=3 passes=2 uri=/ready;
}
interval=5s — частота опроса, fails=3 — три неудачных ответа подряд исключают сервер, passes=2 — два удачных возвращают его в пул. Возвращать после одной удачи — плохая идея: процесс мог подняться и ещё не прогреть кеш, поэтому возвращают после нескольких удачных проверок, иногда с slow_start — временем постепенного роста доли трафика. Слишком агрессивные пороги тоже вредны: fails=1 при interval=1s означает, что заминка на долю секунды уберёт здоровый сервер из пула и обрушит его трафик на соседей — так начинается каскадный отказ. И помните: health-check должен быть дешёвым, иначе он сам создаёт нагрузку в число_серверов × (1 / interval) запросов в секунду.
Часть V. Липкие сессии: когда они нужны и когда приносят вред
12. В чём проблема
HTTP по природе не помнит клиента. Чтобы пользователь оставался «залогиненным», сервер ставит куку с идентификатором сессии, а данные сессии хранит у себя. Беда начинается, когда эти данные лежат в памяти конкретного процесса PHP на конкретном сервере: клиент зашёл на бэкенд-1, сессия создалась в его памяти, через секунду тот же запрос ушёл на бэкенд-2, где про сессию никто не знает, — и пользователя выбросило на страницу логина.
Решений ровно два. Первое, правильное — вынести состояние сессий в общее хранилище: Redis или базу данных. Тогда любой бэкенд читает любую сессию, липкость не нужна (альтернатива без внешнего хранилища — JWT-токен, который не требует хранения на сервере, см. Безопасную аутентификацию). Второе — заставить балансер привязывать клиента к одному серверу; это и есть sticky sessions, липкие сессии. Привязка идёт через куку, которую ставит сам балансер:
upstream backend_sticky {
least_conn;
server 10.0.0.11:9000;
server 10.0.0.12:9000;
server 10.0.0.13:9000;
sticky cookie srv_id expires=1h domain=.example.com path=/;
}
Клиент получил куку srv_id=10.0.0.12 — все последующие запросы уходят на второй сервер. В облачных балансерах тот же механизм называется AWSALB (AWS), GSERVERID (Yandex Cloud), route или affinity (Azure).
13. Когда липкость вредна и когда оправдана
Кажется, что липкость — панацея: и логин работает, и переписывать не надо. На практике это источник трёх проблем. Перекос нагрузки: как только клиент приклеился, алгоритмы распределения для него перестают работать — бэкенд-1 получил десять «приклеенных» клиентов с долгими соединениями, бэкенд-2 свободен, но перевести туда старых нельзя. Потеря сессий остаётся: упал бэкенд-1 — его клиенты прилетят на другие серверы, где их сессий нет, и разлогинятся. Мешает обновлению: раскатка релиза по одному серверу (rolling deploy) уносит сессии исключённого сервера, а вновь созданный сервер не получает клиентов, потому что все старые приклеены к прежним адресам.
| Ситуация | Липкость |
|---|---|
| Сессии в памяти PHP, переписать быстро нельзя | нужна как временная мера |
| Долгоживущее WebSocket-соединение плюс запросы к одному обработчику | нужна (соединение не перенести) |
| Сессии в Redis, в базе или JWT | не нужна |
| Кеш на бэкенде (OPcache плюс локальные файлы) | опциональна, лучше ip-hash |
| Тяжёлые выгрузки отчётов | вредна — нужен least-connections |
| Тысячи пользователей за одним корпоративным NAT | вредна — приклеит всех к одному серверу |
Правило простое: липкие сессии — костыль, а не архитектура. Они нужны ровно в одном случае: когда не можете вынести состояние наружу прямо сейчас, а сервис должен работать сегодня. План — липкость как временная мера, состояние в Redis как цель.
Часть VI. Конфигурации: Nginx upstream и HAProxy на практике
14. Nginx как балансировщик седьмого уровня
upstream app_backend {
least_conn; # метод: у кого меньше активных соединений
server 10.0.0.11:9000 max_fails=3 fail_timeout=30s;
server 10.0.0.12:9000 max_fails=3 fail_timeout=30s weight=2;
server 10.0.0.13:9000 max_fails=3 fail_timeout=30s;
server 10.0.0.99:9000 backup; # только когда все остальные исключены
keepalive 32; # держим соединения к бэкендам открытыми
}
server {
listen 443 ssl http2;
server_name shop.example.com;
ssl_certificate /etc/ssl/shop/fullchain.pem;
ssl_certificate_key /etc/ssl/shop/privkey.pem;
location / {
proxy_pass http://app_backend;
# Реальный адрес клиента — иначе все бэкенды видят IP балансера
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;
proxy_http_version 1.1;
proxy_set_header Connection ""; # нужно для переиспользования keepalive
proxy_connect_timeout 2s;
proxy_read_timeout 60s;
# Не показывать ошибку сразу — попробовать следующий бэкенд
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
}
location ~* \.(jpg|jpeg|png|webp|css|js|woff2)$ { # статика — минуя PHP
root /var/www/shop/public;
expires 30d;
}
}Что из-за чего ломается:
Hostбез него бэкенд получает имяapp_backendвместоshop.example.com, приложение строит адреса исходя из него, и пользователя уносит редиректом на внутреннее имя.X-Real-IPиX-Forwarded-For— без них логи, антифрод и лимиты видят IP балансера вместо IP посетителя; на стороне PHP читайтеX-Forwarded-Forаккуратно — ту запись, которую добавили вы, а не всю цепочку от клиента (её подделывают).proxy_next_upstreamговорит: при502,503,504или обрыве соединения не показывать ошибку, а повторить запрос на следующем бэкенде. Опасность — повторное выполнение:GETповторять можно, аPOSTзаказа тоже, и пользователь получит два заказа;non_idempotentвключайте только осознанно.keepaliveэкономит рукопожатие на каждом запросе; при большом QPS (queries per second, запросов в секунду) заметно.
15. HAProxy: минимальный конфиг бэкенда
Файл haproxy.cfg устроен из секций: global (ресурсы процесса), defaults (настройки по умолчанию), frontend (что слушаем снаружи), backend (куда отправляем внутрь).
global
maxconn 20000 # потолок одновременных соединений на процесс
log /dev/log local0
defaults
mode http # L7; для L4 здесь было бы mode tcp
option httplog # подробный лог каждого HTTP-запроса
timeout connect 2s # сколько ждать установки соединения с бэкендом
timeout server 60s # сколько ждать ответ от бэкенда
retries 2 # сколько раз попробовать другой сервер
frontend https_in
bind *:443 ssl crt /etc/haproxy/certs/
default_backend app_backend
backend app_backend
balance leastconn # варианты: roundrobin | leastconn | source | uri
# активная проверка живости: просим /ready и ждём код 200
option httpchk GET /ready HTTP/1.0
http-check expect status 200
# пороги: интервал опроса, 3 неудачи — исключение, 2 успеха — возврат
default-server check inter 5s fall 3 rise 2 slowstart 30s
# привязка по куке, которую ставит сам HAProxy
cookie SRVID insert indirect nocache
server app1 10.0.0.11:8080 cookie a1 check
server app2 10.0.0.12:8080 cookie a2 check weight 2
server app3 10.0.0.13:8080 cookie a3 check
server app4 10.0.0.99:8080 cookie a4 check backup
Ключевые слова: mode http включает L7-режим (в mode tcp HAProxy не смотрит в HTTP и становится быстрым L4-балансером); option httpchk — активная проверка живости по таймеру для каждого сервера с check; http-check expect status 200 — критерий успеха; inter 5s fall 3 rise 2 — интервал опроса и пороги исключения/возврата; slowstart — плавный рост доли трафика для непрогретого процесса; backup — резерв на случай, когда все остальные исключены; cookie ... insert indirect nocache — привязка по собственной куке (indirect — не показывать куку приложению, nocache — не кешировать ответ из-за неё). Есть и listen stats — встроенная панель, где видно, какой бэкенд исключён и почему; доступ к ней держат из внутренней сети.
16. Как проверить, что балансировка работает
- Запросите
/readyна каждом бэкенде и через балансер — ответы должны совпасть. Если на бэкенде 200, а через балансер 503, ошиблись в критерии. - Выключите один бэкенд (
systemctl stop php-fpm) под нагрузкой: правильно — несколько ошибок на переходе и затем ноль; если ошибки длятся, проверки живости нет или пороги медленные. - Верните бэкенд: часто
riseзавышен, и сервер стоит без трафика пять минут. - Посчитайте распределение: тысяча запросов, деление по логам. Для round-robin — примерно равное, для
weight=2— двойная доля; втрое больше на одном сервере — значит, осталась липкость или IP-hash при NAT. - Проверьте под реальной нагрузкой — методика в статье Нагрузочное тестирование.
Часть VII. Типичные ошибки и чек-лист
17. Что чаще всего ломают
- Один балансер перед одним сервером. Формальность, добавляющая задержку и точку отказа: балансировка имеет смысл, только когда бэкендов минимум два.
- Балансировка базы данных тем же способом. MySQL и PostgreSQL нельзя «распределить» round-robin: это не сервисы без состояния (stateless — «не помнящий состояния»). Базу решают репликацией и пулом соединений.
- Health check, который всегда 200, и слишком агрессивные пороги: в первом случае балансер думает, что всё в порядке, пока приложение отдаёт пустые страницы, во втором — заминка в одну секунду выгоняет живой сервер и запускает каскадный отказ.
- Липкие сессии при сессиях в Redis. Состояние уже в общем хранилище, а липкость осталась из прошлого конфига и зря перекашивает нагрузку.
- Отсутствие
X-Forwarded-ForиX-Forwarded-Proto. Приложение видит IP балансира: лимиты считаются с одного адреса, антифрод банит всех сразу, а без заголовка о протоколе HTTPS-клиент попадает в цикл редиректов. - Не выставленные таймауты. Зависший бэкенд держит воркера минуту (столько стоит
proxy_read_timeoutпо умолчанию), и соединения быстро кончаются. - Не проверено поведение при падении. Конфиг красив, а в реальном отказе выясняется, что резерв не настроен, а повторы добивают базу.
18. Чек-лист перед включением
- Бэкендов минимум два, они равноценны либо различие описано весами
weight. - Алгоритм выбран осознанно: round-robin для одинаковых запросов,
least_connдля разнородных,ip-hash— с пониманием проблемы NAT. - Health check активный, смотрит на эндпоинт готовности и проверяет базу, кеш и диск; пороги разумные (5–10 секунд, 3 неудачи, 2 успеха) с медленным стартом после возвращения.
- Липкость либо выключена, либо документирована как временная мера с планом отказа от неё.
X-Real-IP,X-Forwarded-For,X-Forwarded-Protoпередаются; приложение доверяет им через список доверенных прокси.- Таймауты выставлены явно,
proxy_next_upstreamне применяется к запросам, которые нельзя повторять. - Резервный сервер (
backup) есть, и его ёмкости хватает, чтобы принять весь трафик в одиночку. - Метрики с балансира идут в мониторинг (статья Метрики и мониторинг), конфиг лежит в версиях контроля, а поведение проверено выключением сервера под нагрузкой.
- Сам балансер не единственная точка отказа: облачный управляемый, пара своих с плавающим адресом (keepalived, VRRP) — либо осознанное согласие с риском.
Итог. Что даёт балансировка и чего она не даёт
Балансировщик закрывает две задачи, и полезно держать их раздельно. Распределение нагрузки позволяет расти горизонтально: вместо покупки всё более мощной машины вы добавляете одинаковые, и обходится это дешевле. Устранение единой точки отказа позволяет сайту оставаться живым при поломке одного сервера. Обе задачи решаются только при двух и больше бэкендах; до этого балансер — лишний узел.
Дальше всё сводится к трём вопросам. Где балансируете: L4 — быстро, по адресам и портам, не знает про HTTP; L7 — читает запрос, маршрутизирует по пути и заголовкам, и именно это нужно веб-приложению. Как выбираете сервер: round-robin для ровных запросов, least-connections для разнородных, IP-hash только когда понимаете цену. Как узнаёте о смерти сервера: пассивно — по ошибкам пользователей, активно — по дешёвым проверкам эндпоинта готовности с разумными порогами. Липкие сессии — не архитектура, а временный мостик для приложения, которое хранит состояние в памяти; настоящий путь — вынести состояние в общее хранилище. Чего балансировка не даёт: она не делает быстрым медленный код, не заменяет кеширование и не спасает от упавшей базы.
Материал написан простым языком. Примеры конфигураций рассчитаны на размещение на VPS (WordPress и собственные PHP-приложения) и используют типичный набор: Nginx, HAProxy, PHP-FPM, Redis. Синтаксис конкретных версий стоит сверять с официальной документацией: директивы активных проверок живости в Nginx различаются в открытом и коммерческом сборках, а в HAProxy часть опций переезжала между секциями в линейке 2.x.
👤 Зарегистрируйтесь, чтобы добавить материал в закладки.
