Push-уведомления в REST API на примере системы Таргет Mail.Ru
«Ну, а здесь, знаешь ли, приходится бежать со всех ног, чтобы только остаться на том же месте, а чтобы попасть в другое место, нужно бежать вдвое быстрее«Льюис Кэрролл, «Алиса в Зазеркалье»
Недавно мы в Таргете Mail.Ru реализовали систему push-уведомлений. Грамотное использование очередей задач позволяет реализовать быструю систему доставки уведомлений. В этом посте я расскажу о применении и реализации этой модели в нашем сервисе.
Можно выделить две основные модели взаимодействия пользователя с сервисом: pull и push. Рассмотрим их применительно к задаче организации уведомлений.Pull-модель — способ взаимодействия пользователя с сервисом, при котором пользователь инициирует запрос к сервису и синхронно получает на него ответ. Если результат не готов, пользователь вынужден периодически опрашивать сервис, пока не получит интересующие его результаты. Преимущество этого способа — это простота реализации как клиента, так и сервера за счет синхронности выполняемого алгоритма. А недостаток — создание излишней нагрузки как на стороне сервиса, так и на стороне клиента. Чтобы сохранить актуальность состояния объектов в своей системе, клиент вынужден с высокой частотой делать запросы к сервису.
Push-модель — способ взаимодействия пользователя с сервисом, при котором сам сервис доставляет данные пользователю на заранее заданный адрес. Преимущество: отсутствие паразитной нагрузки — запрос отправляется тогда и только тогда, когда в системе произошли какие-то изменения. Недостаток: большая, по сравнению с pull-моделью, сложность реализации как следствие асинхронности процессов.
Таргет Mail.Ru — система показа таргетированной рекламы на проектах Mail.Ru Group. Его API позволяет работать с рекламными кампаниями и объявлениями, получать различную статистику. В подобных системах push-уведомления находят все более широкое применение.Пример №1Пользователь добавляет новое рекламное объявление. Чтобы начать показываться, объявление должно пройти предварительную модерацию. При использовании традиционной pull-модели пользователь был бы вынужден периодически опрашивать сервис. С push-моделью пользователь автоматически получает уведомление об изменении статуса объявления.
Пример №2Часто API используется агентствами, автоматизирующими работу сразу нескольких (часто десятков и сотен) клиентов. Наличие системы push-уведомлений позволяет контролировать все манипуляции, производимые с данными их аккаунтов. Также на одно событие может быть зарегистрировано сразу несколько подписчиков.
В API Таргета Mail.Ru клиенты имеют возможность подписываться на изменения объектов системы — кампаний и рекламных объявлений. Наше API следует идеологии REST. Подписки на push-уведомления отлично в нее вписываются: «подписка» является ресурсом, который можно создать, получить и удалить.Все подписки текущего пользователя:
GET /api/v2/subscriptions.json HTTP/1.1 Host: target.mail.ru Создание новой подписки: POST /api/v2/subscriptions.json HTTP/1.1 Host: target.mail.ru
{
«callback_url»: «http://target-api-client.org/subscription_callback.json»,
«resource»: «CAMPAIGN»,
«resource_id»: 123
}
При подписке указывается URL, на который при любом изменении ресурса будет отправлен POST-запрос с уведомлением:
{
«created»:»2014–06–02 18:23:29.797499»,
«diff»: {
«name»: {
»+++»: «Новое название»,
»---»: «Старое название»
},
«updated»: {
»+++»:»2014–06–02 18:23:29»,
»---»:»2014–06–02 18:21:58»
}
},
«id»:»07c0810ac51c47c98e001b1e91c94ba4»,
«resource»: «CAMPAIGN»,
«resource_id»: 86461
}
Удаление подписки:
DELETE /api/v2/subscriptions/
Для синхронизации основного потока исполнения и обработчиков используется еще одна внутренняя очередь — gevent.queue. Каждый обработчик принимает задачу, взятую из очереди, извлекает из нее url, отправляет на него уведомление, помещает задачу во внутреннюю очередь и завершается. После раздачи новых задач демон разбирает внутреннюю очередь с выполненными задачами, отправляет подтверждения их выполнения в Tarantool Queue и погружается в непродолжительный сон.
Общая схема алгоритма выглядит так (python):
queue = Queue (…) # соединяемся с сервером очереди задач
tube = queue.tube (…) # получаем очередь
worker_pool = Pool (…) # создаем пул обработчиков
processed_task_queue = gevent_queue.Queue () # внутренняя очередь; складываем в нее обработанные задачи
while run_application: # запускаем бесконечный цикл обработки задач free_workers_count = worker_pool.free_count ()
for number in xrange (free_workers_count): # проверяем, можно ли запустить еще обработчиков task = tube.take (…) # берем задачу из очереди if task: worker = Greenlet (notification_worker, task, …) # создаем обработчик worker_pool.add (worker) # добавляем в пул worker.start () # и запускаем. Он выполнится асинхронно по отношению в основному потоку выполнения done_with_processed_tasks (processed_task_queue) # закрываем выполненные задачи sleep (…) # заслуженный отдых Любая система имеет свои особенности и ограничения. Особенности обработки приходящих push-уведомлений вытекают из особенностей работы с очередями задач.Проблема: одно и то же сообщение может прийти более одного раза.
Возможное решение: каждое сообщение имеет свой уникальный идентификатор. Если повторная обработка сообщения не желательна, можно хранить список идентификаторов уже обработанных сообщений и проверять идентификатор нового сообщения на принадлежность к списку.
Проблема: порядок прихода сообщений не детерминирован, сообщения могут приходить не в том порядке, в котором были отправлены.
Возможное решение: каждое сообщение хранит метку времени своего создания. Можно сравнить ее со временем последней модификации объекта в своей системе и не применять изменения, если сообщение старее.
Мы выбрали именно push-модель, потому что она позволяет значительно сократить время доставки изменений до систем пользователей нашего API. Также немаловажным фактором стало меньшее количество «паразитной» нагрузки на наш сервис. По результатам нашего опыта реализации этой модели мы решили поделиться теми how-to, которые нам показались наиболее полезными.Расскажите, как вы используете push-модель в своей разработке?
Другие API, поддерживающие push-уведомления
https://developers.google.com/google-apps/calendar/v3/push — push-уведомления в Google Calendar.https://developers.google.com/drive/web/push — push-уведомления в Google Drive.