Количество облачных приложений баз данных Java растет с каждой минутой. Многие организации развертывают сотни, если не тысячи экземпляров микросервиса. Тем не менее, большинство приложений несут поразительное количество ненужных накладных расходов по отношению к среде выполнения. Это, в свою очередь, делает приложение медленным и более дорогим для запуска.
В этой статье я покажу, как написать приложение базы данных, которое в 10 раз меньше обычного (*). Требуемое хранилище будет около 32 МБ вместо обычных (*) ~ 300 МБ, принимая во внимание как приложение, сторонние библиотеки и среду выполнения Java. В качестве бонуса необходимая оперативная память для запуска приложения также будет уменьшена на 25%.
(*) Это требования к хранилищу для следующих полных JDK (исключая приложение и сторонние библиотеки):
|
1
2
3
|
jdk.8.0_191 360 MBjdk-9.0.4 504 MBadoptopenjdk-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» в базе данных.
В конце мы:
- Создайте
StreamFilmлиц - Отфильтровать
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 minCHICAGO NORTH 185 minPOND SEATTLE 185 minTHEORY MERMAID 184 minCONSPIRACY SPIRIT 184 minFRONTIER CABIN 183 minREDS POCUS 182 minHOTEL HAPPINESS 181 minJACKET FRISCO 181 minMIXED 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-samplescd 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/bashSPEEDMENT_VERSION=3.2.1JDBC_VERSION=8.0.18OUTPUT=customjreecho "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.annotationcom.speedment.common.functioncom.speedment.common.injectorcom.speedment.common.invariantcom.speedment.common.jsoncom.speedment.common.jvm_versioncom.speedment.common.loggercom.speedment.common.mapstreamcom.speedment.common.tuplecom.speedment.runtime.applicationcom.speedment.runtime.computecom.speedment.runtime.configcom.speedment.runtime.connector.mysqlcom.speedment.runtime.corecom.speedment.runtime.fieldcom.speedment.runtime.typemappercom.speedment.runtime.welcomejava.basejava.loggingjava.managementjava.namingjava.prefsjava.rmijava.security.sasljava.sqljava.transaction.xajava.xmlmicroservice.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 minCHICAGO NORTH 185 minPOND SEATTLE 185 minTHEORY MERMAID 184 minCONSPIRACY SPIRIT 184 minFRONTIER CABIN 183 minREDS POCUS 182 minHOTEL HAPPINESS 181 minJACKET FRISCO 181 minMIXED 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 (java.base@11.0.5) 2: 2055 1639408 [I (java.base@11.0.5) 3: 4234 511568 java.lang.Class (java.base@11.0.5) 4: 21233 509592 java.lang.String (java.base@11.0.5) 5: 196 270552 [C (java.base@11.0.5) 6: 4181 245400 [Ljava.lang.Object; (java.base@11.0.5) 7: 4801 153632 java.util.concurrent.ConcurrentHashMap$Node (java.base@11.0.5) 8: 3395 135800 java.util.LinkedHashMap$Entry (java.base@11.0.5)…1804: 1 16 sun.util.resources.cldr.provider.CLDRLocaleDataMetaInfo (jdk.localedata@11.0.5)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 — их собственные. |