ru:https://highload.today/blogs/kak-integrirovat-na-proekte-websocket-i-ne-sgoret-poshagovaya-instruktsiya/ ua:https://highload.today/uk/blogs/yak-integruvati-na-proyekti-websocket-ta-ne-zgoriti-pokrokova-instruktsiya/
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. Онлайн-курс "Комерційний Аудіопродакшн" від Skvot.
    Навчіться створювати, зводити й мастерити музику для комерційних проєктів — кіно, серіалів, улюблених ігр чи вірусних рекламних роликів.
    Детальніше про курс та довід лектора

Підключення до SpringBoot-застосунку

Давайте розглянемо, як це все підключити до класичного SpringBoot-застосунку.

Попередня підготовка

Будь-які апдейти починаються з чого? Правильно — з додавання необхідних залежностей:

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

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

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

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

Онлайн-курс "Проджект-менеджер в ІТ" від Laba.
Навчіться запускати, контролювати й успішно реалізовувати ІТ-проєкти. Пройти весь шлях проєктного управління на реальному кейсі вам допоможе PMD із 19-річним досвідом в ІТ.
Детальніше про курс

У документації до неї сказано, що ми маємо реалізувати інтерфейс WebSocketConfigurer. Як бачите, інтерфейс має лише один метод. Тут ми можемо додати обробники повідомлень та перехоплювач хендшейку. Таким чином у нас відбувається валідація зʼєднання.

Враховуючи те, що при ініціалізації сокету ми не маємо можливості передати жодних параметрів у тілі або хедерах, єдиним варіантом для того, щоб ідентифікувати користувача, залишаються URL-параметри. Тож тут ми можемо зчитати наш токен, верифікувати його та, якщо він валідний, підтвердити створення зʼєднання.

 

Обробник подій

Перейдемо до наступного кроку — обробник подій. У нашому прикладі запланована обробка лише текстових повідомлень, тому покладаємось на абстрактний клас TextWebSocketHandler. Повідомлення ми будемо надсилати лише з серверу на клієнт, тож метод handleTextMessage можна залишити пустим.

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

Тепер відправка повідомлень вже тривіальна задача. Спробуємо реалізувати можливість надсилати повідомлення як усім, так і одному окремому користувачу.

Онлайн-курс "Корпоративна культура" від Laba.
Як з нуля побудувати стабільну корпоративну культуру, систему внутрішньої комунікації та бренд роботодавця, з якими ви підвищите продуктивність команди, — пояснить HR-директор Work.ua.
Детальніше про курс

UI-частина

Як це виглядає з точки зору UI-частини? Як я вже зазначав, при ініціалізації сокету ми не можемо додавати ні хедери, ні тіло запиту. Але завжди є можливість передати параметри через URL, що ми і зробимо.

Далі додаємо всі необхідні слухачі подій — і ось, як це виглядає схематично:

У реальності ж один клієнт надсилає HTTP-запит на оновлення даних. Після чого в контролері ми викликаємо метод notifyAll або notifyUser з однієї з попередніх схем. Тут з’являється трошки магії React. Як бачимо, дані оновлюються одразу у двох вікнах.

Усе кльово, все працює. Та якби все було так просто, я би не писав цю статтю…

В enterprise-проєктах зазвичай важливим фактором є продуктивність. Однак часто одні й ті самі мікросервіси можуть існувати у декількох екземплярах, доступ до яких здійснюється безпосередньо через Load Balancer. І в залежності від завантаження CPU, зайнятості оперативної памʼяті або інших параметрів можуть створюватися нові інстанси або видалятись існуючі. Але як поточна реалізація працюватиме в такому випадку?

Онлайн-інтенсив "Як створити рекомендаційну модель за 2 дні" від robot_dreams.
Ви пройдете етапи вибору, навчання, оцінки рекомендаційної моделі для електронної бібліотеки та отримаєте індивідуальний фідбек від лекторки.
Приєднатись до інтенсиву

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

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

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

Вдалим способом вирішення проблеми є створення єдиного сховища подій, яке буде повідомляти всім нашим інстансам про те, що кожен має відправити повідомлення клієнтам, які збережені у них самих. У нагоді тут може стати Message Broker, але я обрав ActiveMQ з простої та банальної причини — він один із найпопулярніших сьогодні. Тож продовжуємо кодити…

Як завжди, спершу оновлюємо залежності. Я вирішив заюзати таку круту штуку, як Apache Camel. Це відкритий кросплатформенний Java-фреймворк, який дозволяє проводити інтеграцію застосунків у простій та зрозумілій формі. Camel дуже спрощує флоу обробки даних. Особливо це зручно, якщо треба побудувати ланцюг із декількох обробників. Також він дуже легко інтегрується зі Spring.

Онлайн-курс "Проджект-менеджмент у геймдеві" від Skvot.
Новий левел для тих, хто хоче поєднати менеджерські скіли та любов до ігор.Отримай необхідний скілсет та керуй командою в ігровій індустрії.
Детальніше про курс

Не порушуємо традицій і далі переходимо до Spring-конфігів. Тут необхідно додати два біни:

  • ActiveMqConnectionFactory, якому ми скормлюємо пропси з application.properties;
  • ActiveMQComponent — компонент Camel, який надалі спростить процес відправки й отримання повідомлень із брокера.

Далі створюємо два методи з тими ж сигнатурами, що й були раніше. Так як я розповідаю про сокети, а не про Camel, довго зупинятись на ньому не будемо.

Єдине, що хочу підкреслити — спосіб, у який ми передаємо інформацію про користувача, котрому відправляємо повідомлення. Це робиться за допомогою Header, які пізніше можно буде зчитати при отриманні повідомлення від брокера.

Після того, як ми надіслали ці повідомлення, їх необхідно отримати на кожному інстансі. Тут виникає одна із причин, чому я додав до проєкту Camel — неймовірна простота у використанні. Ось приклад зчитування та логування меседжу.

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

Однак просто логувати повідомлення недостатньо. Далі необхідно відправити його всім клієнтам.

Для цього створюємо обробник:

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

Нарешті переходимо до тесту. Маємо клієнтів, підʼєднаних до двох різних інстансів.

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

Виконуємо апдейт на одному та спостерігаємо оновлення на одразу обох:

Тепер все працює. Хотів би я так сказати, але…

Додаємо ще ендпоінт

Усе чудово працюватиме, коли ми оперуємо невеликими обʼєктами. Але знову ж таки у реальних проєктах дуже багато систем, які обробляють великі обʼєми даних. Тож нехай і у нас буде такий ендпоінт, який повністю переписуватиме всі ToDo-списки, які стосуються поточного користувача. На вхід він приймає массив цих списків, розмір яких обмежений лише фантазією.

Таким чином повідомлення з оновленими списками будет надходити до брокера, звідки бродкаститись на всі інстанси. Однак брокери повідомлень не призначені для таких цілей. Вони швидко й ефективно працюють із невеликими обʼємами даних.

Як фіксити проблеми з продуктивністю

В іншому випадку це може призвести до проблем із продуктивністю та формуванням так названого Bottlneck. Отже, як це пофіксити?

Застосуємо оновлений підхід. Для подібного типу повідомлень будемо відправляти не весь оновлений обʼєкт, а інформацію про те, який обʼєкт змінився та як саме. Після цього SpecProcessor на інстансах самостійно зберуть усі нові дані та розішлють їх клієнтам.

Онлайн-курс "Excel та Power BI для аналізу даних" від robot_dreams.
Навчіться самостійно аналізувати й візуалізувати дані, знаходити зв’язки, розуміти кожен аспект отриманої інформації та перетворювати її на ефективні рішення.
Детальніше про курс

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

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

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

Після цього оновлюємо наш Data Flow у роутері. Якщо знайдено відповідний процесор, на нього і йде обробка. Потім відбувається відправка повідомлення клієнтам по WebSocket.

Ось спрощена схема, як це працює:

Онлайн-курс "Створення особистого бренду" від Skvot.
Прокачайте особистий бренд для підсилення власного бізнесу, підвищення продажів та впізнаваність на ринку.
Дізнатись більше про програму курсу і досвід лектора

Замість висновку

Давайте повернемося до того, з чого починали, і поглянемо, як це все перетворилося на систему з двонаправленим звʼязком. Ця система може легко масштабуватися і не створює зайвого навантаження на наші ресурси:

 

Сподіваюсь, тепер при необхідності реалізувати сокети ви заздалегідь продумаєте всі деталі, будете знати, з якими труднощами можете зіткнутися та як їх подолати. А якщо хочете детальніше розглянути наведений у цій статті код, переходьте за посиланнями на репозиторії з фронтом і бекендом:

Курс Job Interview Crash Course від Enlgish4IT.
Отримайте 6 шаблонів відповідей на співбесіді, які ви зможете використовувати для структурування своїх відповідей. Отримайте знижку 10% за промокодом ITCENG.
Приєднатися

Якщо ви знайшли помилку, будь ласка, виділіть фрагмент тексту та натисніть Ctrl+Enter.

Воркшоп "PR + AI: Рисерч, Креатив, Контент" від Skvot.
Навчіться адаптувати потенціал АІ під задачі піарника. Корисні тулзи, яким можна делегувати рутину, генерувати свіжі ідеї для контенту і піар-стратегій.
Дізнатись більше

Цей матеріал – не редакційний, це – особиста думка його автора. Редакція може не поділяти цю думку.

Топ-5 найпопулярніших блогерів березня

PHP Developer в ScrumLaunch
Всего просмотровВсього переглядів
2434
#1
Всего просмотровВсього переглядів
2434
Founder at Shallwe, Python Software Engineer (Django/React)
Всего просмотровВсього переглядів
113
#2
Всего просмотровВсього переглядів
113
Career Consultant в GoIT
Всего просмотровВсього переглядів
95
#3
Всего просмотровВсього переглядів
95
CEO & Founder в Trustee
Всего просмотровВсього переглядів
94
#4
Всего просмотровВсього переглядів
94
Рейтинг блогерів

Найбільш обговорювані статті

Топ текстів

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

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

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