Как масштабировать высоконагруженную систему?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Масштабирование высоконагруженной системы
Масштабирование — это расширение пропускной способности системы для обработки растущей нагрузки. Есть два подхода: вертикальное (нужен мощнее сервер) и горизонтальное (больше серверов).
Горизонтальное масштабирование (Horizontal Scaling)
Load Balancing — распределение трафика между серверами
Клиенты
|
v
+--------+
| LB | (Load Balancer - nginx, HAProxy)
+--------+
|
+--- API Server 1
+--- API Server 2
+--- API Server 3
+--- API Server N
# nginx config
upstream backend {
server 192.168.1.1:8000;
server 192.168.1.2:8000;
server 192.168.1.3:8000;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Round-robin балансировка:
- Запрос 1 → Сервер A
- Запрос 2 → Сервер B
- Запрос 3 → Сервер C
- Запрос 4 → Сервер A (циклически)
Weighted балансировка — разные серверы разной мощности:
upstream backend {
server 192.168.1.1:8000 weight=5; # 50% трафика
server 192.168.1.2:8000 weight=3; # 30% трафика
server 192.168.1.3:8000 weight=2; # 20% трафика
}
Кеширование (Caching)
In-Memory Cache — Redis для горячих данных
import redis
from datetime import timedelta
redis_client = redis.Redis(host='localhost', port=6379, db=0)
def get_user(user_id: int):
# Проверяем кеш
cached = redis_client.get(f"user:{user_id}")
if cached:
return json.loads(cached)
# Запрашиваем БД
user = db.query(User).filter(User.id == user_id).first()
# Сохраняем в кеш на 1 час
redis_client.setex(
f"user:{user_id}",
timedelta(hours=1),
json.dumps(user)
)
return user
# Инвалидация кеша при обновлении
def update_user(user_id: int, data: dict):
user = db.query(User).filter(User.id == user_id).update(data)
db.commit()
redis_client.delete(f"user:{user_id}") # Очистить кеш
return user
HTTP Caching — браузерный кеш, CDN
from fastapi import Response
from datetime import datetime, timedelta
@app.get("/users/{user_id}")
async def get_user(user_id: int, response: Response):
user = await get_user_from_db(user_id)
# Кешировать 1 час в браузере
response.headers["Cache-Control"] = "max-age=3600, public"
response.headers["ETag"] = f'"{hash(user)}"'
return user
CDN — Content Delivery Network, геораспределённое кеширование
Клиент в Москве
|
v
+----------+
| CDN Node | (в Москве) — кеширует статику
+----------+
|
v
+----------+
| Origin | (основной сервер в другой стране)
+----------+
Асинхронность и очереди (Message Queues)
Offload тяжелые операции в фоновые задачи
# Основной API (быстро)
from celery import Celery
app = Celery('myapp', broker='redis://localhost:6379')
@app.route("/send-email", methods=["POST"])
async def send_email(data: EmailData):
# Не ждём результат, кладём в очередь
send_email_task.delay(data.email, data.subject, data.body)
return {"status": "email queued"}
# Фоновая задача
@app.task
def send_email_task(email: str, subject: str, body: str):
# Это выполнится в отдельном рабочем процессе
smtp.send(email, subject, body)
Publish-Subscribe — многие потребители
import asyncio
from aio_pika import connect_robust, Message
async def publish_event(event_name: str, data: dict):
connection = await connect_robust("amqp://guest:guest@localhost/")
channel = await connection.channel()
exchange = await channel.declare_exchange(event_name, 'topic', durable=True)
await exchange.publish(
Message(body=json.dumps(data).encode()),
routing_key='notification.user.created'
)
# Потребители слушают событие
async def on_user_created(event_data):
await send_welcome_email(event_data['email'])
Масштабирование базы данных
Read Replicas — копии БД для чтения
Пишем → Primary БД
Читаем → Replica 1, Replica 2, Replica 3
from sqlalchemy import create_engine
# Для записи
write_engine = create_engine('postgresql://user:pass@primary:5432/db')
# Для чтения (round-robin между репликами)
read_engines = [
create_engine('postgresql://user:pass@replica1:5432/db'),
create_engine('postgresql://user:pass@replica2:5432/db'),
]
def get_read_engine():
return random.choice(read_engines)
# При запросе
user = get_read_engine().query(User).filter(...).first()
Sharding — разделение данных по ключу
user_id = 12345
shard_num = user_id % 4 # Распределить на 4 шарда
db = db_shards[shard_num] # Выбрать нужную БД
user = db.query(User).filter(User.id == user_id).first()
Асинхронные фреймворки
# FastAPI + async (быстрее обрабатывает много одновременных запросов)
from fastapi import FastAPI
import asyncio
app = FastAPI()
@app.get("/users/{user_id}")
async def get_user(user_id: int):
# Ждём асинхронно, не блокируя поток
user = await db.fetch_user(user_id)
posts = await db.fetch_user_posts(user_id)
return {"user": user, "posts": posts}
# Uvicorn с несколькими рабочими процессами
# uvicorn main:app --workers 4 --host 0.0.0.0
Мониторинг и профилирование
# Выявляем узкие места
import cProfile
import pstats
from functools import wraps
import time
def profile_func(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
@profile_func
def heavy_operation():
time.sleep(1)
# Метрики для мониторинга
from prometheus_client import Counter, Histogram
request_count = Counter('http_requests_total', 'Total requests')
request_duration = Histogram('http_request_duration_seconds', 'Request duration')
@app.get("/api/data")
@request_duration.time()
async def get_data():
request_count.inc()
return {"data": "value"}
Архитектура примера
[Клиенты]
|
v
[CloudFlare CDN] (статика, DDoS защита)
|
v
[nginx LB] (10k соединений / сервер)
|
+--- [Python API 1-100] (async/await, uvicorn)
+--- [Python API 2-100] (async/await, uvicorn)
+--- [Python API N-100] (async/await, uvicorn)
|
+--- [Redis Cluster] (кеш, сессии, очереди)
+--- [PostgreSQL Primary] (основные данные)
+--- [PostgreSQL Replicas x3] (для чтения)
+--- [Elasticsearch] (полнотекстовый поиск)
+--- [RabbitMQ/Kafka] (асинхронные задачи)
Лучшие практики
Начни с простого — масштабируй когда действительно нужно. Профилируй перед оптимизацией — не гадай. Кешируй часто запрашиваемые данные — Redis дешевле БД. Используй асинхронность — FastAPI + asyncpg. Разделяй на сервисы — микросервисная архитектура. Мониторь всё — prometeus, Grafana, ELK stack.