红帽最新的runtime与红帽data grid 8.0一起发布,该版本提供了分布式的内存中NoSQL数据存储解决方案。您的应用程序可以以内存速度访问,处理和分析数据,以提供卓越的用户体验。。数据网格包括Infinispan开源软件项目。它可以部署为嵌入式库、独立服务器或Red Hat OpenShift容器平台上的容器化应用程序。
接下来,我们查看几个红帽Data Grid的应用场景。
您可能想使用Cache的情况是什么?让我们花点时间考虑一下。您认为可以在哪里使用缓存?那么这个问题可能会有无限的答案。下面列出了一些常见的用例
查找数据:如果您有一个应用程序,则需要一些用户配置文件数据等,则不必每次都挂起,并且它的变化不会太大
延迟或批量:您可能有一个需要花费很多时间来加载某些数据的服务或数据库。
Traffic:您可能有大量的用户,趋势正在激增
Session存储;存储您的webapp会话(可能是购物车等),可用于扩展应用程序
Global Counters:您可能要跨分布式数据集创建分布式键。使用它来更新和获取数据。
场景1:创建本地缓存
首先,数据网格适合地图应用。为什么地图适合缓存?地图速度很快,它们使用诸如hashcode()之类的方法,并且等于确定如何向地图添加数据。这也意味着它们可以快速读取和写入数据。这正是缓存所期望的。数据存储在键和值对中。Google Maps还有更多功能,但让我们从基本的缓存操作方法开始。
CacheManager是检索Cache实例的主要机制,通常用作使用缓存的起点。本质上,如果您使用的是Map对象,则只需创建一个Map并将所有K,V存储在其中。但是,当您使用诸如Red Hat Data Grid / Inifinispan之类的工具时,您将获得的不仅是一个简单的映射,例如,侦听器,事件等,所有这些都将在以后的章节中讨论。
CacheManager是重量级的对象,建议每个JVM使用一个以上的CacheManager(除非特定的配置要求需要多个,但无论哪种方式,这都是最少数量的实例)。
在源码中增加main方法:
// TODO: Construct a simple local cache manager with default configuration
DefaultCacheManager cacheManager = new DefaultCacheManager();
现在有了cacheManager,我们现在可以定义Cache的配置。我们可以从系统中选择许多功能,例如,如果要添加分组,流,侦听器,逐出或群集策略等,我们可以在此处进行。以下示例仅采用默认配置。
// TODO: Define local cache configuration
cacheManager.defineConfiguration("local", new ConfigurationBuilder().build());
现在我们定义了缓存,是我们从CacheManager获取该缓存的时间。我们还定义了缓存应同时具有键和值作为字符串。
// TODO: Obtain the local cache
Cache<String, String> cache = cacheManager.getCache("local");
最后,将值放入缓存中。将“键”和“值”更改为例如 名称和您的名字,或随时使用其他名称。
// TODO: Store a value
cache.put("key", "value");
在这里,我们通过指定键来获取值。密钥与上一行“ cache .put”中使用的密钥相同;通过指定缓存的键,
您可以获取存储在其中的值。相同的过程也用于更新。
// TODO: Retrieve the value and print it out
System.out.printf("key = %s\n", cache.get("key"));
完成后,我们通过调用stop()方法关闭该实例。
// TODO: Stop the cache manager and release all resources
cacheManager.stop();
mvn clean compile && \
mvn exec:java -Dexec.mainClass=org.acme.Exercise1
查看运行结果:
场景2:JSR-107 JCache
高速缓存通常被称为在内存中存储数据的组件,因此它易于读取可能难以计算或需要相当快地访问的值。已创建Java规范请求(JSR-107)来定义Java的临时缓存API。该规范定义了一些标准API,用于存储和管理本地和分布式用例的数据。
让我们看看如何将JSR-107与Red Hat Data Grid / Infinispan结合使用
// TODO: Construct a simple local cache manager with default configuration
CachingProvider jcacheProvider = Caching.getCachingProvider();
CacheManager cacheManager = jcacheProvider.getCacheManager();
MutableConfiguration<String, String> configuration = new MutableConfiguration<>();
configuration.setTypes(String.class, String.class);
// TODO: create a cache using the supplied configuration
Cache<String, String> cache = cacheManager.createCache("myCache", configuration);
让我们更深入地研究上面的代码。我们使用CachingProvider,它是标准API的一部分。
缓存提供程序又为我们提供了一个cacheManager。
我们为缓存创建一个配置对象。一个MutlableConfiguration
这里我们还设置了缓存的类型,如果您还记得这与我们以前的练习不同的话,因为我们现在正在使用JSR-107 API。最后我们得到了缓存。
最后,将值放入缓存中。将“键”和“值”更改为例如 名称和您的名字,或随时使用其他名称。
// Store and retrieve value
cache.put("key", "value");
System.out.printf("key = %s\n", cache.get("key"));
然后关闭我们的CacheManager。
// TODO: Stop the cache manager and release all resources
cacheManager.close();
mvn clean compile && \
mvn exec:java -Dexec.mainClass=org.acme.Exercise2
场景3:Functional API
当使用多个键时,Functional Map API所采用的方法是提供一个惰性的、拉式的API。所有多键操作都采用一个收集参数,该参数指示要使用的键(有时还包含“值”信息),以及一个针对每个键/值对执行的函数。每个函数的功能取决于作为函数参数接收的入口视图,该入口视图随基础图而变化:ReadOnlyMap的ReadEntryView,WriteOnlyMap的WriteEntryView或ReadWriteMap的ReadWriteView。除WriteOnlyMap中的操作外,所有多键操作的返回类型都返回一个Traversable实例,该实例公开用于处理每个函数执行中返回的数据的方法。
此示例演示了使用Functional Map API处理多个条目的一些关键方面:
WriteOnlyMap的所有数据处理方法(包括多键方法)都返回CompletableFuture <Void>,因为该函数无法提供任何无法预先计算或无法在该函数外部进行计算的信息。
通常,尽管当前不能保证Traversable的顺序与输入集合的顺序匹配。有一种特殊类型的多键操作可用于Infinispan中存储的所有键/条目。该行为与上面显示的多键操作非常相似,不同之处在于它们不将键(或值)的集合作为参数:
关于使用Functional Map API处理所有条目,需要注意:-处理所有条目时,不能保证Traversable的顺序。-只读的keys()和entry()提供了遍历缓存中存在的所有键和条目的可能性。遍历条目时,键和值(包括元数据)均可用。与Java的ConcurrentMap相反,无法仅浏览值(和元数据),因为从这种方法获取的东西很少。检索到密钥的条目后,也无需支付额外费用即可提供密钥。
让我们首先使用DefaultCacheManager初始化缓存,就像在之前的实验中一样。但是,我们使用功能性API,因此在获取缓存后,我们的Map实现是不同的。
如何使用Functional API?使用异步API,所有返回单个结果的方法都返回一个包装结果的CompletableFuture。为了避免阻塞,它提供了在CompletableFuture完成时接收回调的可能性,或者可以将其链接或与其他CompletableFuture实例组成。查看代码:
DefaultCacheManager cacheManager = new DefaultCacheManager();
cacheManager.defineConfiguration("local", new ConfigurationBuilder().build());
AdvancedCache<String, String> cache = cacheManager.<String, String>getCache("local").getAdvancedCache();
FunctionalMapImpl<String, String> functionalMap = FunctionalMapImpl.create(cache);
FunctionalMap.WriteOnlyMap<String, String> writeOnlyMap = WriteOnlyMapImpl.create(functionalMap);
FunctionalMap.ReadOnlyMap<String, String> readOnlyMap = ReadOnlyMapImpl.create(functionalMap);
接下来,异步写入此缓存。将以下代码段复制并粘贴Exercise3.java中
// TODO Execute two parallel write-only operation to store key/value pairs
CompletableFuture<Void> writeFuture1 = writeOnlyMap.eval("key1", "value1",
(v, writeView) -> writeView.set(v));
CompletableFuture<Void> writeFuture2 = writeOnlyMap.eval("key2", "value2",
(v, writeView) -> writeView.set(v));
只写操作需要获取锁。它们不需要读取与缓存的条目关联的先前值或元数据参数信息。由于它们涉及与群集或持久层中的远程节点进行对话,因此有时可能会很昂贵。因此,公开只写操作可轻松利用这一至关重要的优化。
现在让我们以类似的方式进行读取操作
//TODO When each write-only operation completes, execute a read-only operation to retrieve the value
CompletableFuture<String> readFuture1 =
writeFuture1.thenCompose(r -> readOnlyMap.eval("key1", EntryView.ReadEntryView::get));
CompletableFuture<String> readFuture2 =
writeFuture2.thenCompose(r -> readOnlyMap.eval("key2", EntryView.ReadEntryView::get));
公开可以针对功能图执行的只读操作。在功能图中每个条目可以读取的信息。只读操作的优点在于,在操作期间不会获取任何锁。
最后,让我们在操作完成时进行打印。
//TODO When the read-only operation completes, print it out
System.out.printf("Created entries: %n");
CompletableFuture<Void> end = readFuture1.thenAcceptBoth(readFuture2, (v1, v2) -> System.out.printf("key1 = %s%nkey2 = %s%n", v1, v2));
// Wait for this read/write combination to finish
end.get();
因此,我们已经看到了WriteOnly和ReadOnly Map的工作方式,我们还添加了ReadWriteMap读写操作提供了写入值或元数据参数以及返回以前存储的信
息的可能性。读写操作对于实现类似条件的,比较和交换(CAS)的操作也至关重要。
在执行读写lambda之前需要获取锁。
// Use read-write multi-key based operation to write new values
// together with lifespan and return previous values
Map<String, String> data = new HashMap<>();
data.put("key1", "newValue1");
data.put("key2", "newValue2");
Traversable<String> previousValues = readWriteMap.evalMany(data, (v, readWriteView) -> {
String prev = readWriteView.find().orElse(null);
readWriteView.set(v, new MetaLifespan(Duration.ofHours(1).toMillis()));
return prev;
});
mvn clean compile && \
mvn exec:java -Dexec.mainClass=org.acme.Exercise3
练习4:从缓存流式传输数据
借助Red Hat Data Grid / Infinispan,您可以使用Java Streams API并计算对现有数据的分析。Infinispan提供了一种传递lambda的简单方法,该lambda不需要显式转换并且可以序列化。以分布式方式执行这些流的能力以二进制格式序列化。
Infinispan分布式Java流可用于计算对现有数据的分析。通过方法的重载,Infinispan可以提供一种传递可序列化的lambda的简单方法,而无需显式强制转换。能够为lambda生成二进制格式是要分发Java流执行的必不可少的步骤。
通过以下内容,我们创建一个lambda来将数据写入我们的缓存
// TODO: Store some values
int range = 10;
IntStream.range(0, range).boxed().forEach(i -> cache.put(i + "-key", i + "-value"));
现在,我们读取该数据,将其值相加。
// TODO: Map and reduce the keys
int result = cache.keySet().stream()
.map(e -> Integer.valueOf(e.substring(0, e.indexOf("-"))))
.collect(() -> Collectors.summingInt(i -> i.intValue()));
System.out.printf("Result = %d\n", result);
mvn clean compile && \
mvn exec:java -Dexec.mainClass=org.acme.Exercise4
您应该能够看到类似于以下内容的输出。在最后一行,您可以看到您的密
钥,即打印的值。
练习5:使用交易
交易对于任何业务应用程序都是必不可少的。通常,事务与数据集一起使用,并且通常与数据库有关。但是,这并不是完全正确的,如果您有分布式数据集,则需要进行事务处理才能使业务逻辑占上风。Infinspan提供交易。在某些情况下,群集会添加一个节点,或者条目已被另一个节点写入。Infinispan事务管理器知道此类事件并进行处理。您可以在此处阅读有关交易设计的更多信息:https://github.com/infinispan/infinispan-designs
让我们从缓存中获取TransactionManager
//TODO Obtain the transaction manager
TransactionManager transactionManager = cache.getAdvancedCache().getTransactionManager();
我们开始交易,写两个条目,然后关闭它。
// TODO Perform some operations within a transaction and commit it
transactionManager.begin();
cache.put("key1", "value1");
cache.put("key2", "value2");
transactionManager.commit();
我们还做一个回滚方案。因此,我们写入条目并回滚。
//TODO Perform some operations within a transaction and roll it back
transactionManager.begin();
cache.put("key1", "value3");
cache.put("key2", "value4");
transactionManager.rollback();
现在,运行我们的代码,看看它是如何工作的。
在CodeReady终端中按以下步骤运行上述练习,或者您也可以在右侧的MyWorkspace菜单中选择执行命令Exercise5。
mvn clean compile && \
mvn exec:java -Dexec.mainClass=org.acme.Exercise5
练习6:使用Lucene查询缓存
Infinispan包括高度可扩展的分布式Apache Lucene Directory实现。
该目录非常类似于传统文件系统和基于RAM的目录的语义,能够使用Lucene替代现有应用程序,并提供可靠的索引共享和Infinispan的其他功能,例如节点自动发现,自动故障转移,重新平衡以及可选的事务,并且可以由传统的存储解决方案(如文件系统,数据库或云存储引擎)提供支持。
该实现扩展了Lucene的org.apache.lucene.store.Directory,因此可用于将索引存储在群集范围的共享内存中,从而使索引的分发变得容易。与基于rsync的复制相比,此解决方案适用于您的应用程序频繁更改索引的用例,并且您需要将它们快速分发到所有节点。一致性级别,同步性和保证,总弹性和自动发现都可以配置;同样,应用于索引的更改可以选择参与JTA事务,可以选择支持具有恢复功能的XA事务。
由于Lucene是Infinispan的一部分,因此我们需要确保我们具有正确的配置。
// Create cache config
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.indexing().index(Index.ALL)
.addProperty("default.directory_provider", "ram")
.addProperty("lucene_version", "LUCENE_CURRENT");
// Obtain the cache
Cache<String, Person> cache = cacheManager.administration()
.withFlags(CacheContainerAdmin.AdminFlag.VOLATILE)
.getOrCreateCache("cache", builder.build());
现在,让我们在上面的示例中添加更多代码。在下面的代码中,我们获取QueryFactory并创建一个查询。
// TODO: Obtain a query factory for the cache
QueryFactory queryFactory = Search.getQueryFactory(cache);
// Construct a query
Query query = queryFactory.from(Person.class).having("name").eq("William").toBuilder().build();
// Execute the query
List<Person> matches = query.list();
mvn clean compile && \
mvn exec:java -Dexec.mainClass=org.acme.Exercise6