> Как реализовать приоритеты в микросервисе рассылки уведомлений (Python)
Уровень: senior · Роль: backend · Язык: Python · Категория: Технические вопросы
Компании: Международный аэропорт Шереметьево
Стек: Python
> Пример ответа
Короткий ответ
Приоритизация в микросервисе рассылки реализуется через очередь сообщений с несколькими уровнями (например, RabbitMQ с несколькими exchange/queue или Kafka с партициями по приоритету) и пул воркеров, которые сначала обрабатывают сообщения с более высоким приоритетом. Дополнительно можно использовать weighted round-robin или отдельные consumer-группы для каждого уровня. Ключевой момент - не блокировать высокоприоритетные сообщения долгими low-priority задачами, поэтому часто применяют отдельные пулы воркеров или preemption.
Подробное объяснение
Основная задача - гарантировать, что уведомления с высоким приоритетом (например, SMS-коды подтверждения) обрабатываются раньше, чем массовые маркетинговые рассылки. Есть несколько подходов:
-
Отдельные очереди по приоритету: создаётся N очередей (например,
notifications.high,notifications.medium,notifications.low). Consumer подписывается на все очереди, но сначала полностью вычитывает high, потом medium, потом low. Минус - при постоянном потоке high-сообщений low может голодать. -
Одна очередь с приоритетом: RabbitMQ поддерживает
x-max-priorityна уровне очереди - сообщения сортируются внутри очереди по полюpriority. Просто, но есть ограничение: приоритет работает только в пределах одного consumer, и нет гарантии строгого порядка при нескольких consumer. -
Kafka с партициями: каждая партиция - отдельный приоритет. Consumer-группа читает партиции в порядке приоритета, но нужно аккуратно управлять количеством партиций и consumer'ов.
-
Weighted fair queueing: реализуется на уровне воркера - из каждой очереди берётся пропорциональное количество сообщений (например, 10 high, 5 medium, 1 low за цикл).
-
Preemption: если high-сообщение появилось, воркер прерывает текущую low-задачу (если она не в транзакции) и берёт high. Сложно реализовать, обычно не нужно.
Для Python-стека типичный выбор - RabbitMQ с несколькими очередями или Celery с task_priority (но Celery использует одну очередь с сортировкой, что менее эффективно при больших объёмах).
На практике
На практике чаще всего используют комбинацию: отдельные очереди для критичных типов уведомлений (SMS, push) и одна общая очередь для email-рассылок. Воркеры настраиваются так: пул из 2-3 воркеров слушает только high-очередь, остальные - medium/low. Это гарантирует, что high-уведомления не ждут, даже если low-очередь забита.
Дополнительно стоит добавить:
- метрики времени обработки по приоритетам;
- алерты на задержку high-сообщений;
- dead-letter queue для упавших сообщений с сохранением приоритета.
Пример кода
Пример на Python с RabbitMQ (pika) и несколькими очередями:
PYTHONimport pikaimport threadingQUEUES = [("notifications.high", 10), # (queue_name, weight)("notifications.medium", 5),("notifications.low", 1),]def process_message(ch, method, properties, body):print(f"Processing {method.routing_key}: {body}")ch.basic_ack(delivery_tag=method.delivery_tag)def worker(connection_params):connection = pika.BlockingConnection(connection_params)channel = connection.channel()for queue_name, _ in QUEUES:channel.queue_declare(queue=queue_name, durable=True)channel.basic_consume(queue=queue_name, on_message_callback=process_message)# Простой weighted round-robin: читаем из high чащеwhile True:for queue_name, weight in QUEUES:for _ in range(weight):method, properties, body = channel.basic_get(queue=queue_name, auto_ack=False)if method:process_message(channel, method, properties, body)else:break# Небольшая пауза, чтобы не крутить пустой циклconnection.sleep(0.1)# Запуск нескольких воркеровfor _ in range(3):t = threading.Thread(target=worker, args=(pika.ConnectionParameters("localhost"),))t.start()
Как отвечать на собеседовании
Начни с уточнения требований: какой объём, какие типы уведомлений, нужна ли строгая гарантия порядка. Затем опиши два-три подхода, сравни trade-off'ы. Обязательно упомяни проблему голодания low-приоритетных сообщений и способы её решения (weighted fair queueing, отдельные пулы). Покажи, что понимаешь ограничения инструментов: RabbitMQ priority - это сортировка внутри очереди, а не глобальный приоритет; Kafka - партиции, но consumer'ы читают их параллельно.
Что проверяет интервьюер
- Понимание распределённых очередей и их ограничений.
- Умение проектировать отказоустойчивые системы (dead-letter, метрики).
- Способность видеть trade-off между простотой и гарантиями доставки.
- Знание конкретных инструментов (RabbitMQ, Kafka, Celery) и их особенностей.
Типичные ошибки
- Предложение использовать одну очередь с приоритетом без учёта того, что при нескольких consumer'ах порядок не гарантируется.
- Игнорирование проблемы голодания low-приоритетных сообщений.
- Отсутствие плана на случай переполнения high-очереди (нужен backpressure или масштабирование воркеров).
- Забывают про idempotency и retry: при повторной отправке приоритет должен сохраняться.
- Смешивание приоритета с типом уведомления - это разные вещи: тип определяет канал доставки, приоритет - срочность.
> Похожие задачи по Python
Откуда брать данные для уведомлений
Как структурировать уведомления
Был ли опыт работы с инцидентами на продакшене и как решал баги
Что такое линтеры и форматтеры и как с ними работать
> Похожие задачи по backend
Откуда брать данные для уведомлений
Как структурировать уведомления
Был ли опыт работы с инцидентами на продакшене и как решал баги
Что такое линтеры и форматтеры и как с ними работать
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью