Как работают асинхронные боты и их основные принципы?
Что проверяют
Понимаете ли вы, как один поток с event loop обслуживает тысячи чатов и что его ломает. Следом обычно спрашивают, что будет, если в обработчике вызвать requests.get или time.sleep.
Как отвечать
Опишите устройство на примере Telegram-бота на aiogram 3 или python-telegram-bot, обе библиотеки построены на asyncio:
- получение апдейтов: long polling (бот сам вызывает
getUpdates) или webhook (Telegram присылает запрос на ваш HTTPS-адрес, обычно за aiohttp или FastAPI). Webhook удобнее под нагрузкой, polling проще для старта; - event loop: один поток, который крутит корутины. Корутина работает до
await, отдаёт управление, и цикл берёт следующую готовую задачу. Пока один обработчик ждёт ответа API, другие продолжают работу; - диспетчер и роутеры: апдейт проходит через middleware и фильтры и попадает в нужный хендлер. Состояние диалога хранится в FSM, в проде в Redis, а не в памяти процесса;
- неблокирующий ввод-вывод: HTTP через
aiohttpилиhttpx.AsyncClient, база черезasyncpgили асинхронный SQLAlchemy. Неизбежный синхронный код уводят в поток черезasyncio.to_thread, тяжёлые вычисления в отдельный процесс или очередь задач.
Отдельно скажите про ограничения самого Telegram. Он режет частоту отправки: примерно одно сообщение в секунду в один чат и порядка 30 в секунду при массовой рассылке, а при превышении отвечает 429 с retry_after. Рассылку поэтому делают через очередь и asyncio.Semaphore, а не через gather на всю базу.
Где спотыкаются
Вызывают синхронную библиотеку внутри async def. time.sleep(5) или requests.get замораживают весь цикл, и бот перестаёт отвечать всем пользователям, а не одному.
Путают асинхронность с параллелизмом. На одном event loop код Python выполняется по очереди, CPU-нагрузку это не ускоряет, для неё нужны процессы.
Создают задачи через asyncio.create_task и не сохраняют ссылку. Цикл держит на задачу только слабую ссылку, и её могут собрать до завершения. Задачи хранят в множестве или запускают через asyncio.TaskGroup.