Что такое горизонтальное масштабирование?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Горизонтальное масштабирование (Horizontal Scaling)
Горизонтальное масштабирование — это добавление новых серверов/узлов в систему для распределения нагрузки и увеличения пропускной способности. Это противоположность вертикальному масштабированию (добавление мощности одному серверу).
Горизонтальное vs Вертикальное масштабирование
Вертикальное масштабирование (Vertical Scaling)
Сервер 1 → Сервер 1 (мощнее)
4 CPU, 8GB RAM → 16 CPU, 64GB RAM
Плюсы:
- Просто реализовать
- Приложение не нужно менять
- Нет проблем с синхронизацией
Минусы:
- Есть потолок (нельзя бесконечно добавлять мощность)
- Дорого (экспоненциально)
- Простой при апгрейде
- Single point of failure
Горизонтальное масштабирование (Horizontal Scaling)
┌──────────┐
│ Load │
│ Balancer │
└────┬─────┘
│
┌────┴────┬──────┐
│ │ │
Server 1 Server 2 Server 3
4CPU, 8GB 4CPU, 8GB 4CPU, 8GB
Плюсы:
- Масштабируется практически без ограничений
- Дешевле (много дешевых серверов vs один мощный)
- Не требует простоя (hot scaling)
- Отказоустойчивость (если один падает, остальные работают)
Минусы:
- Сложнее реализовать
- Нужен load balancer
- Требует распределение состояния
- Проблемы с синхронизацией данных
- Усложняет отладку
Архитектура горизонтального масштабирования
┌─────────────────────────────────────┐
│ Internet / Clients │
└────────────────┬────────────────────┘
│
┌───────▼────────┐
│ Load Balancer │ (Nginx, HAProxy)
├────────────────┤
│ - Round Robin │
│ - Sticky │
│ - Least Conn │
└────────┬───────┘
│
┌───────────┼───────────┐
│ │ │
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Server 1│ │ Server 2│ │ Server 3│
│ App │ │ App │ │ App │
└──┬──────┘ └──┬──────┘ └──┬──────┘
│ │ │
└───────────┼───────────┘
│
┌───────▼────────┐
│ Shared DB │ (PostgreSQL, MySQL)
└────────────────┘
└───────▼────────┐
│ Cache (Redis) │
└────────────────┘
Методы распределения нагрузки (Load Balancing)
1 Round Robin
Запрос 1 → Server 1
Запрос 2 → Server 2
Запрос 3 → Server 3
Запрос 4 → Server 1
Запрос 5 → Server 2
Когда использовать: когда все серверы имеют одинаковую мощность.
2 Least Connections
Server 1: 5 активных соединений
Server 2: 2 активных соединения ← новый запрос идет сюда
Server 3: 8 активных соединений
Когда использовать: когда запросы имеют разную длительность.
3 IP Hash
ip_hash(client_ip) % num_servers → сервер для этого клиента
Плюсы: клиент всегда попадает на один сервер (sticky sessions) Минусы: несбалансированное распределение, если клиентов неравномерно
4 Weighted Distribution
Server 1 (вес 3): 60% трафика
Server 2 (вес 1): 20% трафика
Server 3 (вес 1): 20% трафика
Когда использовать: когда серверы имеют разную мощность.
Пример с Nginx
upstream app_servers {
# Round Robin (по умолчанию)
server 192.168.1.10:8000;
server 192.168.1.11:8000;
server 192.168.1.12:8000;
# Или Least Connections
# least_conn;
# Или Weighted
# server 192.168.1.10:8000 weight=3;
# server 192.168.1.11:8000 weight=1;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://app_servers;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Health check
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 10s;
}
}
Проблемы и решения
Проблема 1: Session State
# Без синхронизации — НЕПРАВИЛЬНО
# Запрос 1 → Server 1: session['user_id'] = 123
# Запрос 2 → Server 2: session['user_id'] не определен!
Решение 1: Sticky Sessions (Session Affinity)
Любой запрос от клиента всегда идет на тот же сервер.
**Решение 2: Распределённая сессия (Redis)
# Использовать Redis для хранения сессий
# Все серверы читают/пишут в Redis
import redis
from flask import Flask
from flask_session import Session
app = Flask(__name__)
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.from_url('redis://localhost:6379')
Session(app)
@app.route('/login')
def login():
session['user_id'] = 123
return 'OK'
@app.route('/profile')
def profile():
# Работает на любом сервере
return f"User {session['user_id']}"
Проблема 2: Синхронизация данных
# Кэш разный на каждом сервере
Server 1 cache: user.name = "John"
Server 2 cache: user.name = "Jane"
Решение: Центральный кэш (Redis, Memcached)
import redis
cache = redis.Redis(host='redis-server', port=6379)
def get_user(user_id):
# Кэш на центральном сервере
cached = cache.get(f'user:{user_id}')
if cached:
return json.loads(cached)
user = db.query(User).filter(User.id == user_id).one()
cache.setex(f'user:{user_id}', 3600, json.dumps(user.to_dict()))
return user
Проблема 3: Uploads и files
# Неправильно: файл только на Server 1
Server 1: /uploads/user_123_photo.jpg
Server 2: файл не существует! 404
Решение: Объектное хранилище (S3, GCS, Minio)
import boto3
s3 = boto3.client('s3')
# Загружаем на S3 (доступно со всех серверов)
s3.put_object(
Bucket='my-bucket',
Key=f'uploads/user_{user_id}_photo.jpg',
Body=file_content
)
# Получаем с S3 (работает на любом сервере)
url = s3.generate_presigned_url(
'get_object',
Params={'Bucket': 'my-bucket', 'Key': f'uploads/user_{user_id}_photo.jpg'},
ExpiresIn=3600
)
Базы данных и горизонтальное масштабирование
Проблема: БД становится узким местом
Server 1 ──┐
Server 2 ──┼─→ Single Database ← узкое место
Server 3 ──┘
Решение 1: Database Replication (Master-Slave)
Master (write) Slave 1 (read)
├─────────────────────────────┤
│ │
Server 1 (write) Server 2 (read)
Server 1 (read) Server 3 (read)
Решение 2: Sharding (горизонтальное разделение БД)
Shard 1: users 1-1000000
Shard 2: users 1000001-2000000
Shard 3: users 2000001-3000000
get_shard(user_id) = hash(user_id) % num_shards
Пример полной архитектуры
# app.py
from flask import Flask, request, session
import redis
import boto3
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
# Распределённая сессия
redis_session = redis.Redis(host='redis', port=6379, db=0)
# Кэш
redis_cache = redis.Redis(host='redis', port=6379, db=1)
# S3 для файлов
s3 = boto3.client('s3', endpoint_url='http://minio:9000')
# БД (master для write, slaves для read)
from sqlalchemy import create_engine
db_write = create_engine('postgresql://user:pass@db-master/app')
db_read = create_engine('postgresql://user:pass@db-slave-1/app')
@app.route('/profile')
def profile():
user_id = session.get('user_id')
# Пытаемся из кэша
cached = redis_cache.get(f'user:{user_id}')
if cached:
return cached.decode()
# Читаем с read replica
user = db_read.execute(f'SELECT * FROM users WHERE id = {user_id}').first()
# Кэшируем результат
redis_cache.setex(f'user:{user_id}', 3600, str(user))
return str(user)
@app.route('/upload', methods=['POST'])
def upload():
file = request.files['file']
user_id = session.get('user_id')
# Загружаем на S3
s3.put_object(
Bucket='app-bucket',
Key=f'user_{user_id}/{file.filename}',
Body=file.read()
)
# Инвалидируем кэш
redis_cache.delete(f'user:{user_id}')
return 'OK'
Когда использовать горизонтальное масштабирование
✅ Используй горизонтальное масштабирование для:
- Stateless приложений (HTTP API, микросервисы)
- Высоконагруженных систем
- Когда нужна отказоустойчивость
- Web приложений
- Микросервисной архитектуры
❌ Избегай горизонтального масштабирования:
- Legacy приложений со статом
- Когда вертикальное масштабирование дешевле
- Высокочастотного трейдинга (latency критичен)
Итог
Горизонтальное масштабирование — это стандартный подход в современной архитектуре. Оно позволяет системе расти без ограничений, но требует правильной архитектуры, удаления state из приложения и использования распределённых сервисов (Redis, S3, message queues). При правильной реализации, это мощный инструмент для создания масштабируемых систем.