Архитектура отправки пуш-уведомлений
Представляем вам очередную расшифровку с VK Tech Talks | Backend. В этот раз совместно с Ильшатом Шакировым разбёремся в архитектуре отправки пуш-уведомлений.
Начнём с того, что вообще такое пуш. Пуш — это всплывающие окна на мобильном устройстве, которые нужны для мгновенного уведомления пользователя о некотором событии.
Существует три типа пушей на платформе ВКонтакте:
- События (лайвы, посты, лайки, etc.);
- Мессенджер (сообщения);
- VOIP-пуши (уведомления для звонков внутри ВКонтакте).
Немного статистики:
Рассмотрим, как работает почти любая система пуш-уведомлений.
Всё начинается с пользователя. Пользователь листает приложение, которое так или иначе достаёт push-токен (строка, однозначно идентифицирующая устройство пользователя и приложения). Этот push-токен отправляется в бэкенд VK на хранение. При возникновении какого-либо события он вместе с содержание уведомления отправляется в push-провайдер (сервер для отправки пуш-уведомлений). Задача push-провайдера — доставить push-уведомление до пользователя.
Очевидно, что контролироваться может лишь эта часть, поэтому о ней и пойдёт речь далее.
Рассмотрим то, как мы будем сохранять push-токены пользователей.
Сделаем небольшое отступление и поговорим об API VK. Если вы хотите создать мобильное приложение, которое будет использовать все возможности платформы API, то вам необходимо зарегистрировать ваше приложение на нашей платформе. После чего вы получите особый токен, который будет использоваться для «похода» в нашей API.


Посмотрим на то, что мы храним о каждом устройстве пользователя:
Что может случиться и какие пути решения?
- Могут измениться параметры устройства.
В обработчике API-метода учитываем одинаковые токены/device_id и создаем/обновляем устройства пользователя. - Пользователь может удалить приложение.
Правильно обрабатываем ошибки от push-провайдера, удаляя «протухшие» устройства пользователя. - Пользователь может не пользоваться телефоном.
Обновляем и следим за update_time устройства, своевременно удаляя его. - На устройстве может авторизоваться другой пользователь.
Храним обратный индекс token -> user и подчищаем «чужие» устройства.
Мы научились сохранять данные о пользователе. Далее необходимо сформировать пуш.
Подытожим:
- Уведомления делятся по типам msg, chat, new_post, like, etc.
- Каждому типу пуша — свой обработчик.
- На вход обработчику — устройства пользователя и данные уведомления.
- На выходе — N задач (по количеству устройств) на отправку пуша .
Поговорим о некоторых проблемах, которые мы решили.
Раньше был старый формат пушей, который заключался в индивидуальной отрисовке пуша для каждой платформы.


Пришло время для нового единого формата пушей:
- Выделение общих полей всех уведомлений;
- Построение на их основе уведомления для каждой платформы;
- Унификация работы клиентов с новым форматом пуш-уведомлений.
Посмотрим на структуру задачи, получаемую с обработчиков. Поделим её на две части: поля самой задачи и поля самого уведомления.
Сбор статистики с клиентов про пуши:
- Поле stat: “time_sent=123&log_id=234”;
- Собираются все действия с пушами: получение пуша, тап по пушу, нажатие на кнопки быстрых действий, скрытие пуша;
- Измеряется время доставки.
Поговорим про «пушилку», которая достаёт и обрабатывает сообщения с брокера.
- Демон на Go;
- 0,02 сек. на обработку одного пуша;
- Умеет отправлять в 6 пуш-провайдеров:
▪ Apple Push Notification Service (APNS)
▪ Google Cloud Messaging (GCM)
▪ Firebase Cloud Messaging (FCM)
▪ Browser
▪ Microsoft Push Notification Service (MPNS)
▪ Windows Notification Services (WNS)
Рассмотрим архитектуру самой пушилки:


Что умеет пушилка? В какие пуш-провайдеры какие пуши отправляются?
Не так давно мы узнали, что GCM/FCM поддерживают HTTP/2.
Окунёмся в историю. Раньше у Apple был свой бинарный протокол поверх TSP, который был достаточно неудобен.
Время шло, Go обновлялся и у HTTP/2 убрали некоторые проблемы. Мы решили обновиться.
Ночью всё прошло хорошо, но ближе к вечеру в наших Google-провайдерах максимальное время обработки пуша «улетела в полку». На графике это примерно 70 секунд, что равно нашим выставленным в коде тайм-аутам. Очевидно, что наши запросы просто отваливались по тайм-ауту.
Начали разбираться с этой проблемой. Залезли в процесс и увидели, что очень много времени тратится внутри реализации HTTP/2 в Go, что странно, потому что мы не писали никакой специальной поддержки для HTTP/2.
Стали копать ещё глубже.


Итак, мы научились сохранять устройство пользователя, генерировать задачу на отправку пуша, обрабатывать задачи и отправлять пуши в провайдер.
Посмотрим на то, что мы мониторим и логируем, чтобы смотреть на то, как работают наши сервисы:
Подытожим что такое сервис-пушилки:
На этом доклад подходит к концу. Полное видео трансляции вы можете посмотреть ниже:
о социальных сетях и технологиях
Автор: Сергей Котов
Корректор: Арсений Метелёв
Слайды и статистика: Ильшат Шакиров / ВКонтакте
