Статьи

Java: как создать легковесные базы данных микросервисов

Количество облачных приложений баз данных 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» в базе данных.

В конце мы:

  1. Создайте Stream Film лиц
  2. Отфильтровать Film с рейтингом «PG-13»
  3. Сортировка оставшихся фильмов в обратном порядке (самый длинный сначала)
  4. Ограничивает поток первых 10 фильмов
  5. Отображает каждую сущность фильма в String с названием фильма и длиной фильма
  6. Печатает каждую 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 (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:

Инструмент Speedment, используемый в проекте, продемонстрирован в этой статье.

Инструмент может быть добавлен в ваш проект в файле 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 — их собственные.