Как Quarkus объединяет императивное и реактивное программирование

    В этом году мы планируем всерьез развивать темы контейнеров, Cloud-Native Java и Kubernetes. Логичным продолжением этих тем будет рассказ о фреймворке Quarkus, уже рассмотренном на Хабре. Сегодняшняя статья посвящена не столько устройству «субатомной сверхбыстрой Java», сколько тем перспективам, которые Quarkus привносит в Enterprise. (Кстати, смотрите наш вебинар «Это Quarkus – Kubernetes native Java фреймворк». Покажем, как начать «с нуля» или перенести готовые решения)



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

    Сверхбыстрая субатомная Java вышла на новый уровень!


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

    Сегодня мы расскажем, как Quarkus объединяет модели императивного и реактивного программирования на базе единого реактивного ядра. Мы начнем с краткого экскурса в историю, а затем детально разберем, в чем заключается дуализм реактивного ядра Quarkus и как Java-разработчики могут воспользоваться этими преимуществами.

    Микросервисы, управляемые событиями архитектуры и serverless-функции – все это сегодня, что называется, на подъеме. С недавних пор создание облачно-ориентированных архитектур стало гораздо проще и доступнее, однако проблемы остались – особенно у Java-разработчиков. Например, в случае serverless-функций и микросервисов есть острая необходимость в том, чтобы сократить время запуска, снизить расход памяти и таки сделать их разработку делом более удобным и приятным. Java в последние годы внесла несколько улучшений, вроде доработанного для контейнеров функционала ergonomics и проч. Однако добиться нормальной работы Java в контейнере по-прежнему непросто. Поэтому мы начнем с того, что рассмотрим некоторые из внутренних сложностей Java, которые особенно остро проявляются при разработке контейнерно-ориентированных Java-приложений.

    Для начала обратимся к истории.


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


    Начиная с версии 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 с этим блестяще справляется. Выбор между императивным и реактивным очевиден – использовать и для того, и для другого реактивное ядро. И с чем оно очень помогает, так это с быстрым неблокирующим кодом, который обрабатывает почти все, что проходит через поток цикла событий (event-loop thread, он же – IO thread). Но если у вас есть классические приложения REST или приложения на стороне клиента, у Quarkus наготове императивная модель программирования. Например, поддержка HTTP в Quarkus строится на использовании неблокирующего и реактивного движка (Eclipse Vert.x и Netty). Все HTTP-запросы, получаемые вашим приложением, вначале проходят через цикл событий (IO Thread), а затем отправляются той части кода, которая управляет запросами. В зависимости от точки назначения код управления запросами может вызываться в рамках отдельного потока (так называемый worker thread, применяется в случае сервлетов и Jax-RS) или же использовать исходный поток ввода-вывода (реактивный маршрут reactive route).



    Для коннекторов систем передачи сообщений используются неблокирующие клиенты, работающие поверх движка Vert.x. Поэтому вы можете эффективно отправлять, получать и обрабатывать сообщения от систем класса messaging middleware.

    На сайте Quarkus.io собрано несколько хороших руководств, которые помогут начать работу с Quarkus:


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

    Полезные ресурсы




    10 видеоуроков по Quarkus, чтобы освоиться в теме


    Как пишут на сайте Quarkus.io, Quarkus – это Kubernetes-ориентированный 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-средах.



    Серию публикацию про Quarkus продолжит второй пост, в котором поговорим о важности нативной компиляции в этом фреймворке. Переходите по ссылке: habr.com/ru/company/redhatrussia/blog/495212
    Red Hat
    Программные решения с открытым исходным кодом

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое