Спроектируйте сервис уведомлений: email, push и SMS, миллионы отправок в день, ретраи, дедупликация, приоритеты.
Короткий ответ
- Приём по API — только валидация и постановка в очередь
- Очереди по каналам и приоритетам, воркеры — по провайдерам
- Ретраи с экспоненциальной задержкой и dead-letter очередь
- Идемпотентность по ключу запроса — защита от дублей
- Шаблоны, предпочтения пользователя и rate limit на получателя
- Статусы доставки — через вебхуки провайдеров в хранилище
Асинхронный конвейер: API ставит в очередь, воркеры каналов шлют через провайдеров с ретраями, идемпотентностью и DLQ.
Как сказать вслух
пример ответаДелаю сервис асинхронным: API принимает запрос, валидирует, проверяет ключ идемпотентности и кладёт сообщение в очередь — отправитель сразу получает accepted. Дальше очереди разделены по каналам и приоритетам, чтобы миллионная маркетинговая рассылка не задержала код подтверждения. Воркеры каждого канала ходят к провайдерам — для email и SMS их лучше иметь два с переключением. Неудачи ретраю с нарастающей задержкой, после нескольких попыток сообщение уходит в dead-letter очередь на разбор. Статусы доставки принимаю вебхуками от провайдеров.
Подробный ответ
Основной ответ
Архитектура: API-слой (валидация, аутентификация сервисов-отправителей, идемпотентность по client_request_id, запись в хранилище статусов) → брокер (Kafka или RabbitMQ; отдельные топики/очереди на канал и класс приоритета: transactional против bulk) → воркеры каналов (рендер шаблона, проверка предпочтений и подписок пользователя, выбор провайдера, отправка). Надёжность: at-least-once от брокера плюс идемпотентность на отправке (дедуп по ключу в Redis/БД, чтобы ретрай конвейера не продублировал SMS); ретраи с exponential backoff и джиттером на временные ошибки провайдера, circuit breaker на провайдера с автопереключением на запасного; окончательные неудачи — в DLQ с алертом. Приоритеты изолируют каналы: у транзакционных сообщений свои очереди и свой пул воркеров. Ограничения: rate limit на получателя (анти-спам), квоты провайдеров, тихие часы. Статусная модель: accepted → queued → sent → delivered/failed, обновляется вебхуками провайдеров; хранится для API-запросов и аналитики.
Ключевые моменты
- Изоляция приоритетов. Общий конвейер — классическая авария: миллион маркетинговых писем забивает очередь, и OTP-коды опаздывают. Отдельные очереди и воркеры обязательны.
- Дубликаты страшнее задержки. Два одинаковых SMS о списании — инцидент. Идемпотентность нужна на входе (ключ клиента) и на выходе (дедуп перед провайдером).
- Провайдеры ненадёжны. Таймауты, лимиты, аварии — норма: нужен circuit breaker, второй провайдер и различение временных и постоянных ошибок.
- DLQ с процессом. Dead-letter очередь без владельца и алертов — кладбище; нужен разбор, метрики и инструмент повторной отправки.
Практический контекст
Сервис уведомлений есть почти в каждой продуктовой компании, поэтому задача популярна: она проверяет работу с очередями, ретраями и идемпотентностью на жизненном материале. Интервьюер обычно давит на края: «провайдер упал на час — что с очередью?», «как не отправить дважды?», «маркетинг запускает рассылку на всю базу — что с кодами подтверждения?». Структурный рассказ по пути сообщения от API до вебхука закрывает большинство вопросов.
Частые ошибки
- Отправляют синхронно из API-запроса, завязывая отправителя на задержки провайдера
- Одна общая очередь без приоритетов — транзакционные сообщения ждут маркетинговые
- Нет идемпотентности: ретрай конвейера дублирует SMS пользователю