Статьи

Новый API функциональной карты в Infinispan 8

В Infinispan 8.0.0.Beta3 мы представили новый экспериментальный API для взаимодействия с вашими данными, который использует преимущества функционального программирования и улучшенные возможности асинхронного программирования, доступные в Java 8.

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

ConcurrentMap и JCache

Подобные карте API-интерфейсы пары ключ / значение часто используются для распределенного кэширования и таблиц данных в памяти. Первоначально ConcurrentMap стал популярным, но он был разработан для запуска в одной JVM, и, следовательно, некоторые операции пострадали в распределенных средах или при подключении хранилищ хранения. Например, такие методы, как « V put (K, V) », « V putIfAbsent (K, V) », « V replace (K, V) », вынуждают реализации возвращать предыдущее значение, но часто это значение не необходимо, но это может быть дорогим для передачи.

JSR-107 решил улучшить это и разработал спецификацию JCache, которая решает эту конкретную проблему, разделяя такие операции, как V put (K, V) в ConcurrentMap, на две операции: void put (K, V) и V getAndPut (K, V) » , и он применил ту же логику к другим операциям, таким как« замена », предоставив альтернативный метод « getAndReplace (K, V) » … и т. д.

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

На мой взгляд, лучшие части JCache являются « Invoke » и « invokeAll » методы. Когда ты

посмотрите на них, вы увидите большой потенциал для реализации get , put , getAndPut , getAndReplace , putAll , getAll и многих других, использующих эти методы. Другими словами, как разработчик, все, что вам нужно реализовать, это эти две функции, а остальное будет синтаксическим сахаром для пользователя. К сожалению, способ обработки аргументов invoke ‘ и ‘ invokeAll ‘ немного неуклюж, и на самом деле он просто кричит о передаче лямбд  и возвращении экземпляров CompletableFuture (Java 8!).

Итак, когда Infinispan перешел на Java 8, мы решили пересмотреть эти концепции и посмотреть, сможем ли мы придумать более качественный, похожий на карту интерфейс для использования в качестве API для кэширования или сетки данных.

API новой функциональной карты

API-интерфейс Infinispan’s Functional Map представляет собой асинхронный API-интерфейс, похожий на карту, который использует лямбда-выражения для взаимодействия с данными.

Асинхронный и ленивый

Будучи асинхронным API, все методы, которые возвращают один результат, возвращают CompletableFuture, который оборачивает результат, так что вы можете использовать ресурсы вашей системы более эффективно, имея возможность получать обратные вызовы после завершения CompletableFuture, или вы можете связать или составьте их с другими CompletableFuture. Если вы хотите заблокировать поток и дождаться результата, как это происходит с вызовом метода ConcurrentMap или JCache, вы можете просто вызвать ` CompletableFuture.get () ` (для таких ситуаций мы работаем над поиском способов избежать создание ненужного потока, когда вызывающая сторона будет блокироваться на CompletableFuture).

Для тех операций, которые возвращают несколько результатов, API возвращает экземпляры интерфейса Traversable, который предлагает ленивый API в стиле pull для работы с несколькими результатами. Хотя интерфейсы push-стиля для обработки нескольких результатов, такие как RxJava, являются полностью асинхронными, их сложнее использовать с точки зрения пользователя. Traversable, будучи ленивым API-интерфейсом в стиле pull, все еще может быть асинхронным, поскольку пользователь может решить работать с traversable на более позднем этапе, а сама реализация Traversable может решить, когда вычислять эти результаты.

Лямбда прозрачность

Поскольку содержимое лямбда-кодов является прозрачным для Infinispan, API был разделен на 3 интерфейса для операций « только чтение» ( ReadOnlyMap ), «чтение-запись» ( ReadWriteMap ) и «только запись» ( WriteOnlyMap ) соответственно, чтобы обеспечить подсказки для Внутренние элементы Infinispan о типе работы, необходимой для поддержки лямбд.

Например, Infinispan был разработан таким образом, что наши реализации ‘ ConcurrentMap.get () ‘ и ‘ JCache.getAll () ‘ не требуют получения блокировок. Эти  операции get () / getAll () предназначены только для чтения, и, следовательно, если вы вызовите нашу функцию ReadOnlyMap ‘ eval () ‘ или ‘ evalMany () ‘, вы получите то же преимущество. Ключевое преимущество ReadOnlyMap ‘ eval () ‘ и ‘ evalMany ()Операции заключаются в том, что они принимают лямбда-выражения в качестве параметров, что означает, что возвращаемые типы являются более гибкими, поэтому мы можем вернуть значение, связанное с ключом, или мы можем вернуть логическое значение, если значение имеет ожидаемое содержимое, или мы можем вернуть некоторые параметры метаданных из него, например, время последнего доступа, время последнего изменения, время создания, срок службы, информация о версии … и т. д.

Другой важный совет, который требуется для эффективного использования системы, — это знать, когда выполняется операция только для записи. Операции только для записи требуют получения блокировок и, как демонстрируют JCache ‘ void removeAll () ‘ и ` void put (K, V) ‘или ConcurrentMap’ putAll () ‘, они не требуют, чтобы предыдущее значение запрашивалось или читалось , что, как объяснено выше, является очень важной оптимизацией, поскольку для чтения предыдущего значения может потребоваться запрос персистентного уровня или удаленного узла. WriteOnlyMap ‘ eval () ‘, ‘ evalMany () ‘ и ‘ evalAll () ‘следуйте этому же шаблону с дополнительной гибкостью для лямбды, чтобы решить, какую операцию записи выполнить.

Последний тип операций, которые мы имеем, это операции чтения-записи, и в этой категории мы находим CAS-подобные (Compare-And-Swap) операции. Для этого типа операций требуется, чтобы считывалось предыдущее значение, связанное с ключом, и для получения блокировок перед выполнением лямбды.  Большинство операций в операциях ConcurrentMap и JCache находятся в этом домене, включая: « V put (K, V) », « Boolean PutIfAbsent (K, V) », « V replace (K, V) », « Boolean replace (( K, V, V) ‘… и т. Д. ReadWriteMap ‘ eval () ‘, ‘ evalMany () ‘ и ‘ evalAll ()‘предоставить способ для выполнения подавляющего большинства этих операций благодаря гибкости передаваемых лямбд. Таким образом, вы можете выполнять CAS-подобные сравнения не только на основе равенства значений, но и на основе равенства параметров метаданных, таких как информация о версии, и вы можете отправить назад предыдущее значение или логические экземпляры, чтобы указать, успешно ли выполнено сравнение, подобное CAS.

$ DEITY, мне нужно выучить новый API !!!

Этот новый функциональный API, подобный Map, предназначен для дополнения существующих предложений API Key / Value Infinispan, поэтому вы все равно сможете использовать стандартные API ConcurrentMap или JCache, если это лучше всего подходит для вашего варианта использования.

Целевая аудитория для этого нового API:

  1. Распределенные или постоянные пользователи сетки данных кэширования / в памяти, которые хотят воспользоваться преимуществами CompletableFuture и / или Traversable для асинхронной / отложенной сетки данных или манипулирования данными кэширования. Очевидным преимуществом здесь является то, что потоки не должны бездействовать в ожидании завершения удаленных операций, но вместо этого они могут уведомляться о завершении удаленных операций, а затем связывать их с другими последующими операциями.
  2. Пользователи, которые хотят выйти за рамки стандартных операций, предоставляемых ConcurrentMap и JCache, например, если вы хотите выполнить операцию замены, используя равенство параметров метаданных вместо равенства значений, или если вы хотите извлечь информацию метаданных из значений … и т. Д.

Внутренне мы чувствуем, что этот новый функциональный API, похожий на Map, отгоняет API, подобные Map, которые мы в настоящее время предлагаем (включая ConcurrentMap и JCache), и избавляется от большого дублирования в нашем AdvancedCache API (например, ‘ getCacheEntry () ‘, ‘ getAsync () ‘,’ putAsync () ‘,’ put (K, V, Metadata) ‘… и т. д.) и, следовательно, мы хотим, чтобы все эти API были реализованы с использованием нового функционального Maplike API. Делая это, мы надеемся уменьшить количество команд, которые реализует наша внутренняя архитектура, и, следовательно, уменьшить нашу кодовую базу.

Этот новый API также предлагает новый подход для передачи параметров для каждого вызова и гораздо более гибкую обработку метаданных по сравнению с нашим текущим подходом. По мере того, как мы будем копаться в этом новом API в следующих постах блога, мы объясним различия и преимущества, предоставляемые ими.

Примеры использования API функциональной карты

Чтобы дать вам представление о том, как выглядит API, вот операция только для записи, чтобы связать ключ со значением, чье CompletableFuture было приковано цепочкой, так что после его завершения можно выполнить операцию только для чтения, чтобы прочитать сохраненное значение, и когда оно завершится, выведите его на системный вывод:

import org.infinispan.commons.api.functional.EntryView.*;
import org.infinispan.commons.api.functional.FunctionalMap.*;
import org.infinispan.manager.*;
import org.infinispan.functional.impl.*;

import java.util.concurrent.CompletableFuture;

// Create Infinispan cache manager
EmbeddedCacheManager cacheManager = ...;

// Construct write-only and read-only map facades
WriteOnlyMap<Integer, String> writeOnlyMap = WriteOnlyMapImpl.create(
    FunctionalMapImpl.create(cacheManager.<Integer, String>getCache().getAdvancedCache()));
ReadOnlyMap<Integer, String> readOnlyMap = ReadOnlyMapImpl.create(
    FunctionalMapImpl.create(cacheManager.<Integer, String>getCache().getAdvancedCache()));

// Execute a write-only operation to store a key/value pair
CompletableFuture<Void> writeFuture = writeOnlyMap.eval(1, "v1", (v, view) -> view.set(v));

// When the write-only operation completes, execute a read-only operation to retrieve the value
CompletableFuture<String> readFuture = 
    writeFuture.thenCompose(r -> readOnlyMap.eval(1, ReadEntryView::get));

// When the read-only operation completes, print it out
readFuture.thenAccept(System.out::println);

Вы можете найти больше примеров этого нового API в  классах FunctionalConcurrentMap  и FunctionalJCache , которые являются реализациями ConcurrentMap и JCache соответственно с использованием нового API-интерфейса Functional Map.

Расскажи больше

В течение следующих нескольких недель я буду публиковать примеры, в которых рассматриваются более мелкие детали этих новых API-интерфейсов Functional Map, но если вы хотите начать, проверьте классы в пакете org.infinispan.functional,  FunctionalConcurrentMap и  FunctionalJCache,  которые являются ConcurrentMap. и реализации JCache, основанные на этих API-интерфейсах Functional Map, и FunctionalMapTest, который демонстрирует операции, выходящие за рамки того, что предлагают ConcurrentMap и JCache.