← Назад к вопросам

Как работают балансировщики в архитектуре?

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 часть любого серьезного веб-приложения. Они обеспечивают масштабируемость, высокую доступность и отказоустойчивость. Понимание их работы критично для разработчика, даже если ты не ставишь их сам - ты должен понимать ограничения и особенности, которые они вносят.