Для того чтобы пользователи, находясь офлайн, узнавали о событиях на сайте, мы создали специальную систему уведомлений. В её задачи входит аккумулировать события для пользователя и в нужный момент сообщать о них через доступные каналы связи, такие как электронная почта и push-уведомления на смартфоны.
Как организовано хранение событий? О каких событиях приходят уведомления? В какой момент они отправляются и по какому принципу? Сегодня мы постараемся ответить на все эти и другие вопросы.
Статья дает общее описание архитектуры системы с небольшими техническими подробностями и будет интересна тем, кто только собирается или уже каким-то способом уведомляет своих пользователей обо всём новом, что произошло за время их отсутствия на сайте (в приложении, сервисе и т.п.)
Какие бывают события?
Все события нашего сайта порождаются действиями одних пользователей по отношению к другим. Найдя симпатичного человека, вы захотите посмотреть его анкету подробнее, тем самым создав событие «Посещение профиля». Если его профиль показался вам интересным, вполне вероятно, что вы захотите написать ему, таким образом создав событие «Новое сообщение». Вы можете добавить его в избранное, а также изъявить желание встретиться с ним. Если он тоже захочет встретиться с вами, то для каждого из пользователей случится событие «Взаимная симпатия».
Оценивая фотографии других пользователей, а также оставляя комментарии, вы будете создавать соответствующие события.
Находясь на сайте, пользователь видит новые события в виде цифр в красных кружках возле каждого раздела, содержащего контакты. Если же его нет сейчас на сайте, то обо всех обновлениях в разделах он сможет получать уведомления.
Как хранятся события?
Чтобы не слать слишком много уведомлений пользователям, мы отправляем уведомление не на каждое событие, а группируем их в течение определённого периода, к примеру, за первые полчаса отсутствия пользователя на сайте. После первой отправки мы опять ждём и, если появились новые события, осуществляем отправку. Однако, некоторые особо важные уведомления могут отправляться вне очереди.
Для агрегации событий нами была разработана специальная СУБД ― демон на Си, который умеет:
- Хранить массив событий для каждого пользователя. Каждое событие характеризуется идентификатором отправителя, числовым типом (1 ― сообщения, 2 ― посетители и т.п.) и произвольной строкой данных, в которую можно передать дополнительную информацию, например в виде сериализованного массива.
- Следить за временем, периодами, тайм-аутами и по запросу отдавать данные, готовые к отправке. Как только подходит время, демон отдаёт все накопленные события для очередного пользователя.
- Всё общение с СУБД осуществляется по RPC-like протоколу, который передает данные, запакованные с помощью Google Protocol Buffers.
В каждом из двух наших дата-центров запущено по четыре таких демона, чтобы без проблем справляться с нагрузкой, которая в пиках достигает 25 тысяч запросов в секунду, а также минимизировать время простоя в случае, если требуется перезагрузить/обновить демон, или в случае падения. Нагрузка между ними распределяется по простому принципу:
user_id % 4 = <номер демона>
Таким образом, события для каждого пользователя всегда «живут» в одной и той же базе. Данные пользователей хранятся не только в памяти, но и сохраняются на диск, поэтому уведомления не теряются. Если потребуется изменить шардинг и добавить ещё несколько демонов, то демоны «гасятся», данные перемещаются нужным образом между хранилищами и при запуске загружаются в память. Это крайне редкая ситуация ― за всё время нам потребовалось делать такое всего лишь раз.
По каким каналам происходит доставка?
Для того чтобы оповещать пользователей о новых событиях, мы используем электронную почту, а также iOS и Android push-уведомления.
Стоит немного сказать о том, что такое push-уведомления. Каждое приложение для iOS имеет возможность присылать вам уведомления, даже когда оно не запущено (конечно, если вы это не запретите). Доставка уведомлений в таком случае осуществляется через сервера Apple, а не напрямую. Когда вы устанавливаете приложение на свой iPhone, Apple сообщает разработчику идентификатор установленного приложения в своей системе, который мы используем как адрес для отправки сообщений. Такая же система существует и для Android-смартфонов, но доставка уже осуществляется через сервера Google.
Если у вас несколько устройств, на которых установлено наше приложение, то уведомление отправится на каждое из них.
Как формируются уведомления?
Для каждого из используемых нами каналов имеется собственный механизм формирования уведомлений.
Общая логика такова, что есть уведомления, содержащие информацию только об одном типе событий (2 посетителя, 5 сообщений и т.п.), и есть групповые уведомления (2 посетителя и 1 сообщение; 2 сообщения и 1 взаимная симпатия и т.п.). Для электронных писем существует несколько отдельных шаблонов о новых сообщениях, посетителях и т.п. плюс шаблон группового письма, в котором каждое событие представлено отдельной строкой.
Для push-уведомлений мы составили тексты на каждый отдельный тип событий в нескольких вариантах. Поскольку в уведомление нельзя вместить много информации, то для групп событий мы выбрали несколько базовых комбинаций (к примеру, посетители + сообщения, сообщения + взаимные симпатии), для которых и написали варианты текстов.
- Пример «одиночного» уведомления в нескольких вариантах:
- Вариант 1: У вас <число> новых сообщений от девушек!
- Вариант 2: Девушки написали вам <число> новых сообщений!
- Пример «группового» уведомления:
- У вас <число> сообщений, а также новые люди хотят с вами встретиться! Узнайте, кто...
Уведомления для iOS отличаются от Android тем, что последние могут иметь заголовок, текст и картинку, а первые только текст, но большей длины.
Как устроена архитектура системы?
С одной стороны, во время использования сайта пользователи генерируют события, которые сразу же добавляются в БД. На графике можно увидеть соотношение событий за 24 часа в одном из двух наших дата-центров (точные цифры на графиках попросили не публиковать):
С другой стороны, на нескольких серверах непрерывно запущен php-скрипт, который постоянно опрашивает БД, есть ли уведомления, время отправки которых уже подошло. Ниже можно увидеть график количества пользователей, для которых готовы уведомления:
Если нужно отправить уведомление на смартфон, то запись в БД помечается специальным флагом и откладывается на 10 минут. Если в часовом поясе пользователя сейчас ночь, то мы отправим уведомление с параметром «Без звука», чтобы не нарушать сон пользователя.
Если пользователь не зашел на сайт в течение 10 минут, тогда мы формируем и отправляем письмо-дайджест, содержащее информацию обо всех накопившихся событиях. После этого отправленные уведомления удаляются из БД, а мы вновь ожидаем новые события.
При каждом запросе страницы от зарегистрированного пользователя мы посылаем команду в БД для очистки всех его событий. Бывает так, что пользователь попал на промо-страницу и, не увидев ничего нового, ушёл с сайта. На таких страницах, где видны не все счетчики новых событий, мы указываем, какие конкретно события нужно чистить.
Когда приходит время очередной отправки, а пользователь всё ещё имеет статус «онлайн», мы помечаем это особым флагом и откладываем запись в БД на некоторое время, ожидая, что он либо продолжит активность на сайте, либо окажется в статусе «офлайн».
Что значит «онлайн»?
Как определить, что пользователь сейчас на сайте, недавно был или уже «офлайн»? Эти понятия достаточно субъективны и подобраны опытным путем.
Пользователи могут проявлять активность на сайте и в наших мобильных приложениях. Для того чтобы хранить время последней активности, мы используем ещё одну СУБД собственной разработки ― Last Access. Этот демон «из коробки» вычисляет статус «офлайн» на основе всех данных активности пользователя: спустя 30 минут после последнего действия пользователь окончательно перестает быть «онлайн».
Для того чтобы отправлять уведомления, используется более сложная логика определения онлайн-статуса. Сначала мы проверяем Last Access: если он сообщает, что статус пользователя сменился на «офлайн», то можно смело осуществлять отправку, т.к. уже прошло достаточно много времени. В противном случае для сайта и приложений используется следующий алгоритм: если человек пользуется сайтом, то для отправки должно пройти 15 минут. Если же используется мобильное приложение и прошло менее 15 минут, то мы просто проверяем, есть ли соединение с запущенным приложением, и если нет, то отсылаем уведомление.
Описанная система удобно и ненавязчиво сообщает пользователям о том, что нового для них происходит на сайте. Мы постоянно дорабатываем и улучшаем её, чтобы она была действительно полезной и нужной нашим многочисленным пользователям.
Александр Treg Трегер, разработчик.
Автор: Treg