Перейти к основному содержимому

Системы уведомлений

В последние годы система уведомлений стала очень популярной функцией во многих приложениях. Уведомления сообщают пользователю важную информацию: срочные новости, обновления продукта, события, предложения и многое другое. Они стали неотъемлемой частью повседневной жизни. В этой главе нужно спроектировать систему уведомлений.

Уведомление — это не только push на мобильное устройство. Мы будем рассматривать три формата: mobile push notification, SMS и email. На рисунке 1 показан пример каждого из них.

Изображение показывает три формата уведомлений: push-уведомления на смартфоне, SMS-сообщение и email-письмо.

Рисунок 1

Шаг 1 — Понять задачу и определить рамки дизайна​

Построить масштабируемую систему, которая рассылает миллионы уведомлений в день, непросто. Для этого нужно хорошо понимать всю экосистему уведомлений. Такой вопрос на интервью специально делается открытым и неоднозначным, поэтому именно кандидат должен задавать вопросы и прояснять требования.

Кандидат: Какие типы уведомлений должна поддерживать система?
Интервьюер: Push notification, SMS message и email.

Кандидат: Это система реального времени?
Интервьюер: Будем считать, что это soft real-time система. Мы хотим, чтобы пользователь получал уведомления как можно быстрее. Однако при высокой нагрузке небольшая задержка допустима.

Кандидат: Какие устройства поддерживаются?
Интервьюер: iOS, Android и laptop/desktop.

Кандидат: Что инициирует уведомления?
Интервьюер: Уведомления могут инициироваться клиентскими приложениями, а также планироваться на серверной стороне.

Кандидат: Смогут ли пользователи отказаться от уведомлений?
Интервьюер: Да, если пользователь выбрал opt-out, он больше не должен их получать.

Кандидат: Сколько уведомлений отправляется в день?
Интервьюер: 10 миллионов mobile push, 1 миллион SMS и 5 миллионов email.

Шаг 2 — Предложить высокоуровневый дизайн и согласовать его​

В этом разделе показан высокоуровневый дизайн, который поддерживает разные типы уведомлений: iOS push, Android push, SMS и email. Раздел состоит из следующих частей:

  • Разные типы уведомлений

  • Поток сбора контактной информации

  • Поток отправки и получения уведомлений

Разные типы уведомлений​

Начнем с того, как на высоком уровне работает каждый вид уведомлений.

iOS push notification​

Изображение показывает поток доставки push-уведомления для iOS: Provider отправляет данные в APNs, а затем уведомление доставляется на устройство iOS.

Рисунок 2

Чтобы отправить push-уведомление на iOS, нам в первую очередь нужны три компонента:

  • Provider. Провайдер собирает и отправляет запросы в Apple Push Notification Service (APNS). Для формирования push-уведомления он передает такие данные:

  • Device token: уникальный идентификатор для доставки push-уведомлений.

  • Payload: JSON-словарь с содержимым уведомления. Пример:

{
"aps":{
"alert":{
"title":"Game Request",
"body":"Bob wants to play chess",
"action-loc-key":"PLAY"
},
"badge":5
}
}
  • APNS: удаленный сервис Apple, который доставляет push-уведомления на iOS-устройства.

  • iOS Device: конечное устройство, получающее push-уведомления.

Android push notification​

Для Android поток похожий. Вместо APNs обычно используется Firebase Cloud Messaging (FCM).

Изображение показывает поток push-уведомления для Android: Provider отправляет данные в FCM, а затем уведомление попадает на Android-устройство.

Рисунок 3

SMS message​

Для SMS обычно используют сторонние сервисы вроде Twilio [1], Nexmo [2] и других. В основном это коммерческие сервисы.

Изображение показывает линейный поток отправки SMS: Provider передает сообщение в SMS Service, а тот доставляет его на телефон пользователя.

Рисунок 4

Email​

Хотя компании могут поднять собственные email-серверы, многие предпочитают коммерческие сервисы. Sendgrid [3] и Mailchimp [4] — одни из самых популярных решений, предлагающие более высокий delivery rate и аналитику.

Изображение показывает поток отправки email: Provider отправляет письмо в Email Service, а затем оно доставляется в email-клиент пользователя.

Рисунок 5

На рисунке 6 показан дизайн после добавления всех сторонних сервисов.

Изображение показывает группу сторонних сервисов — APNs, FCM, SMS Service и Email Service — каждый из которых доставляет уведомления на соответствующее устройство или канал.

Рисунок 6

Поток сбора контактной информации​

Чтобы отправлять уведомления, нам нужно собирать device token, номера телефонов и email-адреса. Как показано на рисунке 7, когда пользователь устанавливает приложение или регистрируется впервые, API-серверы собирают его контактные данные и сохраняют их в базе данных.

Изображение показывает пользователя, балансировщик нагрузки, API-серверы и базу данных, в которую сохраняется контактная информация при установке приложения или регистрации.

Рисунок 7

На рисунке 8 показаны упрощенные таблицы БД для хранения контактной информации. Email-адреса и телефоны хранятся в таблице user, а device token — в таблице device. У одного пользователя может быть несколько устройств, а значит, push-уведомление может быть отправлено на все его устройства.

Изображение показывает схему БД с таблицами user и device, связанными по user_id, где один пользователь может иметь много устройств.

Рисунок 8

Поток отправки и получения уведомлений​

Сначала представим начальный вариант дизайна, а затем предложим его улучшения.

Высокоуровневый дизайн​

На рисунке 9 показан дизайн, а ниже объясняется каждый компонент.

Изображение показывает несколько сервисов (Service 1, Service 2, Service N), которые отправляют уведомления в Notification System, а тот далее маршрутизирует их в APNs, FCM, SMS Service и Email Service.

Рисунок 9

Service 1 to N: это могут быть микросервисы, cron-задачи или распределенные системы, инициирующие события отправки уведомлений. Например, billing service рассылает email-напоминания об оплате, а интернет-магазин отправляет SMS, что посылка будет доставлена завтра.

Notification system: центральный компонент отправки и получения уведомлений. Начнем с простого варианта, в котором используется только один notification server. Он предоставляет API для сервисов 1..N и формирует payload для сторонних сервисов.

Third-party services: отвечают за доставку уведомлений пользователям. При интеграции с ними важно уделять внимание extensibility. Хорошая extensibility означает, что систему можно гибко расширять, легко подключая и отключая сторонние сервисы. Еще один важный момент — сторонний сервис может быть недоступен в некоторых странах или перестать быть доступным в будущем. Например, FCM недоступен в Китае, поэтому там используют альтернативы вроде Jpush и PushY.

iOS, Android, SMS, Email: пользователи получают уведомления на своих устройствах.

У такого дизайна есть три проблемы:

  • Single point of failure (SPOF): один notification server означает единую точку отказа.

  • Система плохо масштабируется: один сервер обрабатывает все, что связано с уведомлениями, и становится трудно независимо масштабировать БД, кэши и разные компоненты обработки.

  • Узкое место по производительности: обработка и отправка уведомлений могут требовать много ресурсов. Например, сборка HTML-писем и ожидание ответа от third-party services занимают время. Если все свалено в одну систему, она может перегружаться, особенно в пиковые часы.

Улучшенный высокоуровневый дизайн​

После того как мы перечислили проблемы исходного варианта, улучшим дизайн:

  • Вынесем базу данных и кэш из notification server.

  • Добавим больше notification server и настроим автоматическое горизонтальное масштабирование.

  • Введем message queues, чтобы ослабить связность компонентов.

На рисунке 10 показан улучшенный высокоуровневый дизайн.

Изображение показывает notification servers, кэш, базу данных, отдельные очереди для iOS, Android, SMS и Email, worker-серверы и сторонние сервисы доставки.

Рисунок 10

Эту схему удобнее читать слева направо.

Service 1 to N: это разные сервисы, которые отправляют уведомления через API, предоставленные notification servers.

Notification servers: выполняют следующие функции:

  • Предоставляют API для отправки уведомлений. Эти API доступны только внутри системы или проверенным клиентам, чтобы защититься от спама.

  • Выполняют базовую валидацию email, телефонов и т. д.

  • Запрашивают из базы данных или кэша информацию, необходимую для рендеринга уведомления.

  • Кладут данные уведомлений в message queues для параллельной обработки.

Пример API для отправки email:

POST https://api.example.com/v/sms/send

Request body:

{
"to":[
{
"user_id":123456
}
],
"from":{
"email":"from_address@example.com"
},
"subject":"Hello World!",
"content":[
{
"type":"text/plain",
"value":"Hello, World!"
}
]
}

Cache: в кэше хранятся user info, device info и шаблоны уведомлений.

DB: хранит данные о пользователях, уведомлениях, настройках и т. д.

Message queues: снимают жесткие зависимости между компонентами. Очереди также выступают в роли буфера, когда нужно отправить большие объемы уведомлений. Для каждого типа уведомлений используется отдельная очередь, поэтому сбой в одном стороннем сервисе не влияет на другие типы уведомлений.

Workers: набор серверов, которые читают события уведомлений из очередей и отправляют их в соответствующие third-party services.

Third-party services: уже были описаны в исходном дизайне.

iOS, Android, SMS, Email: тоже уже были описаны выше.

Теперь посмотрим, как все компоненты работают вместе:

  1. Один из сервисов вызывает API notification servers для отправки уведомления.

  2. Notification servers получают метаданные: user info, device token и notification settings — из кэша или базы данных.

  3. Событие уведомления отправляется в соответствующую очередь. Например, iOS push notification попадает в iOS PN queue.

  4. Workers считывают события из message queues.

  5. Workers отправляют уведомления в third-party services.

  6. Third-party services доставляют уведомления на устройства пользователей.

Шаг 3 — Детальный разбор дизайна​

В высокоуровневом дизайне мы обсудили разные типы уведомлений, поток сбора контактной информации и поток отправки/получения уведомлений. Теперь подробно рассмотрим:

  • Надежность

  • Дополнительные компоненты и важные соображения: notification template, notification settings, rate limiting, retry mechanism, security в push notifications, мониторинг очередей и event tracking

  • Обновленный дизайн

Надежность​

При проектировании системы уведомлений в распределенной среде нужно ответить на несколько важных вопросов о надежности.

Как избежать потери данных?​

Одно из ключевых требований к системе уведомлений — она не должна терять данные. Уведомления можно задерживать или переупорядочивать, но нельзя терять. Чтобы выполнить это требование, система сохраняет данные уведомлений в базе данных и реализует механизм повторной отправки. Для долговременного хранения используется notification log database, как показано на рисунке 11.

Изображение показывает очередь iOS push-уведомлений, worker-кластер, APNs и базу Notification log, в которую записывается история доставки.

Рисунок 11

Получит ли пользователь уведомление ровно один раз?​

Короткий ответ — нет. Хотя в большинстве случаев уведомление действительно доставляется ровно один раз, распределенная природа системы может приводить к дубликатам. Чтобы уменьшить их число, мы добавляем dedupe mechanism и аккуратно обрабатываем каждый сценарий отказа.

Простейшая dedupe-логика такова: когда приходит новое notification event, мы проверяем, видели ли мы уже этот event ID. Если да, событие отбрасывается. Если нет — уведомление отправляется. Подробнее о том, почему exactly-once delivery недостижима, можно прочитать в материале [5].

Дополнительные компоненты и соображения​

Мы уже обсудили, как собирать контактные данные пользователей, отправлять и получать уведомления. Но полноценная система уведомлений этим не ограничивается. Ниже разберем дополнительные части: повторное использование шаблонов, пользовательские настройки, event tracking, мониторинг системы, rate limiting и т. д.

Notification template​

Крупная система уведомлений рассылает миллионы уведомлений в день, и многие из них имеют похожий формат. Notification templates позволяют не собирать каждое уведомление с нуля. Шаблон уведомления — это предформатированная заготовка, которая настраивается через параметры, стили, tracking links и т. д. Пример push notification template:

BODY:
You dreamed of it. We dared it. [ITEM NAME] is back — only until [DATE].

CTA:
Order Now. Or, Save My [ITEM NAME]

Плюсы шаблонов:

  • Единый и согласованный формат

  • Меньше вероятность ошибок

  • Экономия времени

Notification setting​

Обычно пользователи и так получают слишком много уведомлений каждый день и быстро устают от них. Поэтому многие сайты и приложения дают тонкую настройку каналов уведомлений. Эта информация хранится в таблице notification settings со следующими полями:

user_id bigInt
channel varchar # push notification, email or SMS
opt_in boolean # opt-in to receive notification

Перед отправкой любого уведомления система сначала проверяет, подписан ли пользователь на этот тип уведомлений.

Rate limiting​

Чтобы не перегружать пользователей слишком большим количеством уведомлений, можно ограничивать число уведомлений, которое они получают. Это важно, потому что в противном случае пользователи могут просто полностью отключить уведомления.

Retry mechanism​

Если third-party service не смог отправить уведомление, оно возвращается в message queue для повторной попытки. Если проблема сохраняется, разработчикам отправляется alert.

Безопасность push-уведомлений​

Для iOS- и Android-приложений для защиты API push-уведомлений используются appKey и appSecret [6]. Только аутентифицированные или верифицированные клиенты могут отправлять push через наши API.

Мониторинг очередей уведомлений​

Одна из ключевых метрик — общее количество уведомлений в очередях. Если оно слишком велико, значит workers не успевают обрабатывать события достаточно быстро. Чтобы не допустить задержки в доставке, нужно увеличивать число workers. На рисунке 12 показан пример графика количества queued messages.

Изображение показывает линейный график количества сообщений в очереди во времени.

Рисунок 12

Event tracking​

Метрики уведомлений — например open rate, click rate и engagement — очень важны для понимания поведения пользователей. Analytics service отвечает за сбор таких событий. Обычно требуется тесная интеграция notification system с analytics service. На рисунке 13 показан пример событий, которые можно отслеживать.

Изображение показывает диаграмму состояний уведомления: start, pending, sent, deliver, click, unsubscribe и error.

Рисунок 13

Обновленный дизайн​

Если собрать все вместе, получится обновленный дизайн notification system на рисунке 14.

Изображение показывает обновленную архитектуру системы уведомлений с authentication, rate limiting, workers, APNs, аналитикой, шаблонами, логом уведомлений, кэшем и базой данных.

Рисунок 14

По сравнению с предыдущим вариантом здесь появилось много новых компонентов:

  • Notification servers получили еще две критически важные функции: authentication и rate limiting.

  • Мы добавили retry mechanism для обработки сбоев. Если системе не удалось отправить уведомление, оно возвращается в очередь сообщений, а workers пробуют снова ограниченное число раз.

  • Notification templates обеспечивают единообразный и эффективный процесс создания уведомлений.

  • Monitoring и tracking помогают контролировать здоровье системы и находить будущие улучшения.

Шаг 4 — Подведем итоги​

Уведомления незаменимы, потому что держат нас в курсе важной информации. Это может быть push о любимом фильме на Netflix, email о скидке на новые товары или сообщение с подтверждением оплаты в интернет-магазине.

В этой главе мы описали дизайн масштабируемой системы уведомлений, поддерживающей несколько форматов: push notification, SMS и email. Мы использовали message queues, чтобы ослабить связи между компонентами системы.

Помимо высокоуровневого дизайна мы глубже разобрали ряд дополнительных компонентов и оптимизаций:

  • Reliability: предложили надежный retry mechanism для снижения числа сбоев.

  • Security: используем пару AppKey/AppSecret, чтобы только проверенные клиенты могли отправлять уведомления.

  • Tracking и monitoring: добавляем их на разных этапах потока уведомлений, чтобы собирать важные метрики.

  • Respect user settings: если пользователь выбрал opt-out, система сначала проверяет его настройки и только потом решает, отправлять ли уведомление.

  • Rate limiting: пользователи оценят ограничение частоты уведомлений.

Поздравляем, вы дошли до этого места! Можно похлопать себя по плечу. Отличная работа!

Справочные материалы​

[1] Twilio SMS: https://www.twilio.com/sms

[2] Nexmo SMS: https://www.nexmo.com/products/sms

[3] Sendgrid: https://sendgrid.com/

[4] Mailchimp: https://mailchimp.com/

[5] You Cannot Have Exactly-Once Delivery:
https://bravenewgeek.com/you-cannot-have-exactly-once-delivery/

[6] Security in Push Notifications:
https://cloud.ibm.com/docs/event-notifications?topic=event-notifications-en-push-apns

[7] Key metrics for RabbitMQ monitoring:
https://www.datadoghq.com/blog/rabbitmq-monitoring/