Количество облачных приложений баз данных Java растет с каждой минутой. Многие организации развертывают сотни, если не тысячи экземпляров микросервиса. Тем не менее, большинство приложений несут поразительное количество ненужных накладных расходов по отношению к среде выполнения. Это, в свою очередь, делает приложение медленным и более дорогим для запуска.
В этой статье я покажу, как написать приложение базы данных, которое в 10 раз меньше обычного (*). Требуемое хранилище будет около 32 МБ вместо обычных (*) ~ 300 МБ, принимая во внимание как приложение, сторонние библиотеки и среду выполнения Java. В качестве бонуса необходимая оперативная память для запуска приложения также будет уменьшена на 25%.
(*) Это требования к хранилищу для следующих полных JDK (исключая приложение и сторонние библиотеки):
1
2
3
|
jdk.8.0_191 360 MB jdk-9.0.4 504 MB adoptopenjdk-11 298 MB |
Использование ORM, поддерживающего микросервисы
Большинство традиционных ORM не поддерживают инкапсуляцию модулей Java. Часто это влечет за собой отправку большого количества ненужного кода.
В этой статье я буду использовать потоковый Java ORM Speedment с открытым исходным кодом , который в своей последней версии поддерживает модульную систему платформы Java (JPMS). Это позволяет нам создавать оптимизированную пользовательскую среду выполнения Java (JRE, части JDK, необходимые для запуска приложений) только с модулями, явно используемыми нашим приложением.
Прочтите о новых возможностях Speedment 3.2 в этой статье .
Приложение
Все приложение, которое мы хотим развернуть в этой статье, находится как
проект с открытым исходным кодом на GitHub в подкаталоге « microservice-jlink
». Он подключается к общедоступному экземпляру базы данных MySQL «Sakila» (содержащей данные о фильмах), размещенной в облаке, и перечисляет десять самых длинных фильмов с рейтингом «PG-13» на консоли. Модель данных предварительно настроена для соответствия структуре данных этой базы данных. Если вы хотите создать свое собственное приложение, используя другую базу данных, посетите инициализатор Speedment, чтобы сконфигурировать проект для этой базы данных.
main
метод приложения выглядит так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public final class Main { public static void main(String[] args) { final Speedment app = new SakilaApplicationBuilder() .withPassword( "sakila" ) .build(); final FilmManager films = app.getOrThrow(FilmManager. class ); System.out.println( "These are the ten longest films rated as PG-13:" ); films.stream() // 1 .filter(Film.RATING.equal( "PG-13" )) // 2 .sorted(Film.LENGTH.reversed()) // 3 .limit( 10 ) // 4 .map(film -> String.format( // 5 "%-18s %d min" , film.getTitle(), film.getLength().orElse( 0 )) ) .forEach(System.out::println); // 6 } } |
Сначала мы передаем пароль базы данных в построитель Speedment (Speedment никогда не хранит пароли внутри себя). Построитель предварительно настроен на IP-адрес базы данных, порт и т. Д. Из файла конфигурации.
Затем мы получаем FilmManager
который позже можно использовать для создания потоков Java, которые непосредственно соответствуют таблице «film» в базе данных.
В конце мы:
- Создайте
Stream
Film
лиц - Отфильтровать
Film
с рейтингом «PG-13» - Сортировка оставшихся фильмов в обратном порядке (самый длинный сначала)
- Ограничивает поток первых 10 фильмов
- Отображает каждую сущность фильма в
String
с названием фильма и длиной фильма - Печатает каждую
String
в консоли
Само приложение очень легко понять. Также следует отметить, что Speedment отобразит поток Java в SQL под капотом, как показано ниже:
1
2
3
4
5
6
7
8
|
SELECT `film_id`,`title`,`description`,`release_year`, `language_id`,`original_language_id`,`rental_duration`,`rental_rate`, `length`,`replacement_cost`,`rating`,`special_features`,`last_update` FROM `sakila`.`film` WHERE (`rating` = ? COLLATE utf8_bin) ORDER BY `length`IS NOT NULL, `length` DESC LIMIT ?, values:[PG-13, 10] |
Это означает, что из базы данных извлекаются только нужные объекты фильма.
При работе непосредственно под IDE выдается следующий вывод:
01
02
03
04
05
06
07
08
09
10
11
|
These are the ten longest films rated as PG- 13 : GANGS PRIDE 185 min CHICAGO NORTH 185 min POND SEATTLE 185 min THEORY MERMAID 184 min CONSPIRACY SPIRIT 184 min FRONTIER CABIN 183 min REDS POCUS 182 min HOTEL HAPPINESS 181 min JACKET FRISCO 181 min MIXED DOORS 180 min |
Это выглядит идеально.
Модуляризация проекта
Чтобы использовать модули, нам нужно работать под Java 9 или выше, и в нашем проекте должен быть файл module-info.java
:
1
2
3
4
|
module microservice.jlink { requires com.speedment.runtime.application; requires com.speedment.runtime.connector.mysql; // (*) } |
Модуль com.speedment.runtime.application
является базовым модулем, который всегда нужен любому приложению Speedment.
(*) В зависимости от типа базы данных, вы должны заменить модуль MySQL соответствующим модулем для вашей базы данных. Прочитайте все о различных модулях подключения базы данных здесь .
Строим проект
Как упоминалось ранее, полный проект доступен на GitHub . Вот как вы это получаете:
Перейдите в соответствующий подпроект:
1
2
|
cd user-guide-code-samples cd microservice-jlink |
Создайте проект (вы должны использовать Java 9 или выше из-за модульной системы):
1
|
mvn clean install |
Пользовательский сценарий сборки JRE
Проект также содержит пользовательский сценарий сборки JRE с именем build_jre.sh
содержащий следующие команды:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
#!/bin/bash SPEEDMENT_VERSION=3.2.1 JDBC_VERSION=8.0.18 OUTPUT=customjre echo "Building $OUTPUT..." MODULEPATH=$(find ~/.m2/repository/com/speedment/runtime -name "*.jar" \ | grep $SPEEDMENT_VERSION.jar | xargs echo | tr ' ' ':') MODULEPATH=$MODULEPATH:$(find ~/.m2/repository/com/speedment/common -name "*.jar" \ | grep $SPEEDMENT_VERSION.jar | xargs echo | tr ' ' ':') MODULEPATH=$MODULEPATH:$(find . -name "*.jar" | xargs echo | tr ' ' ':') $JAVA_HOME/bin/jlink \ --no-header-files \ --no-man-pages \ --compress=2 \ --strip-debug \ --module-path "$JAVA_HOME\jmods:$MODULEPATH" \ --add-modules microservice.jlink,java.management,java.naming,java.rmi,java.transaction.xa \ --output $OUTPUT |
Вот как работает скрипт:
После установки различных параметров скрипт создает путь к модулю, добавляя speedment/runtime
speedment/common
директорий speedment/runtime
и speedment/common
. Даже если мы добавим их все, система модулей позже выяснит, какие из них фактически используются, и отбросит другие. Последняя строка с MODULEPATH
добавит JAR-файл самого приложения.
После того, как все параметры были установлены, мы вызываем команду jlink
которая создаст пользовательский JRE. Я использовал несколько (необязательных) флагов, чтобы уменьшить размер целевой JRE. Поскольку драйвер JDBC не поддерживает JPMS, я вручную добавил некоторые модули, которые необходимы драйверу, в параметре --add-modules
.
Сборка ультракомпактной JRE
Вооружившись приведенным выше сценарием, мы можем создать сверхкомпактную пользовательскую JRE для нашего приложения облачной базы данных с помощью одной команды:
1
|
./build_jre.sh |
На моем старом MacBook Pro сборка занимает около 5 секунд. Мы можем проверить общий размер JRE / приложения с помощью этой команды:
1
|
du -sh customjre/ |
Это даст следующий результат:
1
|
32M customjre/ |
Потрясающий результат! У нас есть полноценная JVM с сборщиком мусора, JIT-компилятором, всеми библиотеками (кроме драйвера JDBC) и само приложение, упакованное всего в 32 МБ!
Мы можем сравнить это с самим JDK по его невосстановленному размеру, который часто используется в качестве базового уровня для экземпляров облака.
1
|
du -sh $JAVA_HOME |
Это даст следующий вывод на моем ноутбуке:
1
|
298M /Library/Java/JavaVirtualMachines/adoptopenjdk- 11 .jdk/Contents/Home/ |
И эта цифра даже не включает приложение или какие-либо сторонние библиотеки. Таким образом, мы сократили требования к хранилищу с коэффициентом около 10!
Фактически используемые модули
Чтобы увидеть, какие модули прошли через процесс сокращения, мы можем выполнить следующую команду:
1
|
cat customjre/release |
На моем компьютере будет получен следующий вывод (переформатированный и отсортированный для ясности):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
JAVA_VERSION="11.0.5" MODULES=" com.speedment.common.annotation com.speedment.common.function com.speedment.common.injector com.speedment.common.invariant com.speedment.common.json com.speedment.common.jvm_version com.speedment.common.logger com.speedment.common.mapstream com.speedment.common.tuple com.speedment.runtime.application com.speedment.runtime.compute com.speedment.runtime.config com.speedment.runtime.connector.mysql com.speedment.runtime.core com.speedment.runtime.field com.speedment.runtime.typemapper com.speedment.runtime.welcome java.base java.logging java.management java.naming java.prefs java.rmi java.security.sasl java.sql java.transaction.xa java.xml microservice.jlink " |
Итак, все неиспользуемые модули Java (такие как javax.crypto
) не были включены в пользовательскую среду выполнения.
Запуск приложения
Приложение может быть запущено с использованием пользовательского JRE следующим образом:
1
|
customjre/bin/java --class-path ~/.m2/repository/mysql/mysql-connector-java/8.0.18/mysql-connector-java-8.0.18.jar -m microservice.jlink/com.speedment.example.microservices.jlink.Main |
Файл mysql-connector-java-8.0.18.jar
был автоматически загружен Maven в его локальный репозиторий при первой mysql-connector-java-8.0.18.jar
проекта (т. mvn clean install
). Поскольку драйвер JDBC MySQL еще не совместим с модульной системой платформы Java, нам пришлось приклеивать его вручную.
При запуске программа выдает тот же результат, что и выше, но из среды выполнения, которая была в 10 раз меньше:
01
02
03
04
05
06
07
08
09
10
11
|
These are the ten longest films rated as PG-13: GANGS PRIDE 185 min CHICAGO NORTH 185 min POND SEATTLE 185 min THEORY MERMAID 184 min CONSPIRACY SPIRIT 184 min FRONTIER CABIN 183 min REDS POCUS 182 min HOTEL HAPPINESS 181 min JACKET FRISCO 181 min MIXED DOORS 180 min |
Использование памяти
Возможно, более важный вопрос заключается в том, сколько памяти приложения (RSS) в целом используется облачным приложением. Беглый взгляд на это показывает, что использование динамической памяти также уменьшается:
Стандартный JDK
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
Pers-MBP:speedment pemi$ jmap -histo 38715 num #instances #bytes class name (module) ------------------------------------------------------- 1: 25836 3036560 [B ([email protected]) 2: 2055 1639408 [I ([email protected]) 3: 4234 511568 java.lang.Class ([email protected]) 4: 21233 509592 java.lang.String ([email protected]) 5: 196 270552 [C ([email protected]) 6: 4181 245400 [Ljava.lang.Object; ([email protected]) 7: 4801 153632 java.util.concurrent.ConcurrentHashMap$Node ([email protected]) 8: 3395 135800 java.util.LinkedHashMap$Entry ([email protected]) … 1804: 1 16 sun.util.resources.cldr.provider.CLDRLocaleDataMetaInfo ([email protected]) Total 137524 7800144 |
Custom JRE
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
Pers-MBP:speedment pemi$ jmap -histo 38783 | head num #instances #bytes class name (module) ------------------------------------------------------- 1 : 22323 1714608 [B (java.base @11 .0. 5 ) 2 : 4229 511000 java.lang.Class (java.base @11 .0. 5 ) 3 : 19447 466728 java.lang.String (java.base @11 .0. 5 ) 4 : 1776 424408 [I (java.base @11 .0. 5 ) 5 : 69 264656 [C (java.base @11 .0. 5 ) 6 : 4044 240128 [Ljava.lang.Object; (java.base @11 .0. 5 ) 7 : 4665 149280 java.util.concurrent.ConcurrentHashMap$Node (java.base @11 .0. 5 ) 8 : 3395 135800 java.util.LinkedHashMap$Entry (java.base @11 .0. 5 ) … 1726 : 1 16 sun.util.resources.LocaleData$LocaleDataStrategy (java.base @11 .0. 5 ) Total 102904 5727960 |
Улучшение кучи
Использование кучи было уменьшено с 7800444 до 5727960 байт (сокращение более чем на 25%)!
Примечание: перед тем, как jmap
команду jmap
, я позволил приложению предложить явный jmap
мусора и подождать несколько секунд, чтобы выровнять любые различия, вызванные возможными более ранними вызовами сборщика мусора.
обзор
Вот диаграмма, которая показывает разницу в требованиях к хранилищу (чем ниже, тем лучше):
Вот еще один график, показывающий разницу в использовании ОЗУ (чем ниже, тем лучше):
Изменение кода
Если вы хотите изменить код, вам необходимо перестроить приложение после внесения изменений с помощью:
1
|
mvn clean install |
а затем удалите старый customjre
и создайте новый:
1
2
|
rm -rf customjre/ ./build_jre.sh |
Создание вашего собственного приложения базы данных
Если вы хотите подключиться к своей собственной базе данных и написать свою собственную логику приложения, вы можете легко выбрать, какие таблицы и столбцы вы хотите использовать, а затем автоматически сгенерировать собственную модель домена Java и конструктор приложений с помощью Speedment Tool:
Инструмент может быть добавлен в ваш проект в файле pom.xml
и вызван
mvn speedment:tool
. Посетите инициализатор Speedment, чтобы создать собственный файл pom.xml
и шаблон приложения.
Процесс можно упростить с помощью сценариев автоматической сборки Maven, которые будут определять любые зависимости приложений и автоматическую генерацию экземпляров Docker, которые могут быть развернуты сразу после автоматической сборки. Я напишу больше об этом в следующих статьях.
Выводы
Система платформ Java (JPMS) позволяет создавать высоко оптимизированные JRE, подходящие для облачного развертывания.
Можно уменьшить требования к памяти и памяти.
Традиционные ORM не соблюдают полную инкапсуляцию Java-модуля
Ускоренный поток с открытым исходным кодом Stream ORM поддерживает JPMS и может использоваться для создания высокоэффективных облачных приложений баз данных
Ресурсы
Основы о модулях JPMS
Ускорение на GitHub
Инициализатор Speedment, способный генерировать шаблоны проекта pom.xml
См. Оригинальную статью здесь: Java: Как создать легкие микросервисы базы данных
Мнения авторов .NET Code Geeks — их собственные. |