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

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

2.0 Middle🔥 201 комментариев
#DevOps и инфраструктура#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает балансировщик нагрузки (Load Balancer)

Балансировщик нагрузки — это специальный сервер или устройство, которое распределяет входящие запросы между несколькими серверами. Это критично для масштабирования и обеспечения высокой доступности приложений.

Основной принцип

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

Клиент 1 ──┐
Клиент 2 ──┤
Клиент 3 ──┼──→ Load Balancer ──→ Сервер 1
Клиент 4 ──┤                  ──→ Сервер 2
Клиент 5 ──┘                  ──→ Сервер 3

Алгоритмы распределения нагрузки

1. Round Robin (Циклический обход)

Запросы распределяются по серверам по очереди: первый запрос на сервер 1, второй на сервер 2, третий на сервер 3, потом снова на сервер 1.

class LoadBalancer:
    def __init__(self, servers: list):
        self.servers = servers
        self.current_index = 0
    
    def get_next_server(self):
        """Возвращает следующий сервер в круговом порядке"""
        server = self.servers[self.current_index]
        self.current_index = (self.current_index + 1) % len(self.servers)
        return server

# Использование
balancer = LoadBalancer(["192.168.1.1:8000", "192.168.1.2:8000", "192.168.1.3:8000"])
print(balancer.get_next_server())  # 192.168.1.1:8000
print(balancer.get_next_server())  # 192.168.1.2:8000
print(balancer.get_next_server())  # 192.168.1.3:8000
print(balancer.get_next_server())  # 192.168.1.1:8000

2. Weighted Round Robin (Взвешенный циклический обход)

Серверы имеют разный «вес» — мощный сервер получает больше запросов.

class WeightedLoadBalancer:
    def __init__(self, servers_with_weights: dict):
        # {"server1:8000": 3, "server2:8000": 1}  — server1 получает 3x больше запросов
        self.servers = []
        for server, weight in servers_with_weights.items():
            self.servers.extend([server] * weight)
        
        self.current_index = 0
    
    def get_next_server(self):
        server = self.servers[self.current_index]
        self.current_index = (self.current_index + 1) % len(self.servers)
        return server

# Использование
balancer = WeightedLoadBalancer({
    "server1:8000": 3,  # Мощный сервер
    "server2:8000": 1   # Слабый сервер
})

for _ in range(8):
    print(balancer.get_next_server())
# Вывод: server1, server2, server1, server1, server1, server2, server1, server1

3. Least Connections (Наименьшее количество соединений)

Запрос отправляется на сервер с наименьшим количеством активных соединений.

class LeastConnectionsBalancer:
    def __init__(self, servers: list):
        self.servers = {server: 0 for server in servers}  # Счётчик соединений
    
    def get_next_server(self):
        """Возвращает сервер с наименьшим числом соединений"""
        return min(self.servers, key=self.servers.get)
    
    def add_connection(self, server: str):
        """Увеличиваем счётчик при новом соединении"""
        self.servers[server] += 1
    
    def remove_connection(self, server: str):
        """Уменьшаем счётчик при завершении соединения"""
        self.servers[server] = max(0, self.servers[server] - 1)

# Использование
balancer = LeastConnectionsBalancer(["server1:8000", "server2:8000", "server3:8000"])

# Имитируем соединения
balancer.add_connection("server1:8000")
balancer.add_connection("server1:8000")  # server1 имеет 2 соединения
print(balancer.get_next_server())  # server2 (0 соединений)

4. IP Hash (Хеширование по IP клиента)

Запросы от одного IP клиента всегда идут на один и тот же сервер. Полезно для сессий.

import hashlib

class IPHashBalancer:
    def __init__(self, servers: list):
        self.servers = servers
    
    def get_server_for_ip(self, client_ip: str) -> str:
        """Определяет сервер на основе хеша IP"""
        hash_value = int(hashlib.md5(client_ip.encode()).hexdigest(), 16)
        return self.servers[hash_value % len(self.servers)]

# Использование
balancer = IPHashBalancer(["server1:8000", "server2:8000", "server3:8000"])
print(balancer.get_server_for_ip("192.168.1.100"))  # Всегда вернёт один и тот же сервер
print(balancer.get_server_for_ip("192.168.1.100"))  # Тот же сервер
print(balancer.get_server_for_ip("192.168.1.101"))  # Другой сервер

Health Check (Проверка здоровья серверов)

Балансировщик периодически проверяет, живы ли серверы, и исключает мёртвые из пула.

import asyncio
import aiohttp
from datetime import datetime
from enum import Enum

class ServerStatus(Enum):
    HEALTHY = "healthy"
    UNHEALTHY = "unhealthy"

class HealthCheckBalancer:
    def __init__(self, servers: list, health_check_interval: int = 5):
        self.servers = {server: ServerStatus.HEALTHY for server in servers}
        self.health_check_interval = health_check_interval
        self.current_index = 0
    
    async def health_check(self, server: str) -> bool:
        """Проверяет, здоров ли сервер"""
        try:
            async with aiohttp.ClientSession() as session:
                url = f"http://{server}/health"
                async with session.get(url, timeout=aiohttp.ClientTimeout(total=3)) as resp:
                    return resp.status == 200
        except Exception:
            return False
    
    async def run_health_checks(self):
        """Периодически проверяет серверы"""
        while True:
            for server in self.servers:
                is_healthy = await self.health_check(server)
                self.servers[server] = ServerStatus.HEALTHY if is_healthy else ServerStatus.UNHEALTHY
            
            await asyncio.sleep(self.health_check_interval)
    
    def get_next_healthy_server(self):
        """Возвращает следующий здоровый сервер"""
        healthy_servers = [
            s for s, status in self.servers.items() 
            if status == ServerStatus.HEALTHY
        ]
        
        if not healthy_servers:
            raise Exception("No healthy servers available")
        
        server = healthy_servers[self.current_index % len(healthy_servers)]
        self.current_index += 1
        return server

Sticky Sessions (Липкие сессии)

Одного пользователя всегда направляют на один сервер, чтобы не потерять сессию.

class StickySessionBalancer:
    def __init__(self, servers: list):
        self.servers = servers
        self.sessions = {}  # {session_id: server}
    
    def get_server_for_session(self, session_id: str) -> str:
        """Возвращает сервер для сессии или создаёт новый маппинг"""
        if session_id not in self.sessions:
            # Выбираем сервер первый раз (используем простой Round Robin)
            self.sessions[session_id] = self.servers[len(self.sessions) % len(self.servers)]
        
        return self.sessions[session_id]
    
    def remove_session(self, session_id: str):
        """Удаляем сессию при logout"""
        self.sessions.pop(session_id, None)

# Использование
balancer = StickySessionBalancer(["server1:8000", "server2:8000", "server3:8000"])
print(balancer.get_server_for_session("user123"))  # server1
print(balancer.get_server_for_session("user123"))  # server1 (тот же)
print(balancer.get_server_for_session("user456"))  # server2 (новый пользователь)

Реальные примеры балансировщиков

Nginx (Layer 7 - Application)

upstream backend {
    server 192.168.1.1:8000 weight=5;  # Взвешенный Round Robin
    server 192.168.1.2:8000 weight=3;
    server 192.168.1.3:8000 weight=2;
}

server {
    listen 80;
    server_name api.example.com;
    
    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
    }
}

HAProxy (Layer 4/7 - Transport/Application)

global
    maxconn 4096

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend web
    bind *:80
    default_backend servers

backend servers
    balance roundrobin
    server srv1 192.168.1.1:8000 check
    server srv2 192.168.1.2:8000 check
    server srv3 192.168.1.3:8000 check

AWS Load Balancer (облачное решение)

  • Application Load Balancer (ALB) — level 7
  • Network Load Balancer (NLB) — level 4
  • Classic Load Balancer (CLB) — устаревший

Преимущества балансировщика нагрузки

  1. Масштабируемость — можно добавлять серверы горизонтально
  2. Высокая доступность — если один сервер упадёт, другие работают
  3. Производительность — нагрузка распределяется равномерно
  4. Надёжность — автоматическое обнаружение мёртвых серверов

Вывод

Балансировщик нагрузки — это неотъемлемая часть высоконагруженных систем. Выбор алгоритма зависит от типа приложения: Round Robin для стандартных веб-сервисов, Least Connections для долгоживущих соединений, IP Hash для сессионных приложений.

Как работает балансировщик нагрузки? | PrepBro