Итоговый проект: асинхронный API-сервис
Параллельно получаем данные из многих источников, обрабатываем их и отдаём - единый сервис на asyncio.
Главная идея: реальный сервис большую часть времени ждёт - сетевых ответов, диска, других сервисов.
asyncio позволяет одному потоку держать тысячи таких ожиданий одновременно и переключаться на то, что готово. В этом итоговом проекте мы строим небольшой, но приближённый к продакшену агрегатор котировок: он параллельно получает цены из нескольких внешних API, приводит их к типизированным записям, кэширует результаты с временем жизни (TTL) и отвечает на HTTP-запросы - переиспользуя типизацию, dataclasses, контекстные менеджеры, asyncio и профилирование из тем 1-15.
К концу темы вы сможете:
- разбить сервис на слои получение -> обработка -> отдача вокруг одного цикла событий,
- выполнять ввод-вывод параллельно через
asyncio.gatherиasyncio.TaskGroup, - ограничивать параллелизм через
Semaphoreи добавлять таймауты на каждый вызов, - владеть сетевыми ресурсами через асинхронный контекстный менеджер,
- держать цикл отзывчивым, вынося CPU-нагрузку в
run_in_executor, - профилировать сервис, чтобы доказать, куда реально уходит время.
1. Форма сервиса
Прежде чем писать код, определите границы. У нашего сервиса три обязанности, каждая - тонкий слой с чётким контрактом:
- Получение - обращаемся к N внешним API по HTTP и возвращаем сырой JSON. Это чистый ввод-вывод и причина использовать async.
- Обработка - превращаем разнородные сырые данные в единую типизированную форму (
Quote). В основном лёгкая по CPU, изредка нет. - Отдача - принимаем запросы клиента, оркестрируем получение + обработку, кэшируем ответ и отвечаем.
Разделение означает, что каждый слой тестируется и профилируется отдельно, а замена, скажем, HTTP-клиента не затрагивает логику отдачи. Диаграмма ниже показывает один запрос, проходящий через цикл, пока три вызова к источникам выполняются одновременно.