Як Quarkus поєднує імперативне та реактивне програмування

Цього року ми плануємо всерйоз розвивати теми контейнерів, Cloud-Native Java и Кубернетес. Логічним продовженням цих тем буде розповідь про фреймворк Quarkus, вже розглянутому на Хабрі. Сьогоднішня стаття присвячена не так пристрою «субатомної надшвидкої Java», скільки тим перспективам, які Quarkus привносить в Enterprise.

Як Quarkus поєднує імперативне та реактивне програмування

Java і JVM як і раніше виключно популярні, але при роботі з безсерверними технологіями та хмарно-орієнтованими мікросервісами Java та інші мови для JVM застосовуються все рідше, тому що займають занадто багато місця в пам'яті і занадто повільно завантажуються, через що погано підходять для використання з короткоживучими контейнерами. На щастя, зараз ця ситуація починає змінюватися завдяки Quarkus.

Надшвидка субатомна Java вийшла на новий рівень!

42 релізи, 8 місяців роботи спільноти та 177 приголомшливих розробників – результатом цього став випуск у листопаді 2019 року Кваркус 1.0, релізу, який знаменує собою важливу віху у розвитку проекту та пропонує масу класних функцій та можливостей (докладніше про них можна прочитати в анонсі).

Сьогодні ми розповімо, як Quarkus поєднує моделі імперативного та реактивного програмування на базі єдиного реактивного ядра. Ми розпочнемо з короткого екскурсу в історію, а потім детально розберемо, у чому полягає дуалізм реактивного ядра Quarkus і як Java-Розробники можуть скористатися цими перевагами.

Мікросервіси, керовані подіями архітектури и без сервера-функції - все це сьогодні, що називається, на підйомі. З недавніх пір створення хмарно-орієнтованих архітектур стало набагато простіше і доступніше, проте проблеми залишилися - особливо Java-розробників. Наприклад, у разі serverless-функцій та мікросервісів є гостра необхідність у тому, щоб скоротити час запуску, знизити витрату пам'яті і таки зробити їхню розробку справою зручнішою та приємнішою. Java в останні роки внесла кілька покращень, на кшталт допрацьованого для контейнерів функціоналу ergonomics та ін. Однак досягти нормальної роботи Java в контейнері, як і раніше, непросто. Тому ми почнемо з того, що розглянемо деякі з внутрішніх складнощів Java, які особливо гостро виявляються при розробці контейнерно-орієнтованих Java-додатків.

Для початку звернемося до історії.

Як Quarkus поєднує імперативне та реактивне програмування

Потоки та контейнери

Починаючи з версії 8u131, Java стала більш-менш підтримувати контейнери за рахунок покращень у функціоналі ergonomics. Зокрема, тепер JVM знає, на скільки процесорних ядрах вона виконується, і може відповідним чином налаштовувати пули потоків - як правило, пули fork/join. Безумовно, це чудово, але, припустимо, у нас є традиційний веб-додаток, який використовує HTTP-сервлети і запускається в Tomcat, Jetty та ін. В результаті ця програма видасть кожному запиту окремий потік і дозволить йому блокувати цей потік при очікуванні операцій введення-виведення, наприклад, при зверненні до БД, файлів або інших сервісів. Тобто розмір такої програми залежить не від кількості доступних ядер, а від кількості одночасних запитів. Крім того, це означає, що квоти або ліміти в Kubernetes за кількістю ядер тут не надто допоможуть, і справа в результаті закінчиться тротлінгом.

Вичерпання пам'яті

Потоки – це пам'ять. І внутрішньоконтейнерні обмеження на згадку аж ніяк не панацея. Просто почніть збільшувати кількість програм і потоків, і рано чи пізно ви зіткнетеся з критичним зростанням частоти перемикань і, як наслідок, з деградацією продуктивності. Крім того, якщо програма використовує традиційні мікросервісні фреймворки або підключається до БД, або задіює кешування, або якось ще додатково витрачає пам'ять, вам цілком очевидно потрібен інструмент, що дозволяє заглянути всередину JVM і подивитися, як вона керує пам'яттю, і при цьому не вбити саму JVM (наприклад, XX: + UseCGroupMemoryLimitForHeap). І навіть незважаючи на те, що, починаючи з Java 9, JVM навчилася сприймати cgroups і відповідним чином адаптуватися, резервування та керування пам'яттю залишається досить складною справою.

Квоти та ліміти

У Java 11 з'явилася підтримка CPU-квот (на зразок PreferContainerQuotaForCPUCount). Kubernetes також пропонує підтримку лімітів та квот. Так, все це має сенс, але якщо додаток знову виходить за рамки виділеної квоти, ми знову приходимо до того, що розмір – як у випадку з традиційними Java-додатками – визначається за кількістю ядер і виділенням окремого потоку на кожен запит, то є користі від усього цього небагато.
Крім того, якщо використовувати квоти та ліміти або функції горизонтального (scale-out) масштабування платформи, що лежить в основі Kubernetes, проблема також не вирішується сама собою. Ми просто витрачаємо більше ресурсів на вирішення вихідної проблеми або в результаті приходимо до перевитрати ресурсів. А якщо це високонавантажена система в публічній загальнодоступній хмарі, ми майже напевно починаємо використовувати більше ресурсів, ніж це дійсно потрібно.

І що з цим робити?

Якщо по-простому, то використовувати асинхронні та неблокуючі бібліотеки вводу-виводу та фреймворки на кшталт Netty, Vert.x або Akka. Вони краще підходять для роботи в контейнерах через свою реактивну природу. Завдяки вводу-виводу, що не блокує, один і той же потік може обробляти відразу кілька одночасних запитів. Поки один запит чекає на результати введення-виводу, потік, що його обробляє, вивільняється і береться за інший запит. А коли результати введення-виведення нарешті надходять, обробка першого запиту продовжується. Чергуючи обробку запитів у межах того самого потоку, можна скоротити загальну кількість потоків і знизити витрата ресурсів на обробку запитів.

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

Як і це все?

Ні, є ще дещо. Реактивне програмування допомагає краще використовувати ресурси, але має свою ціну. Зокрема, код доведеться переписувати згідно з принципами неблокованості та уникати блокування потоків введення-виведення. А це зовсім інша модель розробки та виконання. І хоча тут є безліч корисних бібліотек, це все одно кардинальна зміна звичного способу мислення.

По-перше, вам потрібно навчитися писати код, який виконується асинхронно. Як тільки ви починаєте використовувати неблокуючий введення-виведення, вам потрібно явно прописувати, що має статися при отриманні відповіді на запит. Просто блокувати та чекати більше не вийде. Натомість ви можете надсилати зворотні дзвінки, використовувати реактивне програмування або continuation. Але і це ще не все: щоб використовувати неблокуючий введення-висновок, вам потрібні і сервери, що не блокують, і клієнти, і бажано скрізь. У випадку з HTTP все просто, але є ще і БД, і файлові системи, і багато іншого.

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

  1. Ефективно використовувати ресурси на найбільш навантажених напрямках програмної системи;
  2. Використовувати простіший за стилем код у її інших частинах.

Представляємо Quarkus

Власне, у цьому і є суть Quarkus – об'єднати реактивну та імперативну моделі в рамках одного середовища виконання.

В основі Quarkus лежать Vert.x і Netty, поверх яких використовується ціла низка реактивних фреймворків та розширень, покликаних допомогти розробнику. Quarkus призначений для побудови не тільки HTTP-мікросервісів, але й архітектур, що управляються подіями. Завдяки своїй реактивній природі, він дуже ефективно працює із системами обміну повідомленнями (Apache Kafka, AMQP тощо).

Вся хитрість у тому, як використовувати той самий реактивний двигун як для імперативного, так і для реактивного коду.

Як Quarkus поєднує імперативне та реактивне програмування

Quarkus із цим блискуче справляється. Вибір між імперативним та реактивним очевидний – використовувати і для того, і для іншого реактивне ядро. І з чим воно дуже допомагає, так це зі швидким кодом, що не блокує, який обробляє майже все, що проходить через потік циклу подій (event-loop thread, він же - IO thread). Але якщо у вас є класичні програми REST або програми на стороні клієнта, у Quarkus напоготові імперативна модель програмування. Наприклад, підтримка HTTP Quarkus будується на використанні неблокуючого і реактивного движка (Eclipse Vert.x і Netty). Всі HTTP-запити, отримані вашою програмою, спочатку проходять через цикл подій (IO Thread), а потім відправляються тій частині коду, яка керує запитами. Залежно від точки призначення код керування запитами може викликатися в рамках окремого потоку (так званий worker thread, застосовується у разі сервлетів та Jax-RS) або використовувати вихідний потік введення-виведення (реактивний маршрут reactive route).

Як Quarkus поєднує імперативне та реактивне програмування

Для конекторів систем передачі повідомлень використовуються неблокуючі клієнти, що працюють поверх двигуна Vert.x. Тому ви можете ефективно відправляти, отримувати та обробляти повідомлення від систем класу messaging middleware.

На сайті Quarkus.io зібрано кілька хороших посібників, які допоможуть розпочати роботу з Quarkus:

Крім того, ми підготували онлайнові практичні уроки для знайомства з різними аспектами реактивного програмування, причому для їх проходження достатньо лише браузера, ніяка IDE для цього не потрібна, та й комп'ютер не обов'язковий. Знайти ці уроки можна тут.

Корисні ресурси

10 відеоуроків з Quarkus, щоб освоїтися в темі

Як пишуть на сайті Quarkus.io, Кваркус - Це Кубернетес-орієнтований Java-стек, заточений під GraalVM і OpenJDK HotSpot і зібраний з найкращих Java-бібліотек та стандартів.

Щоб допомогти вам розібратися в темі, ми відібрали 10 відеоуроків, де висвітлюються різні аспекти Quarkus та приклади його використання:

1. Представляємо Quarkus: Java-фреймворк нового покоління для Kubernetes

Автори: Томас Кворнстром (Thomas Qvarnstrom) та Джейсон Грін (Jason Greene)
Мета проекту Quarkus полягає в тому, щоб створити Java-платформу для Kubernetes та serverless-середовищ, а також об'єднати реактивну та імперативну моделі програмування в рамках єдиного середовища виконання, щоб розробники могли гнучко варіювати підхід під час роботи з широким спектром розподілених архітектур додатків. Дізнайтеся більше з вступної лекції нижче.

2. Quarkus: надшвидка субатомна Java

Автор: Блюр Саттер (Burr Sutter)
Відеоурок з інтернет-лекторія DevNation Live демонструє, як використовувати Quarkus для оптимізації корпоративних Java-додатків, API, мікросервісів та serverless-функцій у середовищі Kubernetes/OpenShift, зробивши їх набагато меншими, швидшими та масштабованішими.

3. Quarkus і GraalVM: розганяємо Hibernate до надшвидкостей і тиснемо до субатомних розмірів

Автор: Сейн Гріноверо (Sanne Grinovero)
З презентації ви дізнаєтеся, як з'явився Quarkus, як він працює і як дозволяє зробити комплексні бібліотеки на кшталт Hibernate ORM, сумісними з native-образами GraalVM.

4. Вчимося розробляти serverless-додатки

Автор: Мартін Лютер (Marthen Luther)
У відео нижче показано, як створити простий Java-додаток за допомогою Quarkus і розгорнути його як serverless-додаток на Knative.

5. Quarkus: кодуйте із задоволенням

Автор: Едсон Янага (Edson Yanaga)
Відегайд зі створення вашого першого проекту Quarkus, що дозволяє зрозуміти чому Quarkus завойовує серця розробників.

6. Java та контейнери – яким буде їхнє спільне майбутнє

Автор: Марк Літтл (Mark Little)
Ця презентація знайомить з історією Java та пояснює, чому Quarkus – це майбутнє Java.

7. Quarkus: надшвидка субатомна Java

Автор: Дмитріс Адреандіс (Dimitris Andreadis)
Огляд переваг Quarkus, які отримали визнання розробників: простота, надвисокі швидкості, найкращі бібліотеки та стандарти.

8. Quarkus та субатомні реактивні системи

Автор: Клемент Ескоф'єр (Clement Escoffier)
Завдяки інтеграції з GraalVM Quarkus забезпечує надшвидкий досвід розробки та субатомне середовище виконання. Автор говорить про реактивну сторону Quarkus і про те, як їй користуватися при створенні реактивних програм та програм з потоковою передачею даних.

9. Quarkus та швидка розробка додатків у Eclipse MicroProfile

Автор: Джон Клінган (John Clingan)
Поєднуючи Eclipse MicroProfile і Quarkus, розробники можуть створювати повнофункціональні контейнерні програми MicroProfile, які запускаються за якісь десятки мілісекунд. У відео докладно розуміється, як кодувати контейнерну програму MicroProfile для розгортання на платформі Kubernetes.

10. Java, версія «Турбо»

Автор: Маркус Біль (Marcus Biel)
Автор показує, як використовувати Quarkus для створення супермаленьких та супершвидких Java-контейнерів, що дозволяють зробити справжній прорив, особливо в serverless-середовищах.



Джерело: habr.com

Додати коментар або відгук