Наконец, обсудим как осуществляется доступ к данным, т.е. как данные доставляются потребителю.
Рассмотрим типичные паттерны взаимодействия и технологии, на базе которых они реализуются.
Паттерны взаимодействия
В других компонентах потоковой системы, мы ограничивались обсуждением паттернов передачи данных pull (“вытягивание”) и push (“проталкивание”). В случае взаимодействия с клиентом эти паттерны сами по себе могут быть недостаточными. Помимо pull и push существуют распространённые паттерны:
- Синхронизация
- RMI/RPC
- Простой обмен сообщениями
- Издатель-подписчик
Рассмотрим их по порядку.
Синхронизация
Этот паттерн подразумевает синхронизацию базы данных или хранилища, в которые записываются результаты звена анализа, и клиентом.
Общая идея в том, что при первом подключении клиент запрашивает и загружает текущий набор данных, а при последующих подключениях выгружаются только изменения. Кроме того, изменения могут проталкиваться подключённым клиентам в “почти” реальном времени.
- Достоинства
- Простой протокол
- У клиента полный набор данных
- Простой в реализации API
- Недостатки
- Набор данных может быть велик, тогда для передачи требуется большое время или быстрый канал
- Данные могут занимать значительный объём на устройстве клиента (или даже не умещаться на нём!)
- Необходимо определить стратегию объединения, описывающую, как изменения данных, произведённые клиентом, увязывать с изменениями на сервере. В частности, как разрешать конфликты.
Несмотря на недостатки, во многих случаях этот паттерн оказывается вполне приемлемым. Например, в многопользовательской мобильной игре, когда необходимо, чтобы на каждом устройстве было представлено одно и то же состояние игры и не обязательно рассматривать каждое действие игрока как отдельную транзакцию, этот паттерн может хорошо подходить.
Удалённый вызов метода (RMI) и удалённый вызов процедуры (RPC)
Этот паттерн взаимодействия построен таким образом, что при поступлении новых данных, сервер API вызывает процедуру на подключённом клиенте, когда поступают данные, представляющие интерес для клиента.
В общем случае API следит за изменениями в хранилище данных и уведомляет о них клиента посредством удалённого вызова процедур/методов. Существуют две разновидности паттерна: либо данные передаются как аргумент вызова, либо клиент только уведомляется о поступлении новых данных, и должен сам вытянуть новые данные.
- Достоинства
- Простой протокол
- Простой в реализации API
- Легко реализовать асинхронную обработку на клиенте
- Недостатки
- Затруднена обработка ошибок
- При высокой частоте обновлений, клиент может не успевать обработать удалённые вызовы
Простой обмен сообщениями
Это простая схема запрос-ответ: Клиент запрашивает данные у API, API получает данные из хранилища и отправляет их в виде ответа.
В такую схему необходимо добавить метки, позволяющие не передавать впустую одни и те же данные. Например, ответ сервера может содержать метку последнего обновления данных (версия или метка времени), а клиент в запросе – отправлять метку последнего запроса данных (аналогично).
- Достоинства
- Простой протокол
- Простой в использовании API
- API не требуется хранить состояние
- Отправляются только новые данные
- Недостатки
- Не существует механизма уведомить клиента о поступлении новых данных
- Как следствие, возможны бесполезные периодические запросы новых данных от клиента
- Если клиент долго был отключён или темп поступления данных очень высок, то объём новых данных может быть велик, и не обязательно все эти данные нужны клиенту.
Издатель-подписчик
В этом случае, клиент подписывается на некоторую тему, а API рассылает всем подписчикам сообщения об изменении/поступлении новых данных. Тема – это группировка схожих данных.
У этого паттерна есть преимущества перед другими рассмотренными, и он набирает всё большую популярность.
- Достоинства
- Легко реализовать асинхронную обработку на клиенте
- Клиенту не нужно хранить метаданные, описывающие текущее состояние
- API может оптимизировать отправку одинаковых данных нескольким клиентам
- Недостатки
- Более сложный протокол
- Более сложный в реализации API
- API должен хранить метаданные обо всех клиентах и иметь возможность распределять их между серверами в случае сбоя.
Технологии отправки данных клиентам
Рассмотрим распространённые протоколы взаимодействия с клиентами. Для каждого будем обращать внимание на следующие факторы:
- частота сообщений
- направление взаимодействия
- задержка сообщений
- эффективность
- отказоустойчивость
Веб-хуки
Веб-хуки концептуально являются аналогом механизма callback в языках программирования. Веб-хуки выполняются с помощью POST-запросов по протоколу HTTP. Клиенты часто реализуются сторонними разработчиками.
На первом шаге клиент отправляет запрос о регистрации обратного вызова, иногда этот шаг выполняется вручную через форму на сайте. Информация об обратном вызове сохраняется. После этого потоковый API вызывает все зарегистрированные хуки при поступлении новых данных или при наступлении какого-то события.
частота сообщений
Учитывая, что обновления отправляются по HTTP, будет справедливо ожидать, что высокая частота сообщений не поддерживается. Сам протокол не вводит каких-то явных ограничений, но учитывая, что протокол в основе своей текстовый и ему присущи накладные расходы, ожидать от него высокой скорости не стоит
направление взаимодействия
Взаимодействие всегда направлено от сервера к клиенту. Если необходимо что-то изменить, клиент должен перерегистрироваться. Процесс регистрации не стандартизован и полностью находится на совести разработчика API.
задержка сообщений
Задержка средняя. Во многих системах стек HTTP хорошо оптимизирован, и можно пользоваться встроенными в протокол возможностями сжатия и разбиения на части, если объём данных велик.
эффективность
С точки зрения потокового API, протокол достаточно эффективен. Единственное хранимое состояние – список конечных точек для вызова. Для отправки обновлений сервер может отправлять асинхронный POST-запрос для каждой зарегистрированной точки.
отказоустойчивость
Сам протокол не даёт никаких гарантий, вся ответственность на совести разработчика.
Веб-хуки – довольно-таки простой протокол, но видно, что для потоковой системы он подходит не слишком хорошо. Обычно этот протокол применяется в системах, где интенсивность потока сообщений мала и допустим пропуск некоторых сообщений.
HTTP long-poll
“Длинный опрос” подразумевает, что клиент устанавливает соединение с сервером, и это соединение остаётся открытым на неопределённо долгий период времени. Сервер посылает клиенту данные по мере их поступления. Соединение закрывается после получения пакета данных и открывается заново.
Клиент управляет опросом наличия изменений, открывая соединение, но за это приходится расплачиваться необходимостью держать соединение со всеми клиентами.
частота сообщений
Технически ничто не мешает посылать сообщения часто. Однако учитывая издержки на закрытие-открытие соединения и текстовую природу протокола, высокая частота может быть недостижима.
направление взаимодействия
Клиент инициирует соединение и может указать в параметрах запроса на соединение, какие обновления ему следует получать.
задержка сообщений
Как и в случае веб-хуков, средняя задержка умеренная.
эффективность
Хотя сам протокол HTTP эффективный, long-poll сложно таким назвать. Возможности сервера API оказываются ограничены числом одновременно открытых соединений. Также проблемным оказывается вопрос потери сети на клиентском устройстве: не будет ли сервер перегружен “подвисшими” соединениями, если клиент часто переключается, например, между Wi-Fi и мобильной сетью?
отказоустойчивость
Как и веб-хуки, сам протокол не даёт никаких гарантий.
Длинный HTTP-опрос немного ближе к тому, что мы ожидаем увидеть от потокового API, поскольку есть возможность контролировать поток данных со стороны клиента. Однако эффективность оставляет желать лучшего. Этот протокол приобрёл популярность в связи с распространением асинхронного программирования на JavaScript в браузере, и широко использовался в первых системах реального времени в вебе, например в веб-чатах. Также используется в клиентах, не имеющих прямого отношения к вебу, поскольку протокол HTTP достаточно универсален.
События, посылаемые сервером
Протокол разработан в 2015 году как усовершенствование HTTP long-poll. Он устраняет неэффективность, связанную с постоянным открытием и закрытием соединения клиентом. Кроме того, для устройств с ограниченными ресурсами, поддерживается прокси-сервер уведомлений, что позволяет клиенту переходить в режим ожидания, когда оно не используется, и получать сообщения от прокси.
Клиент устанавливает соединение с API, по которому сервер может отправить одно или несколько сообщений. Это делает использование сети более эффективным, но клиент всё же должен держать соединение открытым.
При использовании прокси-сервера, клиент может перейти в режим ожидания, разорвав соединение с сервером API и отправив запрос на подключение к API прокси-серверу. Прокси-сервер теперь взаимодействует с сервером API и при поступлении новых сообщений может отправлять сообщения на устройство пользователя с какой-то периодичностью, используя специфичный для устройства протокол push-уведомлений.
частота сообщений
Хотя в качестве транспортного протокола используется HTTP, частота обновлений может быть гораздо выше в силу уменьшения накладных расходов на открытие/закрытие соединений.
направление взаимодействия
Не считая начального подключения, протокол однонаправленный от сервера к клиенту
задержка сообщений
Как во всех протоколах на базе HTTP – умеренная
эффективность
Протокол настолько эффективен, насколько возможно в рамках HTTP.
отказоустойчивость
Протокол не даёт никаких гарантий. Кроме того, поскольку протокол однонаправленный, клиент никак не может подтверждать доставку сообщений в рамках протокола.
Веб-сокеты
Протокол существует с 2011 года. Это полнодуплексный протокол, основной транспортный механизм – TCP. Его поддерживают все современные браузеры для настольных и мобильных устройств. Хотя обычно он используется в вебе, для большинства популярных языков программирования имеются библиотеки поддержки WebSockets.
WebSockets интересен в том смысле, что для начального согласования используется HTTP, а затем отправляется запрос переключения протокола и происходит переход на TCP.
- Начальный обмен данными между клиентом и сервером осуществляется по протоколу HTTP. Затем сервер переключается на TCP.
- Все события посылаются клиенту по протоколу TCP.
- Клиент может управлять потоком сообщений, поскольку соединение двустороннее.
- В идеале, требуется согласование закрытия.
частота сообщений
Учитывая, что все данные передаются по TCP, частота сообщений может быть выше чем в прочих рассмотренных вариантах. Протокол в силу двунаправленности позволяет реализовать семантику управления скоростью передачи чтобы клиент не “захлебнулся”
направление взаимодействия
Двустороннее.
задержка сообщений
Низкая по сравнению с HTTP.
эффективность
Высокая.
отказоустойчивость
Сам протокол не даёт гарантий, но двунаправленность позволяет реализовать поверх него практически произвольную семантику отказоустойчивости и надёжности.
WebSockets быстро становится одним из самых распространённых протоколов в системах потоковой обработки, в первую очередь в силу эффективности и гибкости.
В случае клиентов с ограниченными ресурсами, не поддерживающих HTTP, возможно использовать протоколы Data Distribution Service (DDS) и MQ Telemetry Transport (MQTT). Оба протокола используют паттерн издатель-подписчик.
Если же возможностей DDS или MQTT недостаточно, то всегда есть вариант напрямую работать с TCP. Обратная сторона конечно в том, что придётся с нуля разрабатывать весь протокол.