Нотификации через RSocket в Альфа-Онлайн: от концепции до запуска в продакшн

Однажды мы решали задачу разработки системы нотификаций для Альфа-Онлайн — мобильного банка в вебе. Нам было необходимо разработать инструмент по отправке сообщений (нотификаций) со стороны сервера в браузер в любой момент времени, пока клиент онлайн. На стороне Альфа-Онлайн каждый процесс самостоятельно определит дальнейшую судьбу сообщения.

После анализа различных вариантов, мы остановились на WebSocket в связке с RSocket.

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

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

Поэтому, когда мы закончили проект, то решили восполнить (или попытаться) этот пробел в русскоязычной среде на реальном примере. Надеемся, что эта статья поможет вам разобраться в применении RSocket over WebSocket и сократит время на погружение:) 

b8a778b12b1589c2d322df5ac037ddc6.PNG

Немного о постановке задачи

Суть требований заключается в том, что:  

  • Необходимо разработать инструмент по отправке сообщений (нотификаций) со стороны сервера в real-time, т.е. мы не знаем, когда поступит нотификация, и должны принять её в любой момент.

  • Обработать нотификацию на уровне клиента можно по-разному, в зависимости от процесса.

  • Браузерный web-push не используем, так как на него дают согласие далеко не все пользователи и не для всех процессов он может потребоваться. Иногда достаточно принять сообщение, а уже то, как этот триггер будет обрабатываться на фронте — дело бизнес-процесса.

  • Нотификации должны поступать от инициатора без задержек, так как клиентская сессия в веб-приложениях длится недолго. К тому же в некоторых бизнес-процессах отсутствие задержек имеет значение.

Нотификация в контексте статьи — это сообщение, отправляемое клиенту со стороны сервера. Нотификацией в вебе может быть, например, опрос от системы сбора обратной связи Voice of Customer. Опрос может прийти клиенту в любое время после совершения операции. 

Технические ограничения:

  • Время открытого WebSocket-соединения ограничено тайм-аутом соединения на стороне nginx.

  • Соединения одного клиента могут быть установлены с несколькими инстансами сервера.

  • Наличие множества прокси и гейтвеев в enterprise-инфраструктуре.

  • Необходимо использовать готовые библиотеки без низкоуровневой разработки логики работы с WebSocket .

  • Решение должно быть масштабируемым.

  • Фреймворки должны быть реактивными.

Немного о WebSocket и RSocket

WebSocket — это протокол связи, который обеспечивает двустороннюю передачу данных между клиентом и сервером через постоянное соединение. Он позволяет установить постоянное и эффективное соединение между веб-браузером и сервером, что отличает его от традиционного подхода, основанного на запросах и ответах.

RSocket — это двусторонний протокол передачи сообщений, который был разработан для удовлетворения потребностей современных распределенных систем и микросервисных архитектур. Протокол RSocket разработан для работы поверх различных транспортных слоев, таких как TCP, WebSocket и Aeron, что обеспечивает его гибкость и широкий спектр применения. Он способен автоматически восстанавливать соединение после сбоев, что критически важно для обеспечения надежности системы нотификаций.

RSocket поверх WebSocket удовлетворяют нашим требованиям:

Двусторонняя связь. RSocket обеспечивает двустороннюю коммуникацию между клиентом и сервером. Это означает, что и клиент, и сервер могут отправлять сообщения друг другу в любое время. Иногда процессы могут потребовать ответа на нотификацию.

Большая гибкость:  

  • RSocket предлагает различные типы взаимодействия, такие как запрос-ответ, стримы, fire-and-forget сообщения т.д. При этом во всех взаимодействиях учитываются нюансы работы WebSocket, что облегчает работу. Это позволяет выбрать наиболее подходящий тип коммуникации для конкретного случая и добавлять новые способы взаимодействия в случае необходимости. Также это исключает поддержку и обновления низкоуровневых механизмов.

  • RSocket имеет репозитории на популярных языках программирования, что делает его совместимым с различными стеками приложений, в том числе с нашим.

Отказоустойчивость. RSocket имеет встроенную поддержку переподключения. Если происходит сбой связи или сервер временно недоступен, RSocket может автоматически переподключиться и продолжить обмен сообщениями. Во многих других протоколах такая возможность отсутствует.

Масштабируемость. RSocket разработан с учётом масштабируемости и производительности. Он может эффективно обрабатывать большое количество одновременных соединений и сообщений, что делает его отличным выбором для современных распределенных систем. 

Архитектура решения

Такая.

b15a5fc1c362a04a3cc8283704e8e07d.png

На схеме:

  • Initiator Microservice — микросервис-инициатор отправки нотификации, связан с бизнес-логикой процесса, в рамках которого необходимо отправить нотификации на клиент.

  • Transport Microservice — транспортный микросервис (на Java), отвечающий за отправку нотификаций, не содержит бизнес-логики.

  • Instance 1…n — инстанс транспортного микросервиса.

  • Web — JS-клиент, принимающий нотификации.

Процесс отправки:

  1. Web устанавливает WebSocket-соединение с Transport Microservice посредством RSocket. 

  2. Transport Microservice сохраняет in-memory информацию о клиенте.

  3. Через какое-то время Initiator Microservice принимает решение о необходимости отправить нотификацию и отправляет REST-запрос в транспортный сервис. 

  4. Transport Microservice принимает нотификацию, отправляет на инстанс с установленным WebSocket-соединением посредством Redis Pub/Sub. 

  5. Transport Microservice отправляет RSocket-фрейм с нотификацией по WebSocket-соединению.

Глубокое погружение в технические детали: принцип работы

Начнём с двусторонней связи в RSocket.

Двусторонняя связь

В RSocket это означает, что клиент и сервер могут обмениваться сообщениями в обоих направлениях одновременно.

В отличии от STOMP, где присутствует чёткое разделение между клиентом и сервером, в RSocket такого разделения нет. И клиент, и сервер занимают равные позиции и имеют равные возможности. 

Поскольку грани между клиентом и сервером стёрты, в RSocket введены понятия Requester (инициатор отправки данных) и Responder (принимает данные). Поэтому Requester«ом может быть как сервер:

55b50a3a04bfde3da81cd5d486ec6610.png

Так и клиент:

23eeec658a366a67b6b8a68a7c0b191a.png

Фреймы 

Фрейм — это сообщение определенного типа, передающееся по WebSocket-соединению. Фреймы могут быть разными, в зависимости от используемого фреймворка (разные типы, содержимое, заголовки и т.д.).

Примечание. Список возможных RSocket-фреймов.

Фреймы в RSocket представлены в бинарном формате, отличаются по содержанию друг от друга, но в основном состоят из заголовков, метаданных и текста:

  • Заголовки: frame type (SETUP, PAYLOAD и т.д.), flags, Stream ID и т.д.

  • Метаданные (metadata) — могут быть включены в заголовок фрейма. Метаданные могут содержать контекстную информацию, дополнительные атрибуты или любую другую информацию, которая может быть полезна при обработке фрейма. Например, метаданные могут содержать информацию о типе сериализации, версии протокола, идентификаторе пользователя, токене аутентификации или любых других деталях, которые могут быть полезными для сервера или клиента.

  • Текст (data) — содержимое, передаваемое во фрейме. 

f4457ea81934fa1e067a4a445b53c4e5.png

Виды взаимодействий

Взаимодействия между Requester и Responder можно построить разными способами, в зависимости от задачи. Виды взаимодействий более подробно можно изучить в документации протокола. Схематично они выглядят так:

0a33362f527bc3a8a73a74dac95a4637.png

В своей задаче мы выбрали Response Stream. После открытия WebSocket-соединения клиент открывает стрим нотификаций посредством фрейма REQUEST_STREAM и в рамках этого стрима получает нотификации с сервера посредством фреймов PAYLOAD.

Глубокое погружение в технические детали: клиент

Небольшой дисклеймер: на момент написания этой статьи актуальная версия rsocket-js 1.0.0-alpha.3. Так как мы разрабатываем продукт в крупном банке, мы не можем себе позволить использовать альфа-версии (забавно — Альфа-Банк не может использовать альфа-версии:) Open Source пакетов в продакшене, поэтому мы используем последнюю релизную версию RSocket — 0.0.7.

На клиентской стороне мы решили создать Singleton RSocket-клиента.

  • У RSocket есть классы конструкторы для создания таких клиентов, которые принимают значения keepAlive, lifeTime и т.д.

  • После создания клиента, который устанавливает handshake с сервером, мы сразу делаем запрос requestStream (фрейм REQUEST_STREAM).

  • Далее по этому стриму получаем сообщения и посылаем их в шину событий (Event Bus). 

В остальных микросервисах нашего клиентского приложения мы должны подписаться на определенное событие и получать сообщения (они же нотификации).

Код ниже создает RSocket-клиента. Конструктор класса RSocketClient импортируется из библиотеки rsocket-core (это просто конфигурационный код, который можно найти в официальной документации RSocket).

const createRSocketClient = () =>
   new RSocketClient({
       setup: {
           keepAlive: KEEP_ALIVE, //таймаут отправки KEEPALIVE-фрейма
           lifetime: LIFE_TIME,   //максимальное время жизни соединения
           dataMimeType: DATA_MIME_TYPE,         //формат данных в data
           metadataMimeType: METADATA_MIME_TYPE, //формат данных в metadata
       },
       transport: new WebsocketClientTransport({
           url: `${RSOCKET_TRANSPORT_PATH}`, //URL для установки соединения
       }),
   });

Далее рассмотрим код, который создает коннект с сервером и устанавливает взаимодействия типа Response Stream:

           #1
           const client = createRSocketClient();
           #2
           const transport = await client.connect(); 
           #3
           const stream = transport.requestStream({ 
             data,
             metadata
           });


           #4
           stream.subscribe({
               onNext: (payload: Payload) => {
                   if (payload.data) {
                       // Ваша обработка сообщения
                   }
               },
               onError: (e: Error) => {
                 // Ваша обработка ошибки при попытке подписаться на стрим
               },
               onComplete: () => {
                 // Ваша обработка завершения подписки на стрим
               },
               onSubscribe: (subscription: ISubscription) => {
                   #5
                   subscription.request(NUMBER_OF_STREAM_MESSAGES); 
               },
           });

Если вы знаете JS, то понять данный код не составит труда, но давайте разберем некоторые строки, которые могут вызывать затруднение:

#1. Мы создаем клиента, с помощью ранее созданной функции.

#2. Пытаемся установить соединение с сервером (так называемый handshake)

#3. Делаем запрос на requestStream, при попытке установить такой вид взаимодействия, клиент может отправлять объект, который содержит data и metadata (фрейм REQUEST_STREAM).

#4. Подписываемся на стрим.

#5. Здесь константа NUMBER_OF_STREAM_MESSAGES — это число сообщений, после которого подписка на стрим завершится автоматически.

В ходе разработки мы столкнулись с несколькими проблемами на клиентской стороне.

#1. Отсутствие обработки JS-ошибок в библиотеке RSocket.

Первый раз мы попались на это, когда неправильно договорились о формате отправляемой metadata, из-за чего десериализация происходила с эксепшеном, который на стороне RSocket-библиотеки никак не обрабатывался. Отладка заняла немало времени.

Ещё раз мы столкнулись с этой же проблемой, когда при подписке на стрим не захотели использовать функционал обработчиков типа onComplete и onError. Такой подход вызывал экспешн, который тоже не обрабатывался.

#2. Запись в глобальный объект Buffer свой самописный Buffer, который скопирован из библиотеки buffer-lite, но более старой версии (без поддержки base64).

В RSocket работает следующая логика: после установки пакета rsocket-core происходит проверка на наличие buffer в объекте window. Если он отсутствует, то пакет записывает в window свой собственный buffer.

В нашем проекте мы использовали библиотеку buffer, которая не записывала ничего в глобальный window, поэтому мы просто импортировали buffer напрямую из этой библиотеки. Однако, после установки rsocket-core такой подход перестал работать, так как у нас были ситуации, где buffer использовался для преобразования в base64, что не поддерживалось buffer«ом из RSocket.

Решением проблемы стал переход во всем проекте с библиотеки buffer на buffer-lite более свежей версии, чем тот, который скопипасчен в rsocket-core. 

Вот такие пироги.

Глубокое погружение в технические детали: сервер

Реализация серверного решения для RSocket представляет собой микросервис, написанный под Spring Boot 2.7+ на языке Java 17-й версии.
Из особенностей можно выделить простоту конфигурации библиотеки RSocket, как и для большинства интеграций со Spring. 


В целом, чтобы запустить RSocket на серверной стороне, не потребовалось добавлять свои кастомные спринговые конфигурации — дефолтных настроек вполне достаточно. Нужно сделать всего два действия.

#1. Добавить несколько строчек в application.yml.

spring:
  rsocket:
    server:
      port: 0
      mapping-path: "/rsocket"
      transport: "websocket"

Строчка mapping-path — аналогия аннотации @RequestMappingв RestController, описывающая общее начало URL методов WebSocket контроллера, а transport позволяет выбрать протокол, используемый для соединения (на выбор 2 варианта: TCP и WebSocket).

Кстати строчку port можно оставить либо только для локальных тестов, в рабочей среде маршрутизация пойдет через порт Webflux, либо чтобы маршрутизировать запросы WebSocket подключения через отдельный порт.



#2. Описать контроллер для установки WebSocket-соединения, схожий с тем, который используется для REST«овых HTTP-запросов.

По второму пункту для решения нашей задачи мы выбрали вариант взаимодействия Response Stream (через фрейм REQUEST_STREAM).

@Slf4j
@Controller
@RequiredArgsConstructor
public class RSocketController {
	public static final String NOTIFICATION_PATH = "/";
	private final ReactiveRedisTemplate notificationTemplate;

	@ConnectMapping()
	public void connect(RSocketRequester rSocketRequester, @Payload String id) {
    	log.debug("Handling connection for {} ...", rSocketRequester);
    	/*
    	В данном варианте возможность открытия соединения проверяется наличием идентификатора пользователя.
     	*/
    	if (isNotEmpty(trimToEmpty(id))) {
        	log.debug("Connection approved");
        	return;
    	}
    	/*
    	Если по каким-то причинам мы не хотим открывать соединение на запрос с клиентской стороны,
    	то для этого необходимо вызвать метод dispose() у объекта RSocketRequester, который предоставляется в качестве
    	параметра метода благодаря интеграции со Spring и является олицетворением клиента вебсокетного подключения.
     	*/
    	log.debug("Connection denied");
    	rSocketRequester.rsocketClient().dispose();
	}

	@MessageMapping(NOTIFICATION_PATH)
	public Flux sendNotification() {
    	/*
    	Именно здесь происходит подписка на сообщения от реактивного паблишера. В качестве которого выступает класс
    	ReactiveRedisTemplate, предоставляющий доступ к асинхронным (реактивным) операциям над Redis. Такими операциями
    	могут быть чтение, запись и удаление данных из кэша, однако в нашем варианте ReactiveRedisTemplate используется
    	в качестве брокера сообщений, реализующего механизм Redis Pub/Sub
     	*/
    	return notificationTemplate.listenToChannel("notification")
            	.map(ReactiveSubscription.Message::getMessage);
	}
}

Разберём этот код подробнее.

#1. Метод, помеченный аннотацией @ConnectMapping будет отвечать за некоторые приготовления будущего соединения, например проверять возможность установления этого соединения. Наличие этого метода в контроллере не является чем-то необходимым — он будет работать и без него. Его наличие, как правило, зависит от необходимости предварительных настроек или проверок перед установлением соединения.

Обратите внимание на возвращаемый тип этого метода, он должен быть void, либо Mono в случае подписки на реактивные паблишеры (об этом чуть позже). В противном случае при попытке сборки проекта вылетит ошибка.

#2. Методы, помеченные аннотацией @MessageMapping — аналогии методов @GetMapping, @PostMapping и других в классическом REST контроллере. Именно в этих методах происходит обмен информацией между клиентской и серверной стороной соединения.

Здесь возвращаемый тип метода также играет важную роль, например для реализации RSocket взаимодействия Response Stream необходимо указать Flux, потому что он является  представлением одного или нескольких значений, получаемых от реактивного паблишера. Для реализации других взаимодействий необходимо указать свой возвращаемый тип, иногда в связке с определенным типом параметра метода. Подробнее о других способах взаимодействия можно прочитать из документации Spring по ссылке в разделе Приложения.

Всё описанное выше отлично бы работало, если бы не один нюанс на серверной стороне (который к счастью нам удалось решить), а именно:

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

Чисто технически эти заголовки приходили вместе с заголовками самой библиотеки в методе в качестве одного из параметров, помеченным аннотацией @Headers . Однако вытащить их напрямую не представлялось возможным из-за сильной абстракции и закрытости классов библиотеки RSocket.

Поэтому единственная возможность получить кастомные заголовки — только через их отправку напрямую с клиентской стороны (можно было конечно попробовать их вытащить через рефлексию, но это выглядело, мягко говоря, некрасиво). Чем мы и воспользовались, благодаря предварительному GET-запросу, из которого мы и получали необходимые заголовки. Которые после шифровали и отправляли обратно на клиентскую сторону, чтобы затем получить их при установлении WebSocket-соединения.

Общие проблемы, с которыми мы столкнулись

Совместимость с enterprise-инфраструктурой.

У соединения на пути много прокси, гейтвеев и других инфраструктурных компонент. Нужно тщательно проверить, что WebSocket + RSocket будет корректно обрабатываться по всей цепочке. Например, могут возникнуть проблемы, связанные с техническими заголовками и обработкой бинарного содержания RSocket-фреймов.

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

Недостаток документации и слабое комьюнити.

Документация, безусловно, есть, и, в теории, всё понятно. Но когда мы приступили к практике, то поняли, что примеров и более подробного объяснения очень не хватает. Например, как корректно обрабатывать фреймы, работать с metadata (одна из составляющих фреймов). В дискорде есть англоязычное коммьюнити с тредами для многих языков программирования. Но при этом обсуждений там крайне мало, нет оживленных дискуссий и если кто-то хотя бы попытается ответить, то это уже успех.

Наш опыт мы детально задокументировали, что позволит не наступать на грабли каждый раз при поиске информации. В случае, если функционал расширяется и необходим ресёрч, мы сначала строим гипотезы и проверяем их на прототипе приложения.

Высокий порог входа.

Технология относительно новая и просто так сразу на ней не начнешь делать проекты. Для начала нужно изучить теорию, овладеть терминологией и, при необходимости, собрать тестовый прототип, чтобы появилось понимание, как RSocket работает изнутри. А здесь есть некоторые нюансы.

Нюансы работы с WebSocket.

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

  2. Memory usage прямо пропорционально количеству коннекшенов между клиентом и сервером. Поэтому потребление памяти будет немаленьким при большом количестве соединений. 

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

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

Тестирование

В силу специфики тестирования RSocket соединения (и горящих сроков) при внедрении, весь комплекс мероприятий сводился к ручным проверкам. Как автоматизировать данный процесс, не сказано даже в дискорде RSocket коммьюнити, поэтому нам придётся разбираться в этом самостоятельно, уже после выпуска функциональности в продакшн. 

Комплекс ручных проверок можно негласно разбить на несколько типов:  

#1. Проверки, направленные на установку RSocket соединения и получения нотификации.
#2. Проверки, направленные на содержание фреймов.
#3. Иные проверки.

Поговорим о каждом из типов подробнее. 

#1. Проверки, направленные на установку RSocket соединения и получения нотификации 

При тестировании RSocket-соединения нам понадобится лишь DevTools и (что логично) знания о специфике этого соединения. 

Для начала тестирования переходим в песочницу/тестовую среду и открываем DevTools, раздел Сеть (см. скриншот)

8ef05f561ef43aea93f10cad3f3fc540.png

Чтобы не искать наше подключение среди множества других, можем отфильтровать его, выбрав опцию WS. RSoсket-соединение (в случае, если оно есть) сразу же отобразится. 

a60fbcfcefe1c24dab1caefedae4fe1e.png

Если оно не отображается, прежде чем идти к разработчику, проверим следующее:  

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

  • Что оно не отображается у всех пользователей.

Итак, соединение установлено, пора получать нотификации: дёргаем соответствующую API в удобном для вас софте и ждём появления уведомления в UI. Если оно не приходит, прежде чем идти к разработчику:  

  • Перезагрузите страницу и попробуйте ещё раз (старый, добрый, но всегда актуальный совет всем тестировщикам веба)

  • Убедитесь, что RSocket-соединение всё ещё активно.

  • Убедитесь, что все данные в запросе корректны.

Если кликнуть по RSocket, у нас будет возможность увидеть весь спектр сообщений, которыми обмениваются сервер и клиент (раздел «Сообщения» на скриншоте), проследить за статусом запроса (101 Switching Protocols) в разделе «Заголовки» и увидеть продолжительность нашего rsocket-соединения в разделе «Время». При помощи последнего мы также можем убедиться, что оно разорвано, ибо RSocket не всегда отправляет фрейм о закрытии соединения.

133a7de4524b85f4f7f26a7e43677334.png

#2. Фреймы

Для понимания, что у нас содержится во фрейме (особенно в заголовке), нам необходимо расширение для браузера RSocket Frame Inspector. С его помощью фреймы, представленные бинарно в DevTools, имеют удобочитаемый вид.

1733cdb19b894b1eb1506851a9528c42.png

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

Сравниваем типы фреймов с необходимыми нам, проверяем, что в SETUP верно указаны параметры. После отправки нотификации, при помощи запроса проверяем, что сервер отправляет клиенту фрейм PAYLOAD, содержащий контент нотификации. 

В ходе соединения для его поддержания клиент и сервер обмениваются фреймами KEEPALIVE, их наличие мы тоже можем отследить. Если их нет по истечении времени, указанном в параметре keepAlive (во фрейме SETUP), сообщаем разработчику. 

Если соединение было разорвано, проверяем, что новых фреймов не приходит. Соединение можно разорвать, если перейти на другой сайт, закрыть вкладку или разлогиниться. В таком случае мы ждем фрейм ERROR, который подтверждает разрыв соединения. 

#3. Иные проверки

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

Помимо этого, из интереса проверили, появится ли соединение на одной из уже открытых вкладок сверх лимита, если мы закроем одну из тех, на которой оно было. Также проверили, что после повторного логина RSocket-соединение снова может быть установлено для пользователя. 

Резюмируя, можно сказать, что для ручного тестирования RSocket-соединения вам нужно три составляющих: DevTools, Rsocket Frame Inspector и знания о том, что вам нужно проверить. 

Ещё немного рекомендаций

Обратите внимание на таймауты.

Keepalive — интервал времени, через который клиент отправляет KEEPALIVE фреймы серверу для поддержания активного соединения.

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

Timeout — аналог lifetime, но для сервера. RSocket установил это значение равное одной минуте, настроить этот параметр нельзя (если Вы, конечно, не захотите законтрибьютить RSocket репозиторий).

Соответственно, keepalive должен быть меньше timeout. В нашем случае значения были равны и периодически возникали коллизии. Сервер обрывал соединение, не отправляя фреймы закрытия соединения. 

Metadata использовать не обязательно, если в этом нет явной потребности.

Дело в том, что внутренняя логика обработки metadata в официальной документации практически не описана, а отладка ошибок, связанных с обработкой metadata, затруднительна, потому что обработчик отдельных эксепшнов в библиотеке отсутствует.

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

Выделяйте больше памяти на микросервис.

Ведь при большом количестве WebSocket-соединений memory usage будет высоким в любом случае.

Запаситесь успокоительным :) 

Заключение и будущее системы нотификаций

RSocket позволил нам создать быструю масштабируемую систему нотификаций, которую в будущем можно расширять различными модификациями:

  • гарантия доставки;

  • ответ об успешной/неуспешной доставке нотификации и, в принципе, возможность любого ответа на нотификацию (да, такое иногда может пригодиться);

  • выбор опции работы с несколькими коннекшенами на одном клиенте, например.

А дальше уже насколько хватит требований и фантазии :) 

RSocket — мощная технология, применимая не только в системе нотификаций, поэтому её потенциал может выходить за рамки данной системы и использоваться для других целей.

Приложение

Ссылки на дополнительные ресурсы.

Контакты команды

Эту задачу мы решали вместе:

© Habrahabr.ru