В эпоху контейнеров («эпоху Docker») Java все еще на высоте, но что лучше? Spring Boot или Quarkus?

Кто победит? Spring Boot или Quarkus.
Кто победит? Spring Boot или Quarkus.

В эпоху контейнеров («эпоху Docker») Java все еще жив, борясь за это или нет. Java всегда славилась своей производительностью, в основном из-за уровней абстракции между кодом и реальной машиной, стоимостью многоплатформенности (писать один раз, запускать где угодно - помните это?), С JVM в -between (JVM: программная машина, имитирующая то, что делает реальная машина).

В настоящее время с микросервисной архитектурой, возможно, больше нет смысла или каких-либо преимуществ создавать что-то мультиплатформенное (интерпретируемое) для чего-то, что всегда будет работать на одном месте и на одной платформе (контейнер Docker - среда Linux). Переносимость сейчас менее актуальна (может быть, больше, чем когда-либо), дополнительный уровень абстракции не важен.

Учитывая это, давайте проведем простое и грубое сравнение двух альтернатив для создания микросервисов на Java: очень хорошо известного Spring Boot и не очень известного (пока что) Quarkus.

Противники

Что такое Quarkus?

Набор технологий с открытым исходным кодом, адаптированных к GraalVM и HotSpot для написания приложений Java. Он предлагает (обещает) сверхбыстрое время запуска и меньший объем памяти. Это делает его идеальным для контейнеров и бессерверных рабочих нагрузок. Он использует микропрофиль Eclipse (JAX-RS, CDI, JSON-P), подмножество Java EE для создания микросервисов.

GraalVM - универсальная и многоязычная виртуальная машина (JavaScript, Python, Ruby, R, Java, Scala, Kotlin). GraalVM (в частности Substrate VMделает возможной предварительную компиляцию (AOT), преобразуя байт-код в нативный машинный код, в результате чего получается двоичный файл, который может выполняться нативно.

Имейте в виду, что не все функции доступны в нативном исполнении, компиляция AOT имеет свои ограничения. Обратите внимание на это предложение (цитируется команда GraalVM):

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

Так, например, Reflection и Java Native Interface (JNI) не будут работать, по крайней мере, из коробки (требуется дополнительная работа). Вы можете найти список ограничений в документе Native Image Java Limitations.

Что такое Spring Boot?

Действительно? Ну, просто чтобы сказать что-то (не стесняйтесь пропустить это) одним предложением: Spring Boot, созданный на основе Spring Framework, представляет собой среду с открытым исходным кодом, которая предлагает гораздо более простой способ создания, настройки и запуска веб-приложений Java. Сделать его хорошим кандидатом для микросервисов.

Подготовка к битве - создание Docker образов

Образ Quarkus

Давайте создадим приложение Quarkus, чтобы позже обернуть его в образ Docker. По сути, мы будем делать то же самое, что и руководство по началу работы с Quarkus.

Создание maven проекта с архетипом Quarkus:

mvn io.quarkus:quarkus-maven-plugin:1.0.0.CR2:create \ 
    -DprojectGroupId=ujr.combat.quarkus \  
    -DprojectArtifactId=quarkus-echo \ 
    -DclassName="ujr.combat.quarkus.EchoResource" \
    -Dpath="/echo"

В результате получим такую структуру нашего проекта:

Обратите внимание, что также были созданы два образца Dockerfile (src/main/docker): один для обычного образа приложения JVM, а другой - для образа нативного приложения.

В сгенерированном коде мы должны изменить только одну вещь, добавить зависимость ниже, потому что мы хотим сгенерировать контент JSON.

<dependency> 
   <groupId>io.quarkus</groupId> 
   <artifactId>quarkus-resteasy-jsonb</artifactId> 
</dependency>

Quarkus использует спецификацию JAX-RS на протяжении всей реализации проекта RESTEasy.

Вот наше приложение «целиком»:

Вот и все, с помощью следующей команды мы сможем увидеть, как работает приложение:

mvn clean compile quarkus:dev

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

curl -sw "\n\n" http://localhost:8080/echo/ualter | jq .

Теперь, когда мы увидели, что он работает, давайте создадим образ Docker. Загрузите GraalVM отсюда: https://github.com/graalvm/graalvm-ce-builds/releases.

Важно! Не скачивайте последнюю версию GraalVM 19.3.0Quarkus 1.0 с ней не совместим, возможно, Quarkus 1.1 будет. Сейчас должна работать версия GraalVM 19.2.1, скачайте ее.

Настройте его домашний путь в переменной среды:

## В macOS будет: export 
GRAALVM_HOME=/Users/ualter/Developer/quarkus/graalvm-ce-java8-19.2.1/Contents/Home/

Затем установите нативный образ для GraalVM в своей среде:

$GRAALVM_HOME/bin/gu install native-image

Сгенерируем нативную версию для текущей платформы (в этом случае будет сгенерирован нативный исполняемый файл для macOS).

mvn package -Pnative

Если все работает нормально, мы найдем файл с именем quarkus-echo-1.0-SNAPSHOT-runnerвнутри папки ./target. Это исполняемый двоичный код вашего приложения, и вы можете просто запустить его выполнив команду: ./target/quarkus-echo-1.0-SNAPSHOT-runner. Нет необходимости использовать JVM (обычное:  java -cp app:lib/*:...App.jar), это нативный исполняемый двоичный файл.

Давайте создадим нативный Docker образ для нашего приложения. Эта команда создаст нативный образ, то есть Docker образ с нативным исполняемым Linux приложением. По умолчанию нативный исполняемый файл создается на основе текущей платформы (macOS), поскольку мы знаем, что полученный исполняемый файл не является той платформой, которая будет контейнером (Linux), мы проинструктируем сборку Maven создать исполняемый файл из внутри контейнера, генерируя нативный Docker образ:

mvn package -Pnative -Dquarkus.native.container-build=true

На этом этапе убедитесь, что у вас есть среда выполнения контейнера Docker, рабочая среда.

Файл будет 64-битным исполняемым файлом Linux, поэтому, естественно, этот двоичный файл не будет работать в нашей macOS, он был создан для нашего образа контейнера докеров. Итак, двигаясь вперед... перейдем к созданию Docker образа...

docker build -t ujr/quarkus-echo -f src/main/docker/Dockerfile.native . 
  ## Тестирование ... 
  docker run -i --name quarkus-echo --rm -p 8081:8081 ujr/quarkus-echo

Примечание о размере образа Docker:

Окончательный Docker образ был 115 МБ, но вы можете получить крошечный Docker образ, используя версию образа без дистрибутива. Образы без дистрибутива содержат только ваше приложение и его зависимости времени выполнения, все остальное (менеджеры пакетов, оболочки или обычные программы, которые обычно встречаются в стандартном дистрибутиве Linux) удаляется. Образ нашего приложения без дистрибутива имеет размер 42,3 МБ. В файле   ./src/main/docker/Dockerfile.native-distroless есть квитанция на его изготовление.

Об образах без дистрибутивов: «Ограничение содержимого вашего контейнера времени выполнения только тем, что необходимо для вашего приложения, - это лучший метод, применяемый Google и другими техническими гигантами, которые уже много лет используют контейнеры в продакшн»

Образ Spring Boot

На данный момент, наверное, каждый знает, как создать обычный Docker образ Spring Boot, давайте пропустим детали, согласны? Одно важное замечание: код точно такой же. Проще говоря, почти то же самое, потому что мы, конечно же, используем аннотации Spring фреймворка. Это единственная разница. Вы можете проверить каждую деталь в предоставленном исходном коде (ссылка внизу).

mvn install dockerfile:build 
## Тестирование ...
  docker run --name springboot-echo --rm -p 8082:8082 ujr/springboot-echo

Сражение

Давайте запустим оба контейнера, запустим их и запустим пару раз и сравним время запуска и объем памяти. В этом процессе каждый из контейнеров создавался и уничтожался 10 раз. Позже было проанализировано время их запуска и объем памяти. Приведенные ниже числа - это средние результаты, полученные по всем этим тестам.

Время запуска

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

Что касается бессерверной архитектуры, в этой модели обычно временный контейнер запускается событием для выполнения задачи/функции. В облачных средах цена обычно зависит от количества выполнений, а не от ранее приобретенных вычислительных мощностей. Итак, здесь холодный запуск может повлиять на этот тип решения, поскольку контейнер (обычно) будет активен только на время выполнения своей задачи.

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

Насколько более неожиданным будет сценарий (необходимо и быстро), хуже может быть длительный холодный запуск.

Посмотрим, как они себя показали в отношении времени запуска:

Что ж, вы могли заметить, что это еще одна протестированная опция, вставленная в график времени запуска. Фактически, это то же самое приложение Quarkus, но сгенерированное с помощью образа JVM Docker (с использованием файла Dockerfile.jvm). Как мы видим, даже приложение, которое использует образ Docker с JVM, приложение Quarkus имеет более быстрое время запуска, чем Spring Boot.

Излишне говорить, что приложение Quarkus Native, безусловно, является победителем, оно на сегодняшний день является самым быстрым из всех, чтобы запустить его.

Объем памяти

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

Заключение

Подводя итог всего лишь в одном видении, вот что мы получили, глядя на результаты в Linux Ubuntu:

Похоже, что Quarkus выиграл битву в этих двух раундах (время запуска и объем памяти), одолев своего оппонента SpringBoot с явным преимуществом.

Это может заставить нас задуматься ... возможно, пришло время подумать о некоторых реальных лабораториях, опыте и некоторых попытках с Quarkus. Мы должны увидеть, как он работает в реальной жизни, как он вписывается в наш бизнес-сценарий и в чем он будет наиболее полезен.

Но давайте не будем забывать о минусах, как мы видели выше, некоторые функции JVM не могли работать (пока/легко) в нативных исполняемых двоичных файлах. В любом случае, наверное, пора дать Кваркусу шанс проявить себя, особенно если проблема холодного старта вас беспокоит. Как насчет того, чтобы заставить работать один или два Pod (K8s) с Quarkus в окружающей среде, будет интересно посмотреть, как он будет работать через некоторое время, не так ли?

Исходный код на GitHub.

Дальнейшее чтение

Thoughts on Quarkus

Spring Boot: The Most Notable Features You Should Know

Configuring a Quarkus Application

Комментарии (8)