← Назад к вопросам
Как работают балансировщики в архитектуре?
2.0 Middle🔥 202 комментариев
#JavaScript Core
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Load Balancers в веб-архитектуре - полное объяснение
Балансировщик нагрузки (Load Balancer) - это критический компонент масштабируемых приложений. Он распределяет входящие запросы между несколькими серверами для обеспечения высокой доступности и производительности.
1. Основная концепция
// АРХИТЕКТУРА БЕЗ балансировщика (проблема):
// Клиент -> Один сервер -> База данных
// Проблемы:
// - Сервер перегружается при большом трафике
// - Если сервер упадет - приложение недоступно
// - Нет масштабируемости
// АРХИТЕКТУРА С балансировщиком (решение):
// Клиент -> Load Balancer -> Сервер 1
// -> Сервер 2
// -> Сервер 3
// -> База данных
// Балансировщик:
// - Принимает ВСЕ запросы от клиентов
// - Распределяет запросы между серверами
// - Мониторит здоровье серверов
// - Если сервер упал - перенаправляет на живой
2. Типы балансировщиков и уровни (OSI)
// УРОВЕНЬ 4 (Transport) - L4 Load Balancer
// Работает на уровне TCP/UDP
// Распределяет по IP адресам и портам
// Примеры: HAProxy, Nginx (Stream)
// Преимущества L4:
// - Очень быстро (минимальная обработка)
// - Хороший для TCP соединений
// - Использует меньше ресурсов
// Недостатки:
// - Не понимает содержимое запросов
// - Не может маршрутизировать по URL или host
// УРОВЕНЬ 7 (Application) - L7 Load Balancer
// Работает на уровне HTTP/HTTPS
// Распределяет по URL, Host headers, данным запроса
// Примеры: Nginx, Apache, AWS ALB
// Преимущества L7:
// - Может маршрутизировать по содержимому запроса
// - Может балансировать по URL path
// - Может обрабатывать sticky sessions
// Недостатки:
// - Требует больше ресурсов
// - Может быть узким местом при очень большом трафике
// Пример L7 балансировки:
// GET /api/users -> Сервер 1 (API сервер)
// GET /static/* -> Сервер 2 (файловый сервер)
// GET /upload -> Сервер 3 (обработка загрузок)
3. Алгоритмы распределения нагрузки
// 1. Round Robin - циклическое распределение
// Запрос 1 -> Сервер 1
// Запрос 2 -> Сервер 2
// Запрос 3 -> Сервер 3
// Запрос 4 -> Сервер 1 (цикл повторяется)
// Пример на Node.js:
const servers = ['server1', 'server2', 'server3'];
let currentIndex = 0;
function getNextServer() {
const server = servers[currentIndex];
currentIndex = (currentIndex + 1) % servers.length;
return server;
}
// 2. Weighted Round Robin - взвешенное распределение
// Если одни серверы мощнее других
const weightedServers = [
{ name: 'server1', weight: 3 },
{ name: 'server2', weight: 1 },
{ name: 'server3', weight: 2 }
];
// Распределение: 3 запроса на server1, 1 на server2, 2 на server3
// 3. Least Connections - наименьшее количество соединений
// Запрос идет на сервер с наименьшим числом активных соединений
// Хорошо для долгих соединений (WebSocket, gRPC)
const serverLoad = {
'server1': 15, // 15 активных соединений
'server2': 5, // 5 активных соединений
'server3': 10 // 10 активных соединений
};
function getLeastLoadedServer() {
return Object.entries(serverLoad).reduce((min, [server, load]) =>
load < min.load ? { server, load } : min
).server;
}
// Результат: server2 (наименьшая нагрузка)
// 4. IP Hash - маршрутизация по IP клиента
// Один IP всегда идет на один сервер
// Полезно для sticky sessions (если у сервера локальное состояние)
function getServerByIp(clientIp) {
const hash = clientIp.split('.').reduce((sum, num) => sum + parseInt(num), 0);
const serverIndex = hash % servers.length;
return servers[serverIndex];
}
// 5. Least Response Time - по времени ответа
// Запрос на сервер с наименьшим временем ответа
// Используется когда серверы отличаются по производительности
4. Health Checks - проверка здоровья серверов
// Load Balancer периодически проверяет, живы ли серверы
// HTTP Health Check
// GET /health HTTP/1.1
// Ожидает ответ 200 OK
// Пример на Express:
app.get('/health', (req, res) => {
// Проверяем здоровье приложения
if (isApplicationHealthy()) {
res.status(200).json({ status: 'healthy' });
} else {
res.status(503).json({ status: 'unhealthy' });
}
});
// TCP Health Check
// Просто проверяет, что сервер принимает соединения на порту
// Health Check параметры:
// - Interval: 10 секунд (как часто проверять)
// - Timeout: 5 секунд (ждем ответа макс 5 сек)
// - Healthy threshold: 2 проверки подряд = сервер живой
// - Unhealthy threshold: 3 проверки подряд = сервер мертв
// Когда сервер помечается как unhealthy:
// - Load Balancer прекращает отправлять запросы
// - Запросы распределяются на живые серверы
// - Когда сервер восстановится - снова включится
5. Session Persistence (Sticky Sessions)
// ПРОБЛЕМА: Balancer распределяет, но может послать запросы одного
// пользователя на разные серверы. Если у них разные сессии - проблема!
// Запрос 1 (auth) -> Сервер 1 (создает сессию в памяти)
// Запрос 2 (fetch data) -> Сервер 2 (нет сессии! пользователь не авторизован)
// РЕШЕНИЕ 1: Sticky Session by Cookie
// Load Balancer добавляет специальный cookie
// Все запросы с этим cookie идут на тот же сервер
// nginx пример:
upstream backend {
server server1.example.com weight=1 max_fails=3 fail_timeout=30s;
server server2.example.com weight=1 max_fails=3 fail_timeout=30s;
}
server {
location / {
proxy_pass http://backend;
proxy_cookie_path / "/";
# Sticky session by cookie
}
}
// РЕШЕНИЕ 2: Shared Session Storage
// Сессии хранятся не в памяти сервера, а в Redis/Memcached
// Все серверы обращаются к общему хранилищу
// Redis пример (Node.js):
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
const redisClient = createClient();
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'secret-key',
resave: false,
saveUninitialized: false
}));
// Теперь сессия доступна на ВСЕХ серверах:
// Запрос 1 -> Сервер 1 -> Сохраняет в Redis
// Запрос 2 -> Сервер 2 -> Читает из Redis (сессия существует!)
// РЕШЕНИЕ 3: JWT токены
// Сессия хранится в самом токене (в памяти браузера)
// Не нужно хранить состояние на сервере
// Frontend:
const token = await login(email, password);
localStorage.setItem('token', token);
// Каждый запрос:
fetch('/api/users', {
headers: { 'Authorization': `Bearer ${token}` }
});
// Сервер:
app.get('/api/users', authenticate, (req, res) => {
// JWT уже декодирован, сессия в токене
// Работает на ЛЮБОМ сервере
});
6. Типичная архитектура
// СОВРЕМЕННАЯ АРХИТЕКТУРА:
// Internet (клиенты)
// ↓
// [External Load Balancer] - AWS ELB, CloudFlare, Akamai
// (распределяет по географии, DDoS protection, SSL termination)
// ↓
// [Application Load Balancer] - Nginx, HAProxy (L7)
// (распределяет HTTP запросы по приложениям)
// ↙ ↓ ↘
// Сервер1 Сервер2 Сервер3 (Node.js, Python, Java)
// (Express) (Express) (Express)
// ↓ ↓ ↓
// [Redis] - Shared Session/Cache Storage
// ↓
// [Database] - Primary (write)
// ↓
// [Database Replica] - Read-only copies
// Почему так:
// 1. External LB - защита, масштабируемость на глобальном уровне
// 2. Internal LB - эффективное распределение внутри дата-центра
// 3. Multiple servers - высокая доступность
// 4. Shared session - sticky session не требуется
// 5. Read replicas - масштабируемое чтение из БД
7. Как фронтенд видит балансировщик
// Для браузера балансировщик прозрачен
// Клиент отправляет запрос на один IP (LB), но не знает что серверов много
// Пример:
// Клиент -> https://api.example.com/ <- это DNS резолвится на LB IP
// LB распределяет между серверами
// Клиент не видит этого
// На фронтенде нужно учитывать:
// 1. CORS headers (LB может требовать правильные headers)
// 2. Session/Auth (если используются sticky sessions)
// 3. Timeout (LB может прервать долгие соединения)
// Проверка здоровья на фронтенде:
async function checkHealth() {
try {
const response = await fetch('/api/health', { timeout: 5000 });
return response.ok;
} catch (error) {
console.log('Server is down, LB should redirect to another');
}
}
// Retry logic:
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fetch(url);
} catch (error) {
if (i === maxRetries - 1) throw error;
// LB может быть в процессе переключения, retry помогает
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
Балансировщики нагрузки - это essential часть любого серьезного веб-приложения. Они обеспечивают масштабируемость, высокую доступность и отказоустойчивость. Понимание их работы критично для разработчика, даже если ты не ставишь их сам - ты должен понимать ограничения и особенности, которые они вносят.