У Prowid Photos теперь три экземпляра API, но адрес у сайта один — photos.prowid.com. Какому из трёх серверов уйдёт запрос из браузера? Это решает балансировщик нагрузки (load balancer) — «администратор на входе в ресторан»: он встречает гостей, провожает к свободному столику, а если стол сломан, больше не сажает туда никого. В этом уроке разберём, зачем он нужен, как выбирает сервер (алгоритмы), как узнаёт о поломках (health checks), чем отличаются L4 и L7, как это настраивается в Nginx — и почему сам балансировщик может стать слабым звеном.
Зачем нужен балансировщик
Балансировщик стоит между клиентами и серверами и даёт сразу несколько вещей:
- Единая точка входа. Клиенты знают один адрес и не знают, сколько серверов за ним.
- Распределение нагрузки. Запросы расходятся по экземплярам, поэтому ни один не перегружается раньше других.
- Отказоустойчивость. Упавший сервер выводят из ротации, и пользователи его не замечают.
- Обновления без остановки. Сервер убирают из ротации, ждут, пока он закончит текущие запросы, обновляют и возвращают.
- Бонусы. Часто балансировщик заодно завершает TLS (HTTPS), сжимает ответы и ограничивает частоту запросов.
Роль балансировщика может играть Nginx или HAProxy на своём сервере либо управляемый сервис облака. Основы Nginx как reverse proxy вы уже знаете из урока «Nginx как reverse proxy для контейнеров»: балансировщик — это reverse proxy, у которого не один сервер, а группа.
Алгоритмы балансировки
Round robin — «по кругу»: первый запрос на первый сервер, второй на второй, и так далее. Просто и честно, если серверы одинаковы, а запросы похожи:
const servers = ['s1', 's2', 's3'];
let next = 0;
function pickRoundRobin() {
const server = servers[next];
next = (next + 1) % servers.length;
return server;
}
for (let i = 1; i <= 4; i++) {
console.log(`request ${i} -> ${pickRoundRobin()}`);
}
// request 1 -> s1, request 2 -> s2, request 3 -> s3, request 4 -> s1Но запросы не равны: лёгкий GET занимает 5 мс, загрузка фото — 3 секунды. По кругу может получиться так, что один сервер завален тяжёлыми запросами, а другой простаивает. Weighted round robin учитывает разную мощность серверов: сервер с весом 2 получает вдвое больше запросов. Least connections отправляет запрос туда, где сейчас меньше всего открытых соединений, — идеально для загрузок файлов и WebSocket:
const connections = { s1: 3, s2: 1, s3: 2 };
function pickLeastConnections() {
let best = 's1';
for (const name of Object.keys(connections)) {
if (connections[name] < connections[best]) best = name;
}
return best;
}
console.log(pickLeastConnections()); // s2: it has the fewest open connections