Какие уникальные признаки микросервисов?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Уникальные признаки микросервисов
Микросервисная архитектура отличается от монолита фундаментальными принципами организации приложений. Рассмотрим её ключевые характеристики.
1. Декомпозиция по функциям бизнеса (Business Capability)
Каждый микросервис отвечает за одну бизнес-функцию, а не технологический слой.
Монолит (слоистая архитектура):
┌─────────────────┐
│ Presentation │
├─────────────────┤
│ Business Logic │
├─────────────────┤
│ Data Access │
├─────────────────┤
│ Database │
└─────────────────┘
Микросервисы (по функциям):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Users │ │ Orders │ │ Payments │
│ Service │ │ Service │ │ Service │
├─────────────┤ ├─────────────┤ ├─────────────┤
│ Logic+Data │ │ Logic+Data │ │ Logic+Data │
└─────────────┘ └─────────────┘ └─────────────┘
Каждый сервис содержит:
- Свой код (API)
- Свою бизнес-логику
- Свою БД
Вместо одного файла User класса в монолите, у нас есть отдельный микросервис, отвечающий за всё связанное с пользователями.
2. Независимая разработка и развёртывание
Каждый микросервис может быть разработан, протестирован и развёрнут независимо от остальных.
Монолит:
- Изменение в одном модуле требует пересборки всего приложения
- Развёртывание: все компоненты обновляются вместе
- Риск: одна ошибка ломает всё приложение
Микросервисы:
- User Service (Python 3.11) → развёртыватся
- Order Service (Python 3.12) → развёртыватся отдельно
- Payment Service (Go) → может быть на другом языке
- Каждый может иметь свою версию зависимостей
Это позволяет разным командам работать в своём темпе без координации.
3. Распределённая база данных (Database per Service)
Каждый микросервис имеет свою БД, нет общей базы данных.
# Монолит: одна БД
from sqlalchemy import create_engine
engine = create_engine('postgresql://localhost/shared_db')
# Все таблицы: users, orders, payments, products
# Микросервисы: разные БД
# User Service
user_db = create_engine('postgresql://localhost/users_db')
# Order Service
order_db = create_engine('postgresql://localhost/orders_db')
# Payment Service
payment_db = create_engine('postgresql://localhost/payments_db')
Преимущества:
- Независимость: один сервис не блокирует другой
- Масштабируемость: каждая БД оптимизирована для своих нужд
Недостатки:
- Сложнее запросы между сервисами
- Нужна собственная синхронизация данных
4. Слабая связь (Loose Coupling)
Микросервисы взаимодействуют через чётко определённые интерфейсы, слабо зависят друг от друга.
# Монолит: тесная связь
class OrderService:
def create_order(self, user_id, items):
# Вызов функции из другого модуля
user = UserRepository.get_user(user_id) # Прямая зависимость
payment = PaymentService.charge(user, total) # Синхронный вызов
# Если UserService сломается, весь монолит ломается
# Микросервисы: слабая связь
# Order Service
import requests
def create_order(user_id, items):
# Вызов через HTTP API
try:
user = requests.get(f'http://user-service/users/{user_id}').json()
except requests.RequestException:
# Можем обработать ошибку, использовать кэш и т.д.
user = cache.get(user_id)
# Асинхронный вызов через очередь
queue.enqueue('payment.charge', user_id, total)
return {"order_id": new_order_id, "status": "pending"}
Если Payment Service недоступен, Order Service продолжает работать и ставит задачу в очередь.
5. Сервис-ориентированная коммуникация
Микросервисы общаются через API (обычно REST/gRPC) или асинхронные очереди.
# Синхронная коммуникация (HTTP REST)
from fastapi import FastAPI
from httpx import AsyncClient
app = FastAPI()
@app.post("/orders")
async def create_order(order: OrderCreate):
# Синхронный вызов к другому сервису
async with AsyncClient() as client:
user_response = await client.get(
f"http://user-service/users/{order.user_id}"
)
user = user_response.json()
# Создание заказа
order_id = db.create_order(order.dict())
return {"id": order_id, "user": user}
# Асинхронная коммуникация (через очередь)
import celery
app.celery = celery.Celery()
@app.post("/orders")
def create_order_async(order: OrderCreate):
# Ставим задачу в очередь
app.celery.send_task(
'payment.process',
args=[order.id, order.total],
queue='payments'
)
return {"id": order.id, "status": "pending"}
# Payment Service слушает очередь
@app.celery.task
def process_payment(order_id, total):
# Обработка платежа
pass
6. Отказоустойчивость (Resilience)
Микросервисы должны быть устойчивы к сбоям других сервисов.
from circuitbreaker import circuit
import time
# Circuit Breaker паттерн
@circuit(failure_threshold=5, recovery_timeout=60)
def call_payment_service(amount):
response = requests.post(
'http://payment-service/charge',
json={'amount': amount},
timeout=5
)
return response.json()
def create_order(order):
try:
payment = call_payment_service(order.total)
except CircuitBreakerListener:
# Payment Service недоступен, но заказ создаётся
# с статусом "pending payment"
order.status = "payment_pending"
db.save(order)
return {"id": order.id, "status": "pending"}
order.status = "paid"
db.save(order)
return {"id": order.id, "status": "paid"}
# Retry логика
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2))
def call_user_service(user_id):
return requests.get(f'http://user-service/users/{user_id}').json()
# Timeout
response = requests.get(
'http://order-service/orders',
timeout=2 # Не ждём более 2 секунд
)
7. Масштабируемость
Каждый микросервис может масштабироваться независимо в зависимости от нагрузки.
# Kubernetes пример
# User Service не нагружен: 1 реплика
kubectl set replicas deployment/user-service --replicas=1
# Order Service получает всю нагрузку: 10 реплик
kubectl set replicas deployment/order-service --replicas=10
# Payment Service требует мощности: 5 реплик
kubectl set replicas deployment/payment-service --replicas=5
# Каждый сервис может иметь свой размер ресурсов
# User Service: 256Mi RAM (лёгкий)
# ML Service: 2Gi RAM (тяжёлый)
Вместо масштабирования целого монолита, масштабируем только нужные сервисы.
8. Организация команд (Conway's Law)
Архитектура микросервисов часто соответствует организационной структуре команд.
Монолит: одна большая команда
┌──────────────────────────────┐
│ Одна команда работает │
│ на всей кодовой базе │
└──────────────────────────────┘
Микросервисы: отдельные команды
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Users Team │ │ Orders Team │ │Payments Team│
├─────────────┤ ├─────────────┤ ├─────────────┤
│ User Service│ │Order Service│ │Payment Svc │
└─────────────┘ └─────────────┘ └─────────────┘
Каждая команда может:
- Выбирать свой tech stack
- Работать в своём темпе
- Иметь свой процесс разработки
9. Сложность операционных задач
Микросервисы требуют более сложной инфраструктуры и мониторинга.
# Необходимо мониторить много сервисов
from prometheus_client import Counter, Histogram
import time
request_count = Counter(
'service_requests_total',
'Total requests',
['service', 'method', 'endpoint', 'status']
)
request_duration = Histogram(
'service_request_duration_seconds',
'Request duration',
['service', 'endpoint']
)
@app.middleware("http")
async def middleware(request, call_next):
start = time.time()
response = await call_next(request)
duration = time.time() - start
request_count.labels(
service="order-service",
method=request.method,
endpoint=request.url.path,
status=response.status_code
).inc()
request_duration.labels(
service="order-service",
endpoint=request.url.path
).observe(duration)
return response
# Логирование через единый агрегатор
import logging
from pythonjsonlogger import jsonlogger
logger = logging.getLogger()
handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter()
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.info("Order created", extra={
"order_id": "123",
"user_id": "456",
"service": "order-service",
"trace_id": "xyz789"
})
10. Консистентность данных (Eventual Consistency)
Микросервисы часто работают с финальной консистентностью вместо строгой ACID.
# ACID в монолите (строгая консистентность)
transaction = db.begin_transaction()
try:
db.update_users(user_id, balance=balance - 100)
db.update_orders(order_id, status='paid')
transaction.commit()
except Exception as e:
transaction.rollback()
# Eventual Consistency в микросервисах
# Event Sourcing
from dataclasses import dataclass
@dataclass
class OrderPaidEvent:
order_id: str
amount: float
timestamp: str
def create_order(order):
# 1. Создание заказа в Order Service
order.status = 'pending'
db.save(order)
# 2. Публикация события
event_bus.publish('order.paid', OrderPaidEvent(
order_id=order.id,
amount=order.total,
timestamp=datetime.now().isoformat()
))
# 3. User Service слушает событие и обновляет баланс
@event_bus.subscribe('order.paid')
def on_order_paid(event):
user = user_db.get_user(order.user_id)
user.balance -= event.amount
user_db.save(user)
# Может быть небольшая задержка, но в итоге консистентно
Сравнение с монолитом
| Характеристика | Монолит | Микросервисы |
|---|---|---|
| Разработка | Просто | Сложно (распределённые системы) |
| Развёртывание | Всё или ничего | Независимое |
| Масштабируемость | Целое приложение | Каждый сервис |
| Надёжность | Одна точка отказа | Отказы изолированы |
| Производительность | Выше (нет сетевых задержек) | Ниже (сетевые вызовы) |
| Данные | Общая БД (строгая консистентность) | Разные БД (финальная консистентность) |
| Отладка | Проще | Сложнее (распределённые трассировки) |
| Версионирование | Одна версия | Разные версии одновременно |
Когда использовать микросервисы
Хорошо для:
- Больших команд (10+ разработчиков)
- Сложных систем с разными требованиями к масштабируемости
- Когда части системы изменяются независимо
Плохо для:
- Стартапов (избыточная сложность)
- Простых CRUD приложений
- Небольших команд (< 5 человек)
Причина: микросервисы требуют хорошей инфраструктуры, мониторинга и опытной команды.