Что отказоустойчивее - микросервис или монолит?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что отказоустойчивее — микросервис или монолит?
Отказоустойчивость (fault tolerance) — это способность системы продолжать работать при сбое отдельных компонентов. Здесь нет простого ответа — каждый подход имеет свои преимущества и недостатки.
Отказоустойчивость монолита
Недостатки:
- Отказ целого приложения Если упадёт один процесс монолита, недоступны все функции.
# Монолит: одно падение — всё падает
# api.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/users")
async def get_users():
return ["user1", "user2"]
@app.get("/products")
async def get_products():
return ["product1", "product2"]
# Если возникает критическая ошибка в get_users,
# пользователи не смогут получить products
if some_error:
raise Exception("Database connection failed")
# Весь сервис перестаёт работать
- Каскадные отказы Ошибка в одном модуле может сломать другой.
# user_service.py
class UserService:
def get_user(self, user_id):
user = db.query(User).get(user_id) # DB error
self.log_analytics(user) # Ошибка в другом сервисе
return user
# Если log_analytics упадёт, весь get_user не работает
- Сложность восстановления Приходится перезагружать всё приложение, даже если ошибка локальна.
Преимущества:
- Нет сетевых разделов Компоненты работают в одном процессе, нет проблем с сетевыми отказами между компонентами.
# Монолит: всё работает локально
class OrderService:
def create_order(self, user_id, items):
user = UserService().get_user(user_id) # Быстро
inventory = InventoryService().check_stock(items) # Быстро
# Нет проблем с таймаутами или потерей сообщений
order = Order(user=user, items=items)
db.add(order)
db.commit()
- Транзакции Можно использовать ACID транзакции для консистентности.
from django.db import transaction
with transaction.atomic():
user = User.objects.create(username="john")
order = Order.objects.create(user=user)
# Если что-то упадёт, всё откатывается
Отказоустойчивость микросервисов
Преимущества:
- Изоляция сбоев Если упадёт один сервис, другие продолжают работать.
# users-service/main.py
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"id": user_id, "name": "John"}
# products-service/main.py
@app.get("/products/{product_id}")
async def get_product(product_id: int):
return {"id": product_id, "name": "Laptop"}
# api-gateway/main.py
@app.get("/api/dashboard")
async def dashboard():
try:
users = httpx.get("http://users-service:8000/users").json()
except:
users = [] # Graceful degradation
try:
products = httpx.get("http://products-service:8000/products").json()
except:
products = [] # API продолжает работать
return {"users": users, "products": products}
- Масштабирование отдельных частей Можно добавить больше инстансов нужного сервиса.
# Проблема с users-service? Добавляем ещё инстансы
kubectl scale deployment users-service --replicas=5
# orders-service работает нормально, не нужно его масштабировать
- Независимое развёртывание Можно обновить один сервис без перезагрузки других.
Недостатки:
- Сетевые разделы (Network Partitions) Если связь между сервисами разорвана, возникают проблемы с консистентностью.
# order-service пытается обновить inventory
try:
response = httpx.get(
"http://inventory-service:8000/check-stock",
timeout=5
)
except httpx.TimeoutException:
# Что делать? Считать stock доступным или нет?
# Это может привести к overselling
pass
- Распределённые транзакции Не могу использовать простые ACID транзакции.
# order-service
async def create_order(user_id, items):
order = await db.create_order(user_id, items)
# Попытка зарезервировать stock
try:
await inventory_service.reserve_stock(items)
except:
# Откатить заказ?
await db.cancel_order(order.id)
raise
# Что если после reserve_stock сбой?
# Нужна компенсирующая транзакция (Saga pattern)
- Сложность отладки Ошибки разбросаны по разным сервисам, нужны распределённые логи и трейсы.
Паттерны отказоустойчивости
1. Circuit Breaker Предотвращает каскадные отказы.
from pybreaker import CircuitBreaker
db_breaker = CircuitBreaker(fail_max=5, reset_timeout=60)
@app.get("/users/{user_id}")
async def get_user(user_id: int):
@db_breaker
def fetch_user():
return db.query(User).get(user_id)
try:
return fetch_user()
except:
# Вернуть кэшированные данные или default
return {"id": user_id, "name": "Unknown"}
2. Retry Logic Повторить операцию при временном отказе.
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)
)
async def call_external_service():
response = await httpx.get("http://external-service/api")
return response.json()
3. Fallback Использовать альтернативный источник данных.
async def get_user_data(user_id: int):
# Попытка 1: основная БД
try:
return await main_db.get_user(user_id)
except:
pass
# Попытка 2: кэш
try:
return await redis_cache.get(f"user:{user_id}")
except:
pass
# Попытка 3: читаемая реплика
try:
return await read_replica.get_user(user_id)
except:
pass
# Fallback: минимальные данные
return {"id": user_id, "name": "Unknown"}
4. Saga Pattern Для распределённых транзакций.
# Шаг 1: создать заказ
order = await order_service.create(user_id, items)
try:
# Шаг 2: зарезервировать stock
await inventory_service.reserve(items)
except:
# Шаг 2a: откатить заказ (компенсирующая транзакция)
await order_service.cancel(order.id)
raise
try:
# Шаг 3: обработать платёж
await payment_service.charge(user_id, order.total)
except:
# Шаг 3a: вернуть stock
await inventory_service.release(items)
# Шаг 3b: откатить заказ
await order_service.cancel(order.id)
raise
Сравнение
| Аспект | Монолит | Микросервисы |
|---|---|---|
| Отказ целого сервиса | Высокий риск | Низкий риск |
| Отказ компонента | Влияет на всё | Только на сервис |
| Сетевые разделы | Нет проблемы | Большая проблема |
| Простота трансакций | Высокая | Низкая |
| Каскадные отказы | Высокий риск | Можно предотвратить |
| Сложность реализации отказоустойчивости | Низкая | Высокая |
Заключение
Микросервисы теоретически более отказоустойчивы благодаря изоляции, но требуют сложных паттернов (Circuit Breaker, Saga, Retry) для реальной отказоустойчивости. Монолит проще реализовать, но менее гибкий. Выбор зависит от требований проекта и готовности к сложности.