惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
美团技术团队
Last Week in AI
Last Week in AI
WordPress大学
WordPress大学
L
LangChain Blog
雷峰网
雷峰网
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 叶小钗
Engineering at Meta
Engineering at Meta
腾讯CDC
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
有赞技术团队
有赞技术团队
Blog — PlanetScale
Blog — PlanetScale
博客园 - Franky
博客园 - 司徒正美
The Cloudflare Blog
Google DeepMind News
Google DeepMind News
T
Tailwind CSS Blog
C
Check Point Blog
小众软件
小众软件
V
Visual Studio Blog
V
V2EX
F
Full Disclosure
J
Java Code Geeks
MongoDB | Blog
MongoDB | Blog
罗磊的独立博客
人人都是产品经理
人人都是产品经理
量子位
Apple Machine Learning Research
Apple Machine Learning Research
F
Fortinet All Blogs
Microsoft Security Blog
Microsoft Security Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 【当耐特】
博客园_首页
Y
Y Combinator Blog
N
Netflix TechBlog - Medium
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
Recorded Future
Recorded Future
G
Google Developers Blog
Vercel News
Vercel News
大猫的无限游戏
大猫的无限游戏
Microsoft Azure Blog
Microsoft Azure Blog
U
Unit 42
爱范儿
爱范儿
Jina AI
Jina AI

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как мы переписывали логику очередей: Celery => aio-pika => FastStream
GregTMJ · 2026-04-30 · via Все публикации подряд на Хабре

Наш путь активной работы с очередями RabbitMQ начался с классического Celery. Осознав критичность низкоуровневого контроля системы, принялись работать с aio-pika. Но и этот уровень слишком местами сложный (далее расскажу почему), и нашли отличное решение, на текущий момент, в лице FastStream. Сразу оставлю такую пометку, что каждый инструмент подходит для решения своей задачи. Мы больше хотели сделать акцент на удобство и скорость разработки относительно затрачиваемого времени на миграции решений.

N.B.: Код возможно покажется неоптимальным или старым. Это всё наш дорогой Легаси.

Постановка задачи

Наша система построена на основе микросервисов, работающих с RabbitMQ. Внутри - обычный асинхронный код для похода на внешние API и в БД.

Требования:

  • Надежный консьюминг - это для нас критично, чтобы сообщение шло по всему флоу и нигде не останавливалось без причин. Если ошибка падает, то это должно отражаться в 3 местах: БД, логи и метрики.

  • Ретраи при ошибках обработки.

  • Трейсинг - поддержка OpenTelemetry.

  • Мониторинг сервиса через healthcheck’и.

  • Prometheus метрики.


Решение №1: Celery как консьюмер

Почему Celery

Celery — классический инструмент для фоновых задач, знакомый большинству Python-разработчиков. Из коробки: декларативное описание задач, ретраи с экспоненциальной задержкой, хранение результатов, мониторинг через Flower, интеграции с фреймворками. Логика проста: пишешь @app.task, запускаешь воркер — и сообщения из очереди начинают обрабатываться.

Как мы его использовали

Мы не отправляли задачи из кода в духе my_task.delay(), а настраивали Celery на прослушивание внешней очереди, куда сообщения попадали от других систем. По сути, Celery выступал как consumer: подключался к брокеру, забирал сообщения, десериализовал и передавал в наши обработчики. Настройки вроде max_retries, default_retry_delay, countdown позволяли гибко управлять поведением при сбоях. Важно ещё подсветить, что результат всегда игнорируется с помощью параметра ignore_result=True поскольку все результаты записываются в БД.

Пример инициализации воркера:

def create_app(
    name,
    broker,
    include,
    backend=None,
    task_queues=None,
    liveness_probe=1,
    update_period=60,
    watcher_config={},
):
    # Создание само приложение + наложение дополнительных конфигурации
    app = Celery(name, broker=broker, include=include, backend=backend)
    app.conf.update(
        result_expires=120,
    )
    add_without_heartbeat_argument = Option(
        ("--without-heartbeat",),
        default=True,
    )

    app.user_options["worker"].add(add_without_heartbeat_argument)
    if task_queues is not None:
        app.conf.task_queues = task_queues

    if liveness_probe:
        add_update_period_argument = Option(
            ("--update-period",),
            default=update_period,
        )

        HEARTBEAT_FILE.touch()

        app.user_options["worker"].add(add_update_period_argument)

        # Добавление кастомной livenessProbe для K8s
        app.steps["worker"].add(LivenessProbe)
    
    # инициализация трейсинга
    with_tracing = watcher_config.get("with_tracing")
    if with_tracing:
        tracing_exporters = watcher_config.get("tracing_exporters", ())
        signals.worker_process_init.connect(
            init_celery_tracing(app_name=name, tracing_exporters=tracing_exporters),
            weak=False,
        )
    return app

С чем столкнулись

Celery проектировался как система передачи сообщений между системами. Из-за чего столкнулись со следующими проблемами:

  • Потребление памяти — из-за оборачивания каждого сообщения в метаданные и хранения внутренних структур Celery расход оперативной памяти быстро рос пропорционально потоку сообщений. При высоком темпе обработки воркер начинал использовать значительно больше ОЗУ, чем требовалось самой бизнес-логике, что вынуждало выделять избыточные ресурсы.

  • Управление соединениями и heartbeat — Celery скрывает многие детали брокера, из‑за чего при сетевых сбоях восстановление происходило с задержками, а тонкая настройка consumer_timeout, broker_transport_options была сложной и плохо документированной.

  • Избыточность — Как можно заметить мы используем минимальные дополнительные конфигурации Celery.

  • Падение контейнера на битом сообщений — Будет не совсем справедливо относить это полностью к минусам самого Celery, это больше камень в наш огород. Но опыт есть опыт. Если воркер обрабатывал невалидное сообщение в плане структуры, то падал весь контейнер, при этом сообщение консьюмилось. Что приводило к непониманию “куда делось сообщение и что с ним произошло?”.

Стало ясно: Celery — отличный выбор для фоновых задач с результатом, но для непрерывного потокового консьюминга он перегружен и недостаточно прозрачен.


Решение №2: aio-pika — близко к железу

Идея

Перейти на чистый AMQP, отказавшись от посредника в виде Celery. aio-pika — асинхронная библиотека для RabbitMQ, предоставляющая прямой доступ к каналам, обменникам, очередям. Код становится минимальной прослойкой над протоколом: сами управляем подписками, подтверждениями (ack/nack), префетчем, реконнектами.

Что переписали

Написали небольшой собственный “фреймворк” консьюминга: асинхронный раннер, который при запуске создаёт соединение, открывает канал, объявляет очереди с нужными параметрами durable/exclusive, подписывается на них, а в колбэке вызывает наши обработчики.

Поверх этого появились:

  • Ручной retry — Декоратор поверх колбэка, имеющий свой счётчик кол-во повторов при возникновений ошибок.

  • LivenessProb — Кастомный на уровне ядра aio-pika представлен чуть ниже.

  • Трейсинг — вручную оборачиваем вызовы в OpenTelemetry спаны, передаём trace context в заголовках AMQP при ретраях.

Пример LivenessProb:

class CustomRobustConnection(RobustConnection):
    def __init__(
        self,
        url: URL,
        loop: asyncio.AbstractEventLoop | None = None,
        **kwargs: Any,
    ):
        super().__init__(url=url, loop=loop, **kwargs)
        # Кастомный класс пробы, схож с тем, что выше для Celery
        self._liveness_probe = LivenessProbe()

    async def connect(self, timeout: TimeoutType = None) -> None:
        # Тут держитесь крепче. Я когда впервые это увидел, без литра кофе не смог понять как оно работает
        self._RobustConnection__connect_timeout = timeout

        if self.is_closed:
            raise RuntimeError(f"{self!r} connection closed")

        if self.reconnecting:
            raise RuntimeError(
                (
                    "Connect method called but connection "
                    f"{self!r} is reconnecting right now."
                ),
                self,
            )

        if not self._RobustConnection__reconnection_task:
            self._RobustConnection__reconnection_task = self.loop.create_task(
                self.__connection_factory(),
            )

        await self._RobustConnection__fail_fast_future
        await self.connected.wait()

    async def __connection_factory(self) -> None:
        logger.debug("Starting connection factory for %r", self)
        while not self.is_closed and not self._close_called:
            logger.debug("Waiting for connection close event for %r", self)
            await self._RobustConnection__connection_close_event.wait()

            if self.is_closed or self._close_called:
                return

            try:
                self.transport = None
                self.connected.clear()

                logger.debug("Connection attempt for %r", self)
                await Connection.connect(self, self._RobustConnection__connect_timeout)

                if not self._RobustConnection__fail_fast_future.done():
                    self._RobustConnection__fail_fast_future.set_result(None)

                logger.debug("Connection made on %r", self)
                self._liveness_probe.start()

            except CONNECTION_EXCEPTIONS as e:
                if not self._RobustConnection__fail_fast_future.done():
                    self._RobustConnection__fail_fast_future.set_exception(e)
                    return

                logger.warning(
                    'Connection attempt to "%s" failed: %s. '
                    "Reconnecting after %r seconds.",
                    self,
                    e,
                    self.reconnect_interval,
                )
                self._liveness_probe.stop()
            except Exception:
                logger.exception(
                    "Reconnect attempt failed %s. " "Retrying after %r seconds.",
                    self,
                    self.reconnect_interval,
                )
                self._liveness_probe.stop()

            await asyncio.sleep(self.reconnect_interval)

Что стало лучше

Полный контроль над жизненным циклом соединения, тонкая настройка prefetch, возможность реализовать любую логику подтверждения (например, отложенный ack после завершения цепочки действий). Нет лишних метаданных в теле сообщения — брокер передаёт ровно то, что отправил продюсер. Асинхронность нативная, работает на asyncio без костылей.

Проблемы

Каждая «плюшка» делалась вручную и со временем объём инфраструктурного кода разросся. Десятки строк для декларации очередей, логирование reconnect‑цикла, согласование формата trace‑заголовков между сервисами. Healthcheck, хоть и был создан вручную, требовал аккуратности: нужно было отслеживать состояние не только TCP-соединения, но и открытого канала.


Решение №3: FastStream — золотая середина

Основная идея

FastStream — надстройка над aio-pika (а также над NATS, Kafka), которая даёт декларативный стиль описания consumer’ов, lifespan‑хуки, встроенные механизмы: healthcheck-эндпоинт, OpenTelemetry-интеграция, метрики Prometheus.

По сути, это aio-pika, обёрнутая в лучшие практики, которые мы сами реализовывали руками в предыдущем решении. Механизм retry описан как пример использования middleware.

Что пошло так

  • Healthcheck из коробки — достаточно указать FastStream объект в ASGI-приложении (через AsgiFastStream), и на /health возвращается статус брокера.

  • Lifespan — менеджер контекста управляет запуском и корректной остановкой consumer’ов, повторными соединениями. Не нужно писать свои обработчики сигналов. Это касалось не только RMQ коннектов, но и например коннектов к базам данных.

  • Мониторинг и трейсинг — подключение OpenTelemetry сводится к нескольким строчкам: спаны автоматически создаются для каждого обработанного сообщения, propagate context через заголовки.

  • Декларативные middleware — проще внедрять кросс‑касательную логику (логирование, валидацию) без захламления бизнес-кода.

  • Простота конфигурации — брокер, очереди, обменники описываются через Python-декораторы и типы, нет нужды вручную управлять каналами и ack/nack.

  • Интеграция с Pydantic — Сообщение на уровне описание консьюмеров сразу пытается отвалидироваться в Pydantic модель, если это нужно.

  • Dependency Injection — Как можно увидеть в примере ниже FastStream предлагает нам возможность подключение DI как в FastAPI.

Вот во что превратилась кодовая база одной инициализации процесса:

app = AsgiFastStream(
    broker,
    # health + metrics из коробки
    asgi_routes=[
        (
            "/health",
            make_system_ping_asgi(broker, timeout=5.0, include_in_schema=False),
        ),
        ("/metrics", make_asgi_app(registry)),
    ],
    # Кастомный lifespan. Обычно здесь идёт инициализации подключений к БД
    lifespan=lifespan,
)

Было бы ещё не очень справедливо не показать, что из себя представляет broker

broker = RabbitBroker(
    settings.RMQ_URL,
    # Примеры миддлвары
    # RabbitPrometheusMiddleware из коробки для мониторинга сообщений
    # EnrichLogMiddleware и FailCatchComplexMiddleware кастомные для отслеживания логирования и ошибок 
    middlewares=(
        RabbitPrometheusMiddleware(registry=registry),
        EnrichLogMiddleware,
        FailCatchComplexMiddleware(
            ignore_routing_keys=[
                settings.RMQ_FAIL_TABLE_QUEUE,
                settings.RMQ_DASHBOARD_SETTINGS_QUEUE,
                settings.RMQ_TIMEOUT_QUEUE,
            ],
        ),
    ),
    logger=logger,
    # Кастомные парсеры и декодеры для обработки входящих сообщений
    parser=json_parser,
    decoder=decoder,
)

broker.include_router(router)

и пример как инициализируется консьюмер

from faststream import Depends
from faststream.rabbit import (
    ExchangeType,
    RabbitExchange,
    RabbitMessage,
    RabbitQueue,
    RabbitRouter,
)
from faststream.rabbit.annotations import RabbitBroker as ContextRabbitBroker

# Вместо RabbitRouter можно использовать broker.
router = RabbitRouter()
@router.subscriber(
    RabbitQueue(
        name=...,
        durable=True,
        routing_key=...,
    ),
    RabbitExchange(
        name=...,
        durable=True,
    ),
)
async def on_service_hub_message(
    message: RabbitMessage,
    # Имеется общий контекст всей системы
    broker: ContextRabbitBroker,
    # DI
    async_session=Depends(get_db_session),
) -> None:

Подключение консьюмеров простое, почти схожее с Celery, но чуть шире. Возможности FastStream на этом не заканчиваются. Если копать ещё глубже, то там можно найти документацию AsyncAPI и In-memory тесты.

Что пошло не так

  • Производительность — из-за дополнительных слоёв абстракции (middleware, автоматическая обвязка спанов, встроенные ретраи) пропускная способность ниже, чем у голого aio-pika. Это для нас лишь только в теории, поскольку поток не достигает больших значений 30-40 RPS. Справедливости ради, это не относится к минусам, поскольку бенчмарки я не проводил, и фраза “поверь мне брат” меня убедила.

  • Сложность отслеживания кодовой базы — Тут опять из-за дополнительных слоёв абстракции иногда заходишь внутрь посмотреть что там, и можно с лёгкостью потеряться.

Итоговое сравнение

Критерий

Celery

aio-pika (самописный)

FastStream

Подход

Динамические задачи

Низкоуровневый AMQP

Высокоуровневый consumer

Асинхронность

Ограниченная (gevent)

Полная (asyncio)

Полная (asyncio)

Healthcheck

Требует доп. решения

Ручная реализация

Из коробки

Retry

Встроен в задачу

Реализуется вручную

Реализуется вручную (есть пример)

Трейсинг (OTel)

Через сигналы Celery

Ручное встраивание

Из коробки

Контроль

Низкий

Максимальный

Средний (middleware)

Производительность

Умеренная

Высокая

Больше чем у Celery, ниже чем у aio-pika

Кривая входа

Низкая

Средняя (требует знаний AMQP)

Низкая (знакомо по FastAPI)


Заключение

Пройдя путь от монолитного Celery через хрупкий самописный код до сбалансированного FastStream, мы пришли к инструменту, который закрывает все эксплуатационные потребности: healthcheck, трейсинг, метрики — и при этом не требует поддерживать сотни строк инфраструктурного кода.

Да, возможно я выступаю в качестве адвоката этого инструмента, но что могу поделать, когда он так понравился? это вы ещё не увидели как стараюсь переписать всё на Rust.

Отойдя от жаргона и шуток повторю первую свою мысль: У каждой задачи свой инструмент. Мой — это FastStream.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

0%да0

0%нет0

Никто еще не голосовал. Воздержавшихся нет.