Архитектура распределённых систем
Современный подход к разработке масштабируемых приложений
Microservices — архитектурный стиль, при котором приложение разбивается на множество независимых сервисов, каждый из которых отвечает за свою бизнес-функцию.
┌─────────────────────────────┐
│ MONOLITH │
│ ┌───────────────────────┐ │
│ │ User Service │ │
│ │ Order Service │ │
│ │ Payment Service │ │
│ │ Notification Service │ │
│ │ Database │ │
│ └───────────────────────┘ │
└─────────────────────────────┘
Один процесс
Одна база данных
Одно развертывание
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ User Service │ │Order Service │ │Payment Service│
│ :8001 │ │ :8002 │ │ :8003 │
│ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │
│ │ DB │ │ │ │ DB │ │ │ │ DB │ │
│ └──────┘ │ │ └──────┘ │ │ └──────┘ │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└─────────────────┴─────────────────┘
API Gateway
:8080
Каждый сервис — отдельный процесс, своя БД
┌─────────────────────────────────────────┐
│ API Gateway (Kong/Nginx) │
└─────────┬───────────────────────────────┘
│
┌─────┴─────┬──────────┬──────────┬──────────┐
│ │ │ │ │
┌───┴────┐ ┌───┴────┐ ┌───┴────┐ ┌───┴────┐ ┌───┴────┐
│ User │ │ Order │ │Payment │ │Catalog │ │Shipping│
│Service │ │Service │ │Service │ │Service │ │Service │
│:8001 │ │:8002 │ │:8003 │ │:8004 │ │:8005 │
└───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘
│ │ │ │ │
┌───┴────┐ ┌───┴────┐ ┌───┴────┐ ┌───┴────┐ ┌───┴────┐
│Postgres│ │Postgres│ │ Redis │ │MongoDB │ │Postgres│
└────────┘ └────────┘ └────────┘ └────────┘ └────────┘
# user-service/main.py
from fastapi import FastAPI
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
app = FastAPI()
engine = create_engine("postgresql://user:pass@localhost/user_db")
Session = sessionmaker(bind=engine)
@app.get("/users/{user_id}")
def get_user(user_id: int):
session = Session()
user = session.query(User).filter(User.id == user_id).first()
return {
"id": user.id,
"name": user.name,
"email": user.email
}
@app.post("/users")
def create_user(user_data: UserCreate):
session = Session()
user = User(name=user_data.name, email=user_data.email)
session.add(user)
session.commit()
return {"id": user.id, "name": user.name}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8001)
# order-service/main.py
from fastapi import FastAPI
import httpx
app = FastAPI()
# Клиент для User Service
user_service_url = "http://user-service:8001"
@app.post("/orders")
async def create_order(order_data: OrderCreate):
# 1. Проверяем пользователя через User Service
async with httpx.AsyncClient() as client:
user_response = await client.get(
f"{user_service_url}/users/{order_data.user_id}"
)
if user_response.status_code != 200:
raise HTTPException(404, "User not found")
user = user_response.json()
# 2. Создаём заказ
order = Order(
user_id=order_data.user_id,
items=order_data.items,
total=calculate_total(order_data.items)
)
db.session.add(order)
db.session.commit()
# 3. Отправляем событие (async)
event_bus.publish("order_created", {
"order_id": order.id,
"user_id": order.user_id,
"total": order.total
})
return {"order_id": order.id, "status": "created"}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8002)
# api-gateway/main.py
from fastapi import FastAPI, HTTPException
import httpx
app = FastAPI()
SERVICES = {
"users": "http://user-service:8001",
"orders": "http://order-service:8002",
"payments": "http://payment-service:8003"
}
@app.api_route("/{service}/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
async def proxy(service: str, path: str, request: Request):
if service not in SERVICES:
raise HTTPException(404, "Service not found")
service_url = SERVICES[service]
url = f"{service_url}/{path}"
# Проксируем запрос
async with httpx.AsyncClient() as client:
response = await client.request(
method=request.method,
url=url,
headers=dict(request.headers),
content=await request.body()
)
return Response(
content=response.content,
status_code=response.status_code,
headers=dict(response.headers)
)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8080)
💡 API Gateway — единая точка входа для всех клиентов
# Order Service вызывает User Service
import httpx
async def validate_user(user_id: int):
async with httpx.AsyncClient() as client:
response = await client.get(f"http://user-service/users/{user_id}")
return response.json()
# Проблемы:
# - Зависимость от доступности сервиса
# - Медленно (сетевая задержка)
# - Каскадные сбои
💡 Используйте для операций, которые требуют немедленного ответа
# Order Service публикует событие
from redis import Redis
import json
redis = Redis(host='redis', port=6379)
def create_order(order_data):
order = Order.create(order_data)
# Публикуем событие (не ждём ответа)
redis.publish("order_created", json.dumps({
"order_id": order.id,
"user_id": order.user_id,
"total": order.total
}))
return order
# Payment Service подписывается на событие
def on_order_created(message):
data = json.loads(message['data'])
# Обрабатываем платеж асинхронно
process_payment(data['order_id'], data['total'])
redis_subscriber = redis.pubsub()
redis_subscriber.subscribe(**{"order_created": on_order_created})
💡 Используйте для операций, которые можно выполнить асинхронно
┌─────────────┐
│ Service │
│ (App) │
└──────┬──────┘
│
┌──────┴──────┐
│ Sidecar │ ← Service Mesh Proxy
│ (Envoy) │ - Load balancing
└──────┬──────┘ - Circuit breaker
│ - Retry
│ - Monitoring
↓
Network
💡 Service Mesh управляет коммуникацией между сервисами
Единая точка входа для всех клиентов.
Client → API Gateway → [Service1, Service2, Service3] Функции: - Маршрутизация - Аутентификация - Rate limiting - Логирование - Мониторинг
💡 Примеры: Kong, AWS API Gateway, Nginx
Защита от каскадных сбоев.
from circuitbreaker import circuit
@circuit(failure_threshold=5, recovery_timeout=60)
async def call_user_service(user_id: int):
async with httpx.AsyncClient() as client:
response = await client.get(f"http://user-service/users/{user_id}")
return response.json()
# Если сервис падает 5 раз подряд,
# Circuit Breaker открывается на 60 секунд
# Все запросы сразу возвращают ошибку
# (не нагружают упавший сервис)
Управление распределёнными транзакциями.
# Создание заказа требует действий в нескольких сервисах
class CreateOrderSaga:
def __init__(self):
self.steps = [
self.reserve_inventory,
self.process_payment,
self.create_shipment
]
self.compensations = [
self.release_inventory,
self.refund_payment,
self.cancel_shipment
]
async def execute(self, order_data):
completed_steps = []
try:
for step in self.steps:
await step(order_data)
completed_steps.append(step)
except Exception as e:
# Откатываем выполненные шаги
for step in reversed(completed_steps):
compensation = self.get_compensation(step)
await compensation(order_data)
raise e
💡 Если один шаг падает — откатываем все предыдущие
Каждый сервис имеет свою базу данных.
User Service → User Database (PostgreSQL) Order Service → Order Database (PostgreSQL) Catalog Service → Catalog Database (MongoDB) Analytics Service → Analytics Database (ClickHouse) Правила: - Сервис не может напрямую обращаться к БД другого сервиса - Коммуникация только через API - Каждый сервис выбирает БД под свои нужды
| Сценарий | Рекомендация |
|---|---|
| Большая команда (>10 разработчиков) | ✅ Подходит |
| Разные требования к масштабированию | ✅ Подходит |
| Разные технологии для разных частей | ✅ Подходит |
| Маленькая команда (<5 разработчиков) | ❌ Не подходит (начните с монолита) |
| Простое приложение | ❌ Избыточно |
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"]
# docker-compose.yml
version: '3.8'
services:
user-service:
build: ./user-service
ports:
- "8001:8001"
environment:
- DATABASE_URL=postgresql://user:pass@postgres:5432/user_db
order-service:
build: ./order-service
ports:
- "8002:8002"
depends_on:
- user-service
# kubernetes/user-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: user-service:latest
ports:
- containerPort: 8001
---
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- port: 8001
targetPort: 8001
Микросервисы, которые слишком тесно связаны.
# ❌ Сервисы вызывают друг друга синхронно
order_service → user_service (синхронно)
order_service → payment_service (синхронно)
order_service → inventory_service (синхронно)
# Если один падает — падает весь поток
# Это не микросервисы, это распределённый монолит!
✅ Решение: используйте асинхронную коммуникацию, события
Несколько сервисов используют одну БД.
# ❌ Все сервисы обращаются к одной БД
user_service → shared_database
order_service → shared_database
payment_service → shared_database
# Проблемы:
# - Сложно изменить схему
# - Невозможно независимое развертывание
# - Сложно масштабировать
✅ Решение: Database per Service
Слишком мелкое разбиение.
# ❌ Слишком много сервисов
user-service
user-profile-service
user-settings-service
user-preferences-service
user-notifications-service
# Проблемы:
# - Сложность управления
# - Много сетевых вызовов
# - Сложное тестирование
✅ Решение: группируйте связанные функции в один сервис
"Начните с монолита. Разбивайте на микросервисы только когда почувствуете боль от монолита."
— Martin Fowler