Иногда мы хотим, чтобы сервер уведомлял клиента об изменениях. Это невозможно в традиционных веб-приложениях HTTP. В традиционных веб-приложениях клиент должен установить соединение с сервером, а затем ждать ответа от сервера. Это проблема, которую решает событие, отправленное сервером. Идея, лежащая в основе события, отправляемого сервером (SEE), заключается в том, что клиент подписывается на поток обновлений, генерируемых сервером, и уведомление отправляется клиенту всякий раз, когда происходит новое событие.

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

Опрос - это метод, при котором клиент неоднократно опрашивает сервер для получения данных, при отсутствии обновлений с сервера возвращается пустой ответ. Ограничение опроса заключается в том, что дополнительный опрос создает большие накладные расходы HTTP.

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

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

Вариант использования

Давайте погрузимся в события, отправляемые сервером, путем создания уведомления в стиле Facebook. В видео ниже показано, что мы собираемся реализовать.

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

Реализация службы уведомлений

Если посмотреть на NotificationController, первая конечная точка, которую предоставляет служба, - это подписка.

Он создает экземпляр SseEmitter, сохраняет его и возвращает экземпляр клиенту. Таким образом поддерживается соединение между сервером и клиентом.

Служба уведомлений хранит экземпляр эмиттера в памяти, давайте проверим код ниже.

Он сохраняет все активные экземпляры в массиве и перед добавлением эмиттера в массив устанавливает обратные вызовы onCompletion и onTimeout, которые удаляют экземпляр эмиттера из массива.

Вторая конечная точка, которую предоставляет служба, - это уведомление об отправке.

Он просто вызывает метод pushNotification.

Давайте посмотрим на реализацию метода pushNotification.

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

Реализация клиента

Чтобы подписаться на поток событий, создайте объект из EventSource и передайте URL-адрес конечной точки подписки.

При желании вы можете прослушивать события открытия и ошибки, затем в строках 15–18 он прослушивает событие, названное с именем пользователя, вошедшего в систему, и, следовательно, отображает уведомления, которые принадлежат только зарегистрированному пользователю.

Вы можете найти полный код здесь.

Масштабируемость

Вышеупомянутое решение отлично работает для службы с одним экземпляром, но если запущено несколько экземпляров, у них нет возможности общаться друг с другом. И когда отправляется новое уведомление, некоторые подписчики могут пропустить уведомление в зависимости от того, где был отправлен запрос на отправку уведомления. Чтобы лучше понять, давайте взглянем на диаграмму ниже:

Если пользователь C отправляет запрос на подписку, и LoadBalancer перенаправляет его в службу B, а пользователь A отправляет уведомление пользователю C, но запрос попал в службу A, то пользователь C не получит уведомление, потому что экземпляр эмиттера для пользователя C хранится в Сервисе B.

Redis Pub / Sub на помощь

Чтобы решить проблему масштабируемости, мы можем полагаться на функцию Redis Pub / Sub. Экземпляр, получивший запрос на отправку уведомления, отправит сообщение в Pub / Sub и уведомит другие экземпляры, чтобы каждый мог уведомить своих подписчиков.

Реализация масштабируемого уведомления с помощью Redis Pub / Sub

Прежде чем я начну реализацию с Redis Pub / Sub, настоятельно рекомендуется взглянуть на следующее:

Как описано в предыдущем разделе, нам нужно изменить реализацию метода отправки уведомления NotificationController, при получении запроса он будет публиковать сообщение в Redis Pub / Sub. Реализация должна быть следующей:

И код для фактической отправки уведомления перемещается в прослушиватель Pub / Sub следующим образом:

Вы можете найти полный код Redis Pub / Sub здесь.

Заключение

Мы видели, что событие Server-Sent является предпочтительным решением в случае, если мы хотим отправлять обновления с сервера на клиент, и мы увидели недостатки других решений.

WebSocket - хорошее решение в случае, если между сервером и клиентом требуется полнодуплексная и двунаправленная связь.

Также мы увидели, что Redis Pub / Sub может решить проблему связи между экземплярами службы, тем самым делая службу масштабируемой.