Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Переложи всё на инфру

Переложи всё на инфру

В своём докладе расскажу, как мы решаем типовые микросервисные задачи: как привнести надёжность в коммуникацию между сервисами, как организовать аутентификацию и авторизацию (и как их не путать!), как разделять данные на разные микросервисные базы, как быть с event driven, cqrs, shared database паттернами.

Расскажу, какие есть варианты, если мы всё разнесли по разным базами, а нам вдруг понадобились данные нескольких баз одновременно.

Расскажу какая бывает надёжность, без духоты, конкретно притащу все паттерны, что знаю, и честно скажу, что мы используем, а что нет, и что может быть полезно вам.

Всё это будет на питоне, но для всех этих случаев я покажу и расскажу, какие есть варианты получить всё то же самое, но без кода, прямо «на инфре».

https://pycon.ru/perelozhi-vsyo-na-infru

Avatar for Denis Anikin

Denis Anikin

July 26, 2025
Tweet

More Decks by Denis Anikin

Other Decks in Programming

Transcript

  1. КТО Я Техлид в AI Platform в Райфе — Лидер

    корп. Python Community — Python, typescript, kubernetes, архитектура — Выступаю, работаю в ПК — 2
  2. 4

  3. И КОГДА ЖЕ? Немного изменений — Небольшая нагрузка — Не

    очень важна устойчивость — 8 Shared database :: О паттерне
  4. ВАЖНЫЕ МОМЕНТЫ База для любой микросервисной архитектуры — Каждый сервис

    — своя база — Всё вроде просто, но… — 15 Database per service :: О паттерне
  5. МЫ ВЕРНУЛИСЬ К SHARED DATABASE 20 Database per service ::

    Получаем данные другого сервиса
  6. НЮАНСЫ REST Не текут абстракции — Контракт стабилен («ну типа»)

    — Но всё ли ок? — 23 Database per service :: Получаем данные «сервиса B» синхронно
  7. async def fetch_some_data_from_service_b(httpx_client): service_b_data = await httpx_client.get("https://service-b/entity") ... service_b_data["title"] #

    используем где-то ... 1 2 3 4 5 service_b_data = await httpx_client.get("https://service-b/entity") async def fetch_some_data_from_service_b(httpx_client): 1 2 ... 3 service_b_data["title"] # используем где-то 4 ... 5 24 Database per service :: Получаем данные «сервиса B» синхронно
  8. Категория Что это? Как достичь? Robustness Плохой ввод не ломает

    Pydantic, defensive prog. (100% mypy), ruff Resilience Восстанавливается после сбоя Circuit breakers, retries, fallbacks + cache, bulkhead Reliability Работает стабильно и предсказуемо Репликация, мониторинг, алерты, auto-restart 26 Database per service :: Синхронно + надёжно
  9. async def really_fetch_data_from_b(...): return await httpx_client.get("https://service-b/entity") async def fetch_some_data_from_service_b(...): ...

    service_b_data = really_fetch_data_from_b(...) service_b_data["title"] # используем где-то ... @cachebox.cached(cachebox.TTLCache(0, ttl=30)) 1 @stamina.retry(on=httpx.HTTPError, attempts=3, wait_initial=0.2) 2 3 4 5 6 7 8 9 10 @cachebox.cached(cachebox.TTLCache(0, ttl=30)) @stamina.retry(on=httpx.HTTPError, attempts=3, wait_initial=0.2) 1 2 async def really_fetch_data_from_b(...): 3 return await httpx_client.get("https://service-b/entity") 4 5 async def fetch_some_data_from_service_b(...): 6 ... 7 service_b_data = really_fetch_data_from_b(...) 8 service_b_data["title"] # используем где-то 9 ... 10 27 Database per service :: Синхронно + надёжно
  10. async def really_fetch_data_from_b(...): return await httpx_client.get("https://service-b/entity") async def fetch_some_data_from_service_b(...): ...

    service_b_data = really_fetch_data_from_b(...) service_b_data["title"] # используем где-то ... from aiobreaker import CircuitBreaker 1 2 emergency_db_breaker = CircuitBreaker(fail_max=5, reset_timeout=... 60) 3 4 @emergency_db_breaker 5 @cachebox.cached(cachebox.TTLCache(0, ttl=30)) 6 @stamina.retry(on=httpx.HTTPError, attempts=3, wait_initial=0.2) 7 8 9 10 11 12 13 14 15 emergency_db_breaker = CircuitBreaker(fail_max=5, reset_timeout=... 60) @emergency_db_breaker @cachebox.cached(cachebox.TTLCache(0, ttl=30)) @stamina.retry(on=httpx.HTTPError, attempts=3, wait_initial=0.2) from aiobreaker import CircuitBreaker 1 2 3 4 5 6 7 async def really_fetch_data_from_b(...): 8 return await httpx_client.get("https://service-b/entity") 9 10 async def fetch_some_data_from_service_b(...): 11 ... 12 service_b_data = really_fetch_data_from_b(...) 13 service_b_data["title"] # используем где-то 14 ... 15 29 Database per service :: Синхронно + надёжно
  11. ЕЩЁ ПРОБЛЕМЫ! Большой трафик → завалим другой сервис — Решение?

    2 паттерна — rate limiter принимающий — bulkhead исходящий — 31 Database per service :: Синхронно + надёжно
  12. emergency_db_breaker = CircuitBreaker(fail_max=5, reset_timeout=... 60) bulkhead_for_service_b = asyncio.Semaphore(10) @emergency_db_breaker @cachebox.cached(cachebox.TTLCache(0,

    ttl=30)) @stamina.retry(on=httpx.HTTPError, attempts=3, wait_initial=0.2) async with bulkhead_for_service_b: from aiobreaker import CircuitBreaker 1 2 3 4 5 6 7 8 async def really_fetch_data_from_b(...): 9 10 return await httpx_client.get("https://service-b/entity") 11 12 async def fetch_some_data_from_service_b(...): 13 ... 14 service_b_data = really_fetch_data_from_b(...) 15 service_b_data["title"] # используем где-то 16 ... 17 32 Database per service :: Синхронно + надёжно
  13. RESILIENCE retry.linkerd.io/http: 5xx retry.linkerd.io/limit: "2" retry.linkerd.io/timeout: 300ms kind: HTTPRoute 1

    … 2 metadata: 3 annotations: 4 5 6 7 spec: 8 parentRefs: 9 - name: schlep 10 kind: Service 11 … 12 rules: 13 - matches: 14 - path: 15 type: PathPrefix 16 value: "/some-service/" 17 36 Database per service :: Синхронно + перекладываем на инфру
  14. CIRCUIT BREAKER balancer.linkerd.io/failure-accrual: consecutive balancer.linkerd.io/failure-accrual-consecutive-max-failures: "5" balancer.linkerd.io/failure-accrual-consecutive-min-penalty: 10s balancer.linkerd.io/failure-accrual-consecutive-max-penalty: 1m

    balancer.linkerd.io/failure-accrual-consecutive-jitter-ratio: "0.5" kind: Service 1 metadata: 2 name: orders 3 namespace: shop 4 annotations: 5 6 7 8 9 10 spec: 11 selector: 12 app: orders 13 ports: 14 - protocol: TCP 15 … 16 37 Database per service :: Синхронно + перекладываем на инфру
  15. RATE LIMITER requestsPerSecond: 100 requestsPerSecond: 20 - requestsPerSecond: 25 kind:

    HTTPLocalRateLimitPolicy 1 ... 2 spec: 3 targetRef: 4 group: policy.linkerd.io 5 ... 6 total: 7 8 identity: 9 10 overrides: 11 12 clientRefs: 13 - kind: ServiceAccount 14 ... 15 38 Database per service :: Синхронно + перекладываем на инфру
  16. ОСТАЛОСЬ ВСЕГО НИЧЕГО! Добавить кеш + fallback — Семафор (он

    же bulkhead) — Ну и топ — 39 Database per service :: Синхронно + перекладываем на инфру
  17. КАКОЙ МОТИВ? Это надёжно! — Low latency — 43 Database

    per service :: Получаем данные «сервиса B» асинхронно
  18. async def calculate_in_service_a(cart_items): attributes = await select_from_database_service_b_data(product_ids) for item in

    cart_items: buffer.append(attributes[item["id"]]["price"]) return await calculate_llm_reccomendations(buffer) 1 2 3 4 5 6 7 attributes = await select_from_database_service_b_data(product_ids) async def calculate_in_service_a(cart_items): 1 2 3 for item in cart_items: 4 buffer.append(attributes[item["id"]]["price"]) 5 6 return await calculate_llm_reccomendations(buffer) 7 45 Database per service :: Получаем данные «сервиса B» асинхронно
  19. НЮАНСЫ MQ Триггерится сервис b — Ловим в сервисе а,

    делаем реплику — Но тут есть проблема… — 46 Database per service :: Получаем данные «сервиса B» асинхронно
  20. МЫ ТАК ЧАСТО ДЕЛАЕМ 50 Database per service :: Получаем

    данные «сервиса B» асинхронно
  21. АВТОРИЗАЦИЯ И АУТЕНТИФИКАЦИЯ Аутентификация — логопас — ← штука не

    уникальная Авторизация — проверка токена — ← у всех своя 72 Auth