Статьи

Реактивное программирование и реляционные базы данных

Императивный код питает потоки в темпе входящих запросов, в то время как программное обеспечение поглощает мир . В этом посте обсуждаются предположения о реактивном программировании на JVM и что это означает для интеграций — в частности, реляционных баз данных.

Мотивом для создания поста является постоянный рост принятия реактивного программирования, в то время как некоторые основные строительные блоки еще не доступны — в частности, вопрос: как насчет реляционных баз данных?

Что такое реактивное программирование

Есть много ответов о том, что такое Reactive Programming и как это сравнивается с Reactive Systems . Реактивное программирование я рассматриваю как модель программирования, которая способствует масштабируемости и стабильности путем создания управляемых событиями неблокирующих функциональных конвейеров, которые реагируют на доступность и обрабатываемость ресурсов. Отложенное выполнение, параллелизм и асинхронность являются лишь следствием базовой модели программирования.

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

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

Если мы посмотрим на реактивную экосистему, мы обнаружим несколько структур, библиотек и интеграций. У каждого из них есть свои сильные стороны. Многие функциональные области хорошо охвачены либо общим подходом, либо в контексте конкретной реактивной структуры. Итак, давайте обсудим интеграцию реляционных баз данных.

Реляционные базы данных и реактивные

Не секрет, что реляционные базы данных популярны, и, по-видимому, большинство корпоративных проектов в значительной степени зависят от использования реляционных баз данных. В любом случае, наиболее часто задаваемый вопрос: когда мы доберемся до API для реактивной интеграции реляционных баз данных?

Java использует JDBC в качестве основной технологии для интеграции с реляционными базами данных. JDBC имеет блокирующий характер — нет ничего разумного, что можно было бы сделать, чтобы смягчить блокирующий характер JDBC. Первая идея о том, как сделать вызовы неблокирующими, заключается в разгрузке вызовов JDBC для Executor (обычно пула Thread ). Хотя этот подход несколько работает, он имеет несколько недостатков, которые пренебрегают преимуществами модели реактивного программирования.

Пулы потоков требуют — не удивительно — потоки для запуска. Реактивные среды выполнения обычно используют ограниченное количество потоков, соответствующих количеству ядер ЦП. Дополнительные потоки вносят накладные расходы и уменьшают эффект ограничения потока. Кроме того, вызовы JDBC обычно накапливаются в очереди, и как только потоки насыщаются запросами, пул снова блокируется. Так что JDBC сейчас не вариант.

Реактивные усилия базы данных

Есть пара независимых драйверов, таких как Reactiverse’s реактивный-pg-клиент . Эти драйверы поставляются с API-интерфейсом конкретного производителя и не очень подходят для более широкого применения. Клиентские интеграторы должны будут предоставить дополнительные уровни для предоставления общего API. Новые драйверы не могут быть легко подключены к клиентской библиотеке. В отличие от этого, наличие стандартного API обеспечит возможность подключения, в то же время отделяя клиентов от решений для конкретных баз данных — огромное значение для всех.

Oracle анонсировала ADBA , которая является инициативой по предоставлению стандартизированного API для асинхронного доступа к базам данных в Java с использованием фьючерсов. Все в ADBA находится в стадии разработки, и команда ADBA рада получить обратную связь. Группа сотрудников Postgres работает над драйвером Postgres ADBA, который можно использовать для первых экспериментов.

Доступность ADBA неизвестна. Это определенно не идет с Java 12. Версия Java, в которой ADBA планирует дебютировать, в настоящее время откровенно неизвестна.

В следующем фрагменте показано использование ADBA с SELECT INSERT и SELECT :

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
DataSource ds = dataSource();
 
CompletableFuture<Long> t;
try (Session session = ds.getSession()) {
 
    Submission<Long> submit = session.<Long>rowCountOperation("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)")
            .set("$1", 42055, AdbaType.INTEGER)
            .set("$2", "Description", AdbaType.VARCHAR)
            .set("$3", null, AdbaType.INTEGER)
            .apply(Result.RowCount::getCount)
            .submit();
 
    t = submit.getCompletionStage().toCompletableFuture();
}
 
t.join();
 
CompletableFuture<List<Map<String, Object>>> t;
try (Session session = ds.getSession()) {
 
    Submission<List<Map<String, Object>>> submit = session.<List<Map<String, Object>>>rowOperation("SELECT id, name, manual FROM legoset")
            .collect(collectToMap()) // custom collector
            .submit();
 
    t = submit.getCompletionStage().toCompletableFuture();
}
 
t.join();

Обратите внимание, что collectToMap(…) является примером предоставляемой приложением функции, которая извлекает результаты в желаемый тип возвращаемого значения.

TL; DR, нет реактивного API для доступа к реляционным базам данных.

R2DBC на помощь!

Из-за отсутствия стандартного API и отсутствия драйверов команда в Pivotal начала исследовать идею реактивного реляционного API, который идеально подходил бы для целей реактивного программирования. Они придумали R2DBC, который расшифровывается как Reactive Relational Database Connectivity. На данный момент R2DBC является проектом инкубатора для оценки выполнимости и начала обсуждения того, заинтересованы ли поставщики драйверов в поддержке реактивных / неблокирующих / асинхронных драйверов.

На данный момент существует три реализации драйвера:

R2DBC поставляется со спецификацией API ( r2dbc-spi ) и клиентом ( r2dbc-client ), что делает SPI пригодным для использования в приложениях.

В следующем фрагменте показано использование SPI R2DBC с SELECT INSERT и SELECT :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
ConnectionFactory connectionFactory = null;
 
Mono<Integer> count = Mono.from(connectionFactory.create())
        .flatMapMany(it ->
            it.createStatement("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)")
                    .bind("$1", 42055)
                    .bind("$2", "Description")
                    .bindNull("$3", Integer.class)
                    .execute())
        .flatMap(io.r2dbc.spi.Result::getRowsUpdated)
        .next();
 
Flux<Map<String, Object>> rows = Mono.from(connectionFactory.create())
        .flatMapMany(it -> it.createStatement("SELECT id, name, manual FROM legoset")
                   .execute())
        .flatMap(it -> it.map((row, rowMetadata) -> collectToMap(row, rowMetadata)));

Хотя приведенный выше код является довольно громоздким, R2DBC также поставляется с проектом клиентской библиотеки для более гуманного пользовательского API. R2DBC SPI предназначен не для прямого использования, а скорее для использования через клиентскую библиотеку.

Тот же код, переписанный с R2DBC Client, будет:

01
02
03
04
05
06
07
08
09
10
11
12
R2dbc r2dbc = new R2dbc(connectionFactory);
 
Flux<Integer> count = r2dbc.inTransaction(handle ->
        handle.createQuery("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)")
                .bind("$1", 42055)
                .bind("$2", "Description")
                .bindNull("$3", Integer.class)
                .mapResult(io.r2dbc.spi.Result::getRowsUpdated));
 
Flux<Map<String, Object>> rows = r2dbc.inTransaction(handle ->
        handle.select("SELECT id, name, manual FROM legoset")
                .mapRow((row, rowMetadata) -> collectToMap(row, rowMetadata));

Обратите внимание, что collectToMap(…) выступает в качестве примера для предоставляемой приложением функции, которая извлекает результаты в желаемый тип возвращаемого значения.

Команда Spring Data запустила Spring Data R2DBC в качестве инкубатора для предоставления реактивных API через клиент базы данных и поддержки реактивных репозиториев. Пример кода, переписанный с использованием Spring Data R2DBC:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
 
Mono<Integer> count = databaseClient.execute()
                .sql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)")
                .bind("$1", 42055)
                .bind("$2", "Description")
                .bindNull("$3", Integer.class)
                .fetch()
                .rowsUpdated();
 
Flux<Map<String, Object>> rows = databaseClient.execute()
                .sql("SELECT id, name, manual FROM legoset")
                .fetch()
                .all();

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

JDBC о волокнах

Давайте поговорим о комбинации технологий. В то время как JDBC и другие технологии предоставляют блокирующие API (в основном из-за ожидания ввода-вывода), в разработке находится Project Loom . Loom представляет Fibers как легкую абстракцию, которая превратит блокирующие API в неблокирующие. Это возможно путем переключения стека, как только вызов попадает в блокирующий API. Таким образом, базовое Fiber пытается продолжить предыдущий поток, который использовал блокирующий API.

Модель исполнения Fiber значительно сокращает количество необходимых собственных потоков. Следствием этого является лучшая масштабируемость и неблокирующее поведение — за счет разгрузки блокирующих вызовов для Fiber backed Executor . Все, что нам нужно здесь, — это правильный API, который позволяет использовать неблокирующую JDBC при реализации Fiber .

Вывод

Что ждет в будущем реактивное программирование и реляционные базы данных? Честно говоря, я не знаю. Если я попытаюсь сделать обоснованное предположение, я могу увидеть Project Loom и Fibre-based Executor в сочетании с хорошо зарекомендовавшими себя драйверами JDBC как потенциального изменителя игр в отрасли. С ускоренным выпуском Java-версий это может быть не так уж далеко.

ADBA нацелена на включение в среду исполнения Java Standard, которая, как я предвижу, появится не раньше, чем Java 17, которая будет где-то в 2021 году, в соответствии с текущим графиком.

Сравните это с R2DBC, который доступен сейчас. Он поставляется с драйверами и клиентами и допускает экспериментальное использование. Отличный побочный эффект R2DBC заключается в том, что он предоставляет полностью реактивный API, будучи независимым от базового ядра базы данных. Поскольку релизы уже происходят, нет необходимости ни гадать о Project Loom, ни ждать три года, чтобы протестировать API. Сегодня это возможно с R2DBC.

Опубликовано на Java Code Geeks с разрешения Марка Палуха, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Реактивное программирование и реляционные базы данных

Мнения, высказанные участниками Java Code Geeks, являются их собственными.