Как работает балансировщик нагрузки?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает балансировщик нагрузки (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) — устаревший
Преимущества балансировщика нагрузки
- Масштабируемость — можно добавлять серверы горизонтально
- Высокая доступность — если один сервер упадёт, другие работают
- Производительность — нагрузка распределяется равномерно
- Надёжность — автоматическое обнаружение мёртвых серверов
Вывод
Балансировщик нагрузки — это неотъемлемая часть высоконагруженных систем. Выбор алгоритма зависит от типа приложения: Round Robin для стандартных веб-сервисов, Least Connections для долгоживущих соединений, IP Hash для сессионных приложений.