UA RU
logo
Опыт      09/06/2022

Как интегрировать на проекте WebSocket и не сгореть: пошаговая инструкция

Роман Дашківський BLOG

Java Developer в NIX

Привет! Меня зовут Роман Дашковский, я Java Developer в NIX и спикер IT-конференции NIX MultiConf. В этой статье я расскажу, с какими трудностями можно столкнуться при интегрировании на проекте WebSocket и как их преодолеть.

Представим простой ToDo-менеджер. Его клиент (т.е. окно браузера) посылает запросы на сервер и получает ответ. Для того, чтобы другой клиент увидел любые обновления, он должен запросить и получить эти данные.

Но что делать, когда клиент требует от пользователей наблюдать все апдейты «на лету»?

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

Что такое веб-сокет

Так что же такое сокеты, зачем их использовать и как вообще работают веб-приложения? Все это я объясню дальше на простом примере.

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

Жизненный цикл соединения состоит из трех этапов:

  1. Инициализация соединения — происходит handshake-запрос по HTTP, после чего соединение обновляется в WebSocket.
  2. Пересылка данных — отправка сообщения может происходить от клиента на сервер и наоборот.
  3. Разрыв соединения — инициатором разрыва может быть как клиент (например, закрытие вкладки), так и сервер (программно).
  4. Онлайн-курс "Ціноутворення для виробників та ритейлу" від Laba.
    Масштабуйте бізнес, незалежно від конкурентів, завдяки оптимізації стратегії ціноутворення.Досвід та фідбек від Senior Product Manager Pricing Platform у Zalando.
    Детальніше про курс

Подключение к SpringBoot-приложению

Давайте рассмотрим, как это все подключить к классическому SpringBoot-приложению.

Предварительная подготовка

Любые апдейты начинаются с чего? Правильно — с добавления необходимых зависимостей:

  • Для простоты использования Spring имеет множество стартеров разных цветов и размеров. Нас интересуют сокеты, поэтому добавим подходящий стартер.
  • Так как мы будем работать с данными JSON-формата, добавим JSON от гуглов.
  • В нескольких местах я буду использовать некоторые утилиты, упрощающие процесс парсинга URL. Добавляем и третью зависимость от Apache.

Предварительная подготовка завершена. Перейдем к конфигурации Spring.

Конфигурация Spring

Обычно значительная часть подобной «магии» происходит в классах, отмеченных соответствующей аннотацией Configuration. Важно добавить еще одну — @EnableWebSockets.

Онлайн-курс "People Management" від Laba.
Пройдіть шлях від формування відповідальної команди до написання кар'єрної карти для кожного співробітника разом з топменеджеркою з 11-річним досвідом у провідних IT-компаніях.
Детальніше про курс

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

Учитывая то, что при инициализации сокета у нас нет возможности передать никаких параметров в теле или хедерах, единственным вариантом для того, чтобы идентифицировать пользователя, остаются URL-параметры. Поэтому мы можем считать наш токен, верифицировать его и, если он валиден, подтвердить создание соединения.

 

Обработчик событий

Перейдем к следующему шагу — обработчику событий. В нашем примере запланирована обработка только текстовых сообщений, поэтому мы полагаемся на абстрактный класс TextWebSocketHandler. Сообщения мы будем отправлять только с сервера на клиент, так что метод handleTextMessage можно оставить пустым.

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

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

Онлайн-курс "SMM-спеціаліст" від Laba.
Від аналізу аудиторії та створення живого контенту — до побудови комʼюніті навколо бренду в соцмережах.Під менторством Senior SMM Specialist в Uklon.
Дізнатись більше

UI-часть

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

Дальше добавляем все необходимые слушатели событий — и вот как это выглядит схематически:

В действительности же один клиент посылает HTTP-запрос на обновление данных. После чего в контроллере мы вызываем метод notifyAll или notifyUser по одной из предыдущих схем. Здесь появляется немного магии React. Как видим, данные обновляются сразу в двух окнах.

Все клево, все работает. Но если бы все было так просто, я не писал бы эту статью…

В enterprise-проектах обычно важный фактор — производительность. Но часто одни и те же микросервисы могут существовать в нескольких экземплярах, доступ к которым осуществляется непосредственно через Load Balancer. И в зависимости от загрузки CPU, занятости оперативной памяти или других параметров могут создаваться новые инстансы или удаляться существующие. Но как текущая реализация будет работать в этом случае?

Онлайн-курс Digital Marketing від Mate academy.
На курсі Digital Marketing ви отримаєте усі необхідні навички, щоб отримати нову роботу: навчитесь використовувати цифрові канали для залучення аудиторії, просування брендів, товарів та послуг.
Отримати знижку на курс

Наша карта с конекшнами сохраняется In-Memory внутри каждого инстанса. Когда мы инициализируем соединение по WS, ELB выбирает, в какой сервис будет отправлен запрос, и конекшн хранится только в нем. Когда любой сервис будет проходить по карте, вероятно, будет такая ситуация, что часть клиентов так и не получит сообщения.

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

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

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

Пригодится здесь Message Broker, но я выбрал ActiveMQ по простой и банальной причине — он один из самых популярных. Так что продолжаем кодить…

Как обычно, сначала обновляем зависимости. Я решил заюзать такую ​​крутую штуку, как Apache Camel. Это открытый кроссплатформенный Java-фреймворк, позволяющий производить интеграцию приложений в простой и понятной форме. Camel очень упрощает флоу обработки данных. В особенности это комфортно, если нужно выстроить цепь из нескольких обработчиков. Также он очень легко интегрируется со Spring.

Основи Web дизайну від Hillel IT School.
Цей онлайн-курс з основ веб-дизайну дозволить вам опанувати мистецтво створення ефективних та привабливих інтерфейсів для вебсайтів і застосунків. Ви оволодієте ключовими принципами UX/UI дизайну, створюватимете дизайн-макети та прототипи, розроблятимете адаптивні інтерфейси для різних пристроїв, готуючись до професійної кар'єри в галузі веб-дизайну.
Дізнатися більше

Не нарушаем традиции и дальше переходим к Spring-конфигам. Здесь нужно добавить два бина:

  • ActiveMqConnectionFactory, которому мы скармливаем пропсы из application.properties;
  • ActiveMQComponent — компонент Camel, который упростит процесс отправки и получения сообщений с брокера.

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

Единственное, что хочу подчеркнуть — способ, которым мы передаем информацию о пользователе, которому отправляем сообщение. Это делается с помощью Header, которые позже можно будет считать при получении сообщения от брокера.

После того, как мы отправили эти сообщения, их нужно получить на каждом инстансе. Здесь возникает одна из причин, почему я добавил в проект Camel — невероятная простота в использовании. Вот пример считывания и логирования месседжа.

Бізнес англійська від Englishdom.
Тут навчають за методикою Кембриджу, завдяки якій англійську вивчили понад 1 мільярд людей. Саме вона використовується в найкращих навчальних закладах світу, і саме за нею створені курси.
Інформація про курс

Но просто логировать сообщение недостаточно. Дальше его нужно отправить всем клиентам.

Для этого создаем обработчик:

  • парсим тело сообщения из обычной String в JSON-объект и затем к нашему Java-объекту;
  • считываем хидер, который мы засетили раньше;
  • пересылаем дальше уже существующими методами;
  • добавляем его в наш Data Flow в одну строку.

Наконец-то переходим к тесту. Есть клиенты, подключенные к двум разным инстанциям.

Практичний інтенсивний курс з дизайну - Design Booster від Powercode academy.
Навчіться дизайну з нуля за 3 місяці і заробляйте перші $1000, навіть якщо ви не маєте креативного мислення, смаку або вміння малювати. Отримайте практичні навички, необхідні для успішної кар'єри в дизайні.
Зарееструватися

Выполняем апдейт на одном и наблюдаем обновления сразу обоих:

Теперь все работает. Хотел бы я так сказать, но…

Добавляем еще эндпоинт

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

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

Как фиксить проблемы с производительностью

В противном случае это может привести к проблемам с производительностью и формированием так называемого Bottlneck. Итак, как это пофиксить?

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

Онлайн курс з промт інжинірингу та ефективної роботи з ШІ від Powercode academy.
Курс-інтенсив для отримання навичок роботи з ChatGPT та іншими інструментами ШІ для професійних та особистих задач, котрі допоможуть як новачку, так і професіоналу.
Записатися на курс

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

  • класснейм процессора для обработки сообщений;
  • параметры в формате карты.

Далее создаем процессор, который будет извлекать обновленные данные из БД, 3rd Party Service или из другого места и передавать их дальше.

После этого обновляем наш Data Flow в роутере. Если найден подходящий процессор, на него и идет обработка. Затем происходит отправка сообщения клиентам по WebSocket.

Вот упрощенная схема, как это работает:

Курс Розмовної англійської від Englishdom.
Після цього курсу ви зможете спілкуватись з іноземцями і цікаво розкажете про себе.
Приєднатися

Вместо вывода

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

 

Надеюсь, теперь при необходимости реализовать сокеты вы заранее продумаете все детали, будете знать, с какими трудностями можете столкнуться и как их преодолеть. А если хотите подробнее рассмотреть приведенный в этой статье код, переходите по ссылкам на репозитории с фронтом и бэкендом:

Онлайн-курс "English for Designers" від Vocabulaba.
Навчіться описувати деталі своєї роботи, формувати CV англійською та вільно обговорювати флоу проєктів із замовниками.
Детальніше про курс

If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.

Онлайн-курс "Математика тастатистика для Data Science" від robot_dreams.
Навчіться проводити статистичний аналіз даних за допомогою Python та розвиньте математичне мислення для розв’язання реальних завдань Data Science.
Детальніше про курс

Этот материал – не редакционный, это – личное мнение его автора. Редакция может не разделять это мнение.

Ваша жалоба отправлена модератору

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: