Вступ до Vert.x, найшвидшого фреймворку Java сьогодні

Якщо ви нещодавно гуглили "найкращий веб-фреймворк", можливо, ви натрапили на тести Techempower, де класифікується більше трьохсот фреймворків. Там ви могли помітити, що Vert.x є одним із найкращих, якщо не першим за деякими показниками.

Тож давайте поговоримо про це.

Vert.x - це багатофункціональний веб-фреймворк, який має спільні функціональні можливості серед підтримуваних мов Java, Kotlin, Scala, Ruby та Javascript. Незалежно від мови, Vert.x працює на віртуальній машині Java (JVM). Будучи модульним та легким, він спрямований на розвиток мікропослуг.

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

Vert.x, як і Node, управляє єдиним циклом подій. Але Vert.x також використовує переваги JVM. Тоді як Node працює на одному ядрі, Vert.x підтримує пул потоків розміром, який може відповідати кількості доступних ядер. Завдяки більшій підтримці паралельності, Vert.x підходить не тільки для вводу-виводу, але й для важких процесорів, які вимагають паралельних обчислень.

Проте цикли подій - це половина історії. Друга половина має мало спільного з Vert.x.

Для підключення до бази даних клієнту потрібен драйвер з'єднувача. У сфері Java найпоширенішим драйвером для Sql є JDBC. Проблема в тому, що цей драйвер блокується. І це блокування на рівні розетки. Потік завжди застряватиме там, поки не повернеться з відповіддю.

Зайве говорити, що водій був вузьким місцем при реалізації повністю неблокуючої програми. На щастя, відбувся прогрес (хоч і неофіційний) щодо асинхронного драйвера з декількома активними форками, серед них:

  • //github.com/jasync-sql/jasync-sql (для Postgres та MySql)
  • //github.com/reactiverse/reactive-pg-client (Postgres)

Золоте правило

З Vert.x досить просто працювати, і http-сервер можна створити за допомогою декількох рядків коду.

Метод requestHandler - це місце, де цикл подій доставляє подію запиту. Оскільки Vert.x невпевнений, поводження з ним здійснюється вільно. Але майте на увазі єдине важливе правило неблокуючого потоку: не блокуйте його.

Працюючи з паралельністю, ми можемо взяти з такої кількості доступних сьогодні опцій, таких як Promise, Future, Rx, а також власного ідіоматичного шляху Vert.x. Але в міру зростання складності програми, лише асинхронних функціональних можливостей недостатньо. Нам також потрібна простота координації та ланцюжка дзвінків, уникаючи пекла зворотного дзвінка, а також витончено передаючи будь-яку помилку.

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

Беруть участь три операції: перевірка параметра запиту, перевірка, чи справжній ідентифікатор, та отримання даних. Ми обернемо кожну з цих операцій у Майбутнє та координуємо виконання у структурі "для розуміння".

  • Першим кроком є ​​відповідність запиту службі. Scala має потужну функцію узгодження зразків, яку ми можемо використовувати для цієї мети. Тут ми перехоплюємо будь-які згадки про “/ user” і передаємо їх у наш сервіс.
  • Далі ядро ​​цієї послуги, де наше майбутнє розташоване в послідовному розумінні. Перша майбутня перевірка параметрів обгортання f1 . Ми спеціально хочемо отримати ідентифікатор із запиту на отримання та передати його в int. (Scala не вимагає явного повернення, якщо значення повернення є останнім рядком у методі.) Як бачите, ця операція може потенційно викликати виняток, оскільки ідентифікатор може не бути int або навіть недоступним, але наразі це нормально .
  • Другий майбутній f2 перевіряє дійсність id. Ми блокуємо будь-який ідентифікатор нижче 100, явно викликаючи Future.failed за допомогою нашого власного CustomException. В іншому випадку ми передаємо порожнє майбутнє у формі Future.unit як успішну перевірку.
  • Останнє майбутнє f3 отримує користувача з ідентифікатором, наданим f1. Оскільки це лише зразок, ми насправді не підключаємось до бази даних. Ми просто повертаємо якийсь фіктивний рядок.
  • map запускає домовленість, яка видає дані користувача з f3, а потім друкує їх у відповідь.
  • Тепер, якщо в будь-якій частині послідовності трапляється помилка, передається Throwable для відновлення . Тут ми можемо підібрати його тип до відповідної стратегії відновлення. Оглядаючись у нашому коді, ми передбачали кілька потенційних помилок, таких як відсутність ідентифікатора або ідентифікатор, який не був int або не дійсним, що призведе до конкретних винятків. Ми обробляємо кожен з них у handleException, передаючи повідомлення про помилку клієнту.

Ця схема забезпечує не тільки асинхронний потік від початку до кінця, але й чіткий підхід до обробки помилок. І оскільки це впорядковується між обробниками, ми можемо зосередитись на важливих речах, таких як запит до бази даних.

Вертикалі, шина подій та інші проблеми

Vert.x також пропонує модель паралельності, яка називається verticle, яка нагадує систему Actor. (Якщо ви хочете дізнатись більше, зверніться до мого посібника для акторів Akka.) Verticle ізолює свій стан та поведінку, щоб забезпечити безпечне для потоків середовище. Єдиний спосіб спілкування з ним - через шину подій.

Однак шина подій Vert.x вимагає, щоб її повідомлення були String або JSON. Це ускладнює передачу довільних об'єктів, не пов'язаних з POJO. А у високопродуктивній системі взаємодія з перетворенням JSON небажана, оскільки це покладає певні обчислювальні витрати. Якщо ви розробляєте додатки вводу-виводу, вам може бути краще не використовувати ні вертикаль, ні шину подій, оскільки такі програми мало потребують локального стану.

Робота з деякими компонентами Vert.x також може бути досить складною. Ви можете виявити відсутність документації, несподівану поведінку і навіть не функціонування. Vert.x, можливо, страждає від власних амбіцій, оскільки розробка нових компонентів потребує перенесення на багато мов. Це важке завдання. З цієї причини дотримуватися серцевини було б найкращим.

Якщо ви розробляєте загальнодоступний API, тоді має бути достатньо vertx-core. Якщо це веб-програма, ви можете додати vertx-web, який забезпечує обробку параметрів http та автентифікацію JWT / Session. Ці два так чи інакше домінували в орієнтирах. У деяких тестах для використання vertx-web спостерігається деяке зниження продуктивності, але, як видається, це пов'язано з оптимізацією, це може бути виправлено у наступних випусках.