Data Grid Developer Guide
Red Hat Data Grid 复制链接链接已复制到粘贴板!
Data Grid 是一个高性能分布式内存数据存储。
- 无架构数据结构
- 将不同对象存储为键值对的灵活性。
- 基于网格的数据存储
- 旨在在集群中分发和复制数据。
- 弹性扩展
- 动态调整节点数量,以便在不中断服务的情况下满足需求。
- 数据互操作性
- 从不同端点在网格中存储、检索和查询数据。
Data Grid 文档 复制链接链接已复制到粘贴板!
红帽客户门户网站中提供了 Data Grid 的文档。
Data Grid 下载 复制链接链接已复制到粘贴板!
访问 红帽客户门户上的 Data Grid 软件下载。
您必须有一个红帽帐户才能访问和下载数据中心软件。
使开源包含更多 复制链接链接已复制到粘贴板!
红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。我们从这四个术语开始:master、slave、黑名单和白名单。由于此项工作十分艰巨,这些更改将在即将推出的几个发行版本中逐步实施。详情请查看 CTO Chris Wright 的信息。
第 1 章 配置 Data Grid Maven 存储库 复制链接链接已复制到粘贴板!
Data Grid Java 发行版可从 Maven 提供。
您可以从客户门户网站下载 Data Grid Maven 存储库,或者从公共 Red Hat Enterprise Maven 存储库拉取 Data Grid 依赖项。
1.1. 下载 Data Grid Maven 存储库 复制链接链接已复制到粘贴板!
如果您不想使用公共 Red Hat Enterprise Maven 存储库,将 Data Grid Maven 存储库下载并安装到本地文件系统、Apache HTTP 服务器或 Maven 存储库管理器。
流程
- 登录到红帽客户门户。
- 导航到 Software Downloads for Data Grid。
- 下载 Red Hat Data Grid 8.3 Maven 存储库。
- 将存档 Maven 存储库提取到本地文件系统。
-
打开
README.md文件并按照适当的安装说明进行操作。
1.2. 添加 Red Hat Maven 存储库 复制链接链接已复制到粘贴板!
在您的 Maven 构建环境中包括 Red Hat GA 存储库,以获取 Data Grid 工件和依赖项。
流程
将 Red Hat GA 存储库添加到您的 Maven 设置文件中,通常为
~/.m2/settings.xml,或者直接在项目的pom.xml文件中添加。Copy to Clipboard Copied! Toggle word wrap Toggle overflow
1.3. 配置您的 Data Grid POM 复制链接链接已复制到粘贴板!
Maven 使用名为 Project Object Model (POM)文件的配置文件来定义项目并管理构建。POM 文件采用 XML 格式,描述生成的项目打包和输出的模块和组件依赖项、构建顺序和目标。
流程
-
打开项目
pom.xml进行编辑。 -
使用正确的 Data Grid 版本定义
version.infinispan属性。 在 dependencies
Management部分包含infinispan-bom。Bill Of Materials (BOM)控制依赖项版本,这样可避免版本冲突,意味着您不需要为项目添加的每个 Data Grid 工件设置版本。
-
保存并关闭
pom.xml。
以下示例显示了 Data Grid 版本和 BOM:
后续步骤
根据需要,将 Data Grid 工件作为依赖项添加到 pom.xml 中。
第 2 章 缓存管理器 复制链接链接已复制到粘贴板!
主要的入口点是可让您的 CacheManager 接口:
- 配置并获取缓存。
- 管理和监控集群数据中心节点。
- 在集群中执行代码。
如果您在应用程序中嵌入 Data Grid,则使用 EmbeddedCacheManager。如果您将 Data Grid 作为远程服务器运行,您可以使用 RemoteCacheManager。
缓存管理器是高权对象,因此在大多数情况下,您应该为每个 JVM 实例化一个 CacheManager 实例。
EmbeddedCacheManager manager = new DefaultCacheManager();
EmbeddedCacheManager manager = new DefaultCacheManager();
- 1
- 启动没有缓存的本地、非集群的缓存管理器。
缓存管理器具有生命周期,默认构造器也会调用 start () 方法。提供构造器的过载版本,但它们不会启动 CacheManager。但是,您必须先启动 CacheManager,然后才能创建缓存。
同样,当您不再需要正在运行的 CacheManager 时,您必须调用 stop (),以便释放资源。这也确保缓存管理器安全停止它控制的任何缓存。
2.1. 获取缓存 复制链接链接已复制到粘贴板!
配置 CacheManager 后,您可以获取和控制缓存。
调用 getCache (String) 方法以获取缓存,如下所示:
Cache<String, String> myCache = manager.getCache("myCache");
Cache<String, String> myCache = manager.getCache("myCache");
前面的操作会创建一个名为 myCache 的缓存(如果尚不存在),并返回它。
使用 getCache () 方法仅在调用该方法的节点上创建缓存。换句话说,它会执行一个本地操作,它必须在集群中的每个节点上调用。通常,在多个节点之间部署的应用程序会在初始化过程中获取缓存,以确保缓存是 对称的,并在每个节点上存在。
调用 createCache () 方法,以在整个集群中动态创建缓存,如下所示:
Cache<String, String> myCache = manager.administration().createCache("myCache", "myTemplate");
Cache<String, String> myCache = manager.administration().createCache("myCache", "myTemplate");
以上操作还会在随后加入集群的任何节点上自动创建缓存。
使用 createCache () 方法创建的缓存默认为临时的。如果整个集群都关闭,则重启时不会再次自动创建缓存。
使用 PERMANENT 标志来确保缓存可以在重启后,如下所示:
Cache<String, String> myCache = manager.administration().withFlags(AdminFlag.PERMANENT).createCache("myCache", "myTemplate");
Cache<String, String> myCache = manager.administration().withFlags(AdminFlag.PERMANENT).createCache("myCache", "myTemplate");
要使 PERMANENT 标志生效,您必须启用全局状态并设置配置存储供应商。
有关配置存储供应商的更多信息,请参阅 GlobalStateConfigurationBuilderBuilderconfigurationStorage ()。
2.2. 集群信息 复制链接链接已复制到粘贴板!
EmbeddedCacheManager 有几种方法来提供与集群运行方式相关的信息。只有在集群环境中使用时(这是配置传输时),以下方法才有意义。
2.3. 成员信息 复制链接链接已复制到粘贴板!
当您使用集群时,务必要查找集群中的成员资格信息,包括集群的所有者。
getMembers ()方法返回当前集群中的所有节点。
getCoordinator () 方法将告诉您其中一个成员是集群的协调者。对于大多数意图,您不应负责协调者是谁。您可以使用 isCoordinator () 方法查看本地节点是否也是协调器。
第 3 章 缓存接口 复制链接链接已复制到粘贴板!
Data Grid 提供了一个 缓存 接口,它公开了添加、检索和删除条目的简单方法,包括 JDK 的 ConcurrentMap 接口公开的原子机制。根据所使用的缓存模式,调用这些方法会触发很多事情,甚至可能包括将条目复制到远程节点或从远程节点中查找条目或可能缓存存储。
3.1. Cache API 复制链接链接已复制到粘贴板!
对于简单用途,使用 Cache API 不应与使用 JDK Map API 不同,因此应该根据映射到数据中心的缓存从简单内存缓存迁移。
3.1.1. Certain Map 方法的性能确定 复制链接链接已复制到粘贴板!
在映射中公开的某些方法在与 Data Grid 一起使用时有一些性能结果,如 size ()、values ()、keySet () 和 entrySet ()。有关 keySet、值和 entrySet 的具体方法可以明确使用,请参阅其 Javadoc 了解更多详情。
尝试全局执行这些操作会对性能有较大的影响,并成为可扩展性瓶颈。因此,这些方法只用于信息或调试目的。
请注意,将某些标记与 withFlags () 方法一起使用可以缓解其中一些问题,请查看每种方法的文档以获取更多详细信息。
3.1.2. Mortal 和 Immortal 数据 复制链接链接已复制到粘贴板!
此外,要存储条目,Data Grid 的缓存 API 允许您将大量信息附加到数据。例如,只是使用 put (键、值) 会创建一个 immortal 条目,即永久在缓存中处于活动状态的条目,直到它被删除(或从内存中驱除以防止内存不足)。但是,如果您使用 put (key, value, Lifespan, timeunit)将数据 放在缓存中,这会创建一个 mortal 条目,即有固定生命周期的条目,并在其生命周期之后过期。
除了 Life span 外,Data Grid 还支持 maxIdle 作为决定过期的额外指标。可以使用 lifetimespans 或 maxIdles 的任意组合。
3.1.3. putForExternalRead 操作 复制链接链接已复制到粘贴板!
数据中心的 缓存 类包含不同的"吞吐量"操作,称为 putForExternalRead。当 Data Grid 用作其他位置数据的临时缓存时,此操作特别有用。在大量读取场景中,缓存中的竞争不应延迟手动实际事务,因为缓存应只是优化,而不是以某种方式获得的情况。
为达到此目的,placementForExternalRead () 充当仅在缓存中没有密钥时运行的放置调用,如果另一个线程尝试同时存储同一密钥,则会失败。在这个特定场景中,缓存数据是一种选择系统的方法,因此不需要缓存中的故障会影响正在进行的事务,因此导致处理失败的原因不同。putForExternalRead () 被视为快速操作,因为无论它是成功还是不等待任何锁定,因此都无法立即返回到调用者。
要了解如何使用此操作,请查阅基本示例。假设您是 Person 实例的缓存,每个由 PersonId 的键,其数据都源自在单独的数据存储中。以下代码显示了在本示例上下文中使用 putForExternalRead 的最常见模式:
请注意,PutForExternalRead 不应用作使用新 Person 实例更新缓存的机制,它们源自应用程序执行(例如,修改 Person 地址的事务)。更新缓存的值时,请使用标准 put 操作,否则可能会出现缓存损坏数据的可能性。
3.2. AdvancedCache API 复制链接链接已复制到粘贴板!
除了简单的缓存接口外,Data Grid 还提供了一个 高级Cache 界面,适用于扩展作者。AdvancedCache 提供了访问某些内部组件的功能,并应用标志来更改某些缓存方法的默认行为。以下代码片段描述了如何获取高级Cache:
AdvancedCache advancedCache = cache.getAdvancedCache();
AdvancedCache advancedCache = cache.getAdvancedCache();
3.2.1. 标记 复制链接链接已复制到粘贴板!
标志应用于常规缓存方法,以更改某些方法的行为。有关所有可用标志及其影响的列表,请参阅 标记 枚举。标志使用 AdvancedCache.withFlags () 应用。此构建器方法可用于将任意数量的标志应用到缓存调用,例如:
advancedCache.withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_LOCKING)
.withFlags(Flag.FORCE_SYNCHRONOUS)
.put("hello", "world");
advancedCache.withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_LOCKING)
.withFlags(Flag.FORCE_SYNCHRONOUS)
.put("hello", "world");
3.3. 监听器和通知 复制链接链接已复制到粘贴板!
Data Grid 提供了一个监听器 API,客户端可以在事件发生时注册并获得通知。此注解驱动的 API 适用于 2 个不同的级别:缓存级别的事件和缓存管理器级别事件。
事件会触发分配给监听程序的通知。侦听器是简单的 POJO使用 @Listener 注解,并使用 Listenable 接口中定义的方法注册。
Cache 和 CacheManager 都实现了 Listenable,这意味着您可以将监听程序附加到缓存或缓存管理器,以接收缓存级别或缓存管理器级通知。
例如,以下类定义了一个监听程序,以非阻塞的方式每次添加新条目时打印一些信息:
有关更全面的示例,请参阅 @Listener 的 Java 文档。
3.3.1. 缓存级别通知 复制链接链接已复制到粘贴板!
缓存级别的事件以每个缓存为基础发生,默认情况下仅在发生事件的节点上引发。请注意,在分布式缓存中,这些事件只会在受影响的数据所有者上引发。正在添加、删除、修改等条目示例。这些事件会触发通知,以监听程序注册到特定缓存中。
有关所有缓存级别通知的完整列表 ,请参阅 org.infinispan.notifications.cachelistener.annotation 软件包中的 Javadocs,以及它们相应的方法级注解。
有关 Data Grid 中可用的缓存级别通知列表,请参阅 org.infinispan.notifications.cachelistener.annotation 软件包中的 Javadocs。
3.3.1.1. Cluster Listeners 复制链接链接已复制到粘贴板!
当需要侦听单个节点上的缓存事件时,应使用集群监听程序。
为此,需要把监听程序设置为将监听程序标注为集群。
@Listener (clustered = true)
public class MyClusterListener { .... }
@Listener (clustered = true)
public class MyClusterListener { .... }
在非集群的监听器中,集群监听程序有一些限制。
-
集群侦听器只能侦听
@CacheEntryModified、@CacheEntryCreated、@CacheEntryRemoved和@CacheEntryExpired事件。请注意,这意味着,对于这个监听器,任何其他类型的事件都不会侦听。 - 只有 post 事件发送到集群监听程序,才会忽略 pre 事件。
3.3.1.2. 事件过滤和转换 复制链接链接已复制到粘贴板!
安装监听器的节点上所有适用的事件都会被提高到监听器。可以使用 KeyFilter (仅允许对键进行过滤)或 CacheEventFilter (用于过滤键、旧值、旧值、新值、新值、新元数据、是否重试、事件在事件之前)以及命令类型之前,可以动态过滤哪些事件。
此处的示例显示了一个简单的 KeyFilter,它只允许仅在事件修改只为 Me 键的条目时引发事件。
当您想以更有效的方式限制收到的事件时,这非常有用。
另外,还提供了一个 CacheEventConverter,允许在发生事件前将值转换为另一个值。这可以提供模块化化值转换的任何代码。
在与 Cluster Listener 一起使用时,上述过滤器和转换器特别有用。这是因为在事件源自的节点上进行过滤和转换,而不是侦听事件的节点。这可提供不需要在集群中复制事件的好处(过滤),甚至会减少有效负载(转换器)。
3.3.1.3. 初始状态事件 复制链接链接已复制到粘贴板!
安装监听程序时,它仅在完全安装后收到事件的通知。
可能需要在第一次注册监听器时获取缓存内容的当前状态,方法是为缓存中的每个元素生成类型为 @CacheEntryCreated 的事件。在此初始阶段的任何额外生成的事件都将排队,直到引发适当的事件。
这目前仅适用于集群的监听程序。ISPN-4608 涵盖为非集群的监听程序添加此功能。
3.3.1.4. 重复事件 复制链接链接已复制到粘贴板!
在非事务缓存中可能会接收重复的事件。当试图执行写入操作(如 put )时,密钥的主所有者可能会停机。
数据中心内部将重新处理放置操作,方法是自动将其发送到给定密钥的新主所有者,但没有保证如果写入首次复制到备份,则无法保证。因此,以下写入事件(CacheEntryCreatedEvent、CacheEntryModifiedEvent 和 CacheEntryRemovedEvent)可以在单个操作上发送超过 1 个。
如果生成了多个事件,那么将标记重试命令生成的事件,以帮助用户知道何时发生此事件而无需注意查看更改。
另外,在使用 CacheEventFilter 或 CacheEventConverter 时,EventType 包含 isRetry 来指示事件因为重试而生成。https://access.redhat.com/webassets/avalon/d/red-hat-data-grid/8.3/api/org/infinispan/notifications/cachelistener/filter/EventType.html
3.3.2. 缓存管理器级通知 复制链接链接已复制到粘贴板!
缓存管理器级事件发生在缓存管理器上。这些也是全局和集群范围的,但涉及影响单个缓存管理器创建的所有缓存的事件。缓存管理器级别的事件示例是加入或离开集群,或缓存启动或停止。
如需了解所有缓存管理器级 通知及其相应的方法级别注解的完整列表,请参阅 org.infinispan.notifications.cachemanagerlistener.annotation 软件包。
3.3.3. 事件同步 复制链接链接已复制到粘贴板!
默认情况下,所有 async 通知都在通知线程池中分配。同步通知将延迟操作继续,直到监听程序方法完成或完成完成(以前导致线程被阻塞)。或者,您可以将监听程序标注为 异步,在这种情况下,操作会立即继续,而通知会在通知线程池上异步完成。要做到这一点,只需注解您的监听程序:
异步 Listener
@Listener (sync = false)
public class MyAsyncListener {
@CacheEntryCreated
void listen(CacheEntryCreatedEvent event) { }
}
@Listener (sync = false)
public class MyAsyncListener {
@CacheEntryCreated
void listen(CacheEntryCreatedEvent event) { }
}
阻塞同步 Listener
@Listener
public class MySyncListener {
@CacheEntryCreated
void listen(CacheEntryCreatedEvent event) { }
}
@Listener
public class MySyncListener {
@CacheEntryCreated
void listen(CacheEntryCreatedEvent event) { }
}
非阻塞 Listener
@Listener
public class MyNonBlockingListener {
@CacheEntryCreated
CompletionStage<Void> listen(CacheEntryCreatedEvent event) { }
}
@Listener
public class MyNonBlockingListener {
@CacheEntryCreated
CompletionStage<Void> listen(CacheEntryCreatedEvent event) { }
}
3.3.3.1. 异步线程池 复制链接链接已复制到粘贴板!
要调整用于分配此类异步通知的线程池,请在配置文件中使用 & lt;listener-executor /> XML 元素。
3.4. 异步 API 复制链接链接已复制到粘贴板!
除了像 Cache.put ()、cache .remove () 等同步 API 方法外,Data Grid 还有一个异步的、非阻塞的 API,您可以在其中实现相同的结果。
这些方法命名的方式与其阻止的对应部分相似,并附加"Async"。 E.g., Cache.putAsync() , Cache.removeAsync() , etc. 这些异步对应的项会返回包含操作实际结果的 CompletableFuture。
例如,在 cache 参数中,在 Cache<String, String>, Cache.put (String key, String value) 中返回 String,而 Cache.putAsync (String key, String value) 返回 Complet ableFuture<String>。
3.4.1. 为什么使用这样的 API? 复制链接链接已复制到粘贴板!
非阻塞 API 非常强大,其提供了同步通信的所有保证 - 能够处理通信故障和例外 - 在调用完成前,无需阻止。 这可让您提高系统中的利用率并行性。 例如:
3.4.2. 哪些进程实际上异步发生? 复制链接链接已复制到粘贴板!
Data Grid 中有 4 个问题,它们可能被视为典型的写入操作的关键路径。这些是按成本顺序排列:
- 网络调用
- Marshalling
- 写入缓存存储(可选)
- 锁定
使用 async 方法将获取网络调用并分离关键路径。 出于各种技术原因,写入缓存存储和获取锁定,但仍在调用器线程中发生。
第 4 章 集群锁定 复制链接链接已复制到粘贴板!
集群锁定是在 Data Grid 集群中的节点间分布和共享的数据结构。集群锁定允许您运行在节点间同步的代码。
4.1. 锁定 API 复制链接链接已复制到粘贴板!
Data Grid 提供了一个 ClusteredLock API,可让您在嵌入式模式中使用 Data Grid 时同时执行代码。
API 由以下内容组成:
-
ClusteredLock公开实现集群锁定的方法。 -
ClusteredLockManager会公开方法来定义、配置、检索和删除集群锁定。 -
EmbeddedClusteredLockManagerFactory初始化ClusteredLockManager实现。
所有权
Data Grid 支持 NODE 所有权,以便集群中的所有节点都可以使用锁定。
Reentrancy
Data Grid 集群锁定是非真实的,因此集群中的任何节点都可以获得锁定,但只有创建锁定的节点才可以释放它。
如果为同一所有者发送两个连续锁定调用,则第一个调用会获取锁(如果可用),第二个调用会被阻断。
4.2. 使用集群锁定 复制链接链接已复制到粘贴板!
了解如何使用嵌入在应用程序中的集群锁定。
先决条件
-
将
infinispan-clustered-lock依赖项添加到pom.xml中:
<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-clustered-lock</artifactId> </dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-clustered-lock</artifactId>
</dependency>
流程
-
从缓存管理器初始化
ClusteredLockManager接口。此接口是用于定义、检索和删除集群锁定的入口点。 - 为每个集群锁定指定唯一名称。
-
使用
lock.tryLock (1, TimeUnit.SECONDS)方法获取锁定。
4.3. 为锁定配置内部缓存 复制链接链接已复制到粘贴板!
集群锁定管理器包括存储锁定状态的内部缓存。您可以以声明性方式或以编程方式配置内部缓存。
流程
-
定义存储集群锁定状态的集群中的节点数量。默认值为 -1,这会将值复制到所有节点。
为缓存可靠性指定以下值之一,它控制集群锁在集群分成分区或多个节点时的行为方式:
-
AVAILABLE:任何分区中的节点可以在锁定上同时操作。 -
CONSISTENT:只有属于大多数分区的节点才能在锁定上运行。这是默认值。 编程配置
Copy to Clipboard Copied! Toggle word wrap Toggle overflow 声明配置
Copy to Clipboard Copied! Toggle word wrap Toggle overflow
-
第 5 章 集群的计数 复制链接链接已复制到粘贴板!
集群的计数器 是,在 Data Grid 集群中的所有节点间分布和共享的计数器。计数器可以有不同的一致性级别:强和弱度。
虽然强/弱一致性计数器都有单独的接口,但支持更新其值,但返回当前值并在更新其值时提供事件。本文档中提供了以下信息,以帮助您选择最适合您的用例。
5.1. 安装和配置 复制链接链接已复制到粘贴板!
要开始使用计数器,您需要在 Maven pom.xml 文件中添加依赖项:
pom.xml
<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-clustered-counter</artifactId> </dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-clustered-counter</artifactId>
</dependency>
这些计数器可以通过本文档稍后详述的 CounterManager 接口配置 Data Grid 配置文件或按需。当 EmbeddedCacheManager 启动时,会在引导时在 Data Grid 配置文件中配置的计数器。这些计数器以百分比方式启动,它们可在所有集群的所有节点中可用。
configuration.xml
或者以编程方式,在 GlobalConfigurationBuilder 中:
另一方面,可以在 Embeded CacheManager 初始化后随时配置计数器。
CounterConfiguration 是不可变的,可以重复使用。
如果计数器配置成功或为 false,则方法 defineCounter () 将返回 true。但是,如果配置无效,则方法会抛出 CounterConfigurationException。要找出是否已定义了计数器,请使用方法 isDefined ()。
CounterManager manager = ...
if (!manager.isDefined("someCounter")) {
manager.define("someCounter", ...);
}
CounterManager manager = ...
if (!manager.isDefined("someCounter")) {
manager.define("someCounter", ...);
}
5.1.1. 列出计数器名称 复制链接链接已复制到粘贴板!
要列出定义的所有计数器,方法 CounterManager.getCounterNames () 会返回集群范围的所有计数器名称的集合。
5.2. CounterManager 接口 复制链接链接已复制到粘贴板!
CounterManager 接口是定义、检索和删除计数器的入口点。
嵌入式部署
CounterManager 会自动侦听 EmbededCacheManager 的创建,并为每个 Embeded CacheManager 注册一个实例。它启动存储计数器状态所需的缓存并配置默认计数器。
检索 CounterManager 非常简单,就像调用 EmbeddedCounterManagerFactory.asCounterManager (EmbeddedCacheManager) 一样简单,如下例所示:
// create or obtain your EmbeddedCacheManager EmbeddedCacheManager manager = ...; // retrieve the CounterManager CounterManager counterManager = EmbeddedCounterManagerFactory.asCounterManager(manager);
// create or obtain your EmbeddedCacheManager
EmbeddedCacheManager manager = ...;
// retrieve the CounterManager
CounterManager counterManager = EmbeddedCounterManagerFactory.asCounterManager(manager);
服务器部署
对于 Hot Rod 客户端,CounterManager 在 RemoteCacheManager 中注册,并可按如下方式检索:
// create or obtain your RemoteCacheManager RemoteCacheManager manager = ...; // retrieve the CounterManager CounterManager counterManager = RemoteCounterManagerFactory.asCounterManager(manager);
// create or obtain your RemoteCacheManager
RemoteCacheManager manager = ...;
// retrieve the CounterManager
CounterManager counterManager = RemoteCounterManagerFactory.asCounterManager(manager);
5.2.1. 通过 CounterManager 删除计数器 复制链接链接已复制到粘贴板!
通过 Strong/WeakCounter 接口和 CounterManager 删除计数器之间有一个区别。CounterManager.remove (String) 从集群中删除计数器值,并删除本地计数器实例中计数器注册的所有监听程序。另外,计数器实例不再被重复使用,可能会返回无效的结果。
另一方面,Strong /WeakCounter 删除只删除计数器值。实例仍然可以被重复使用,监听程序仍然可以正常工作。
如果在移除后访问计数器,则会重新创建计数器。
5.3. Counter 复制链接链接已复制到粘贴板!
计数器可以是强度(StrongCounter)或弱点一致(WeakCounter),且两者都由名称标识。它们有一个特定的接口,但它们共享一些逻辑,即异步接口(每个操作返回了 CompletableFuture ),提供一个更新事件,并可重置为其初始值。
如果您不想使用 async API,可以通过 sync () 方法返回同步计数器。API 相同,但没有 CompletableFuture 返回值。
两种方法对两个接口都很常见:
-
getName ()返回计数器名称(identifier)。 -
getValue ()返回当前计数器的值。 -
reset ()允许将计数器的值重置为其初始值。 -
添加Listener ()注册监听程序以接收更新事件。有关它的更多详细信息,请参阅 通知和事件 部分。 -
getConfiguration ()返回计数器使用的配置。 -
remove ()从集群中移除计数器值。实例仍然可以被使用,并保留监听程序。 -
sync ()创建一个同步计数器。
如果在移除后访问计数器,则会重新创建计数器。
5.3.1. StrongCounter 接口:当一致性或边界何时重要。 复制链接链接已复制到粘贴板!
强大的计数器使用存储在 Data Grid 缓存中的单个密钥来提供所需的一致性。所有更新都在键锁定下执行,以更新其值。另一方面,读取不会获取任何锁定并读取当前值。另外,使用此方案,它允许绑定计数器值,并提供类似 compare-and-set/swap 等原子操作。
可以使用 getStrongCounter () 方法从 CounterManager 检索 StrongCounter ()。例如:
CounterManager counterManager = ...
StrongCounter aCounter = counterManager.getStrongCounter("my-counter");
CounterManager counterManager = ...
StrongCounter aCounter = counterManager.getStrongCounter("my-counter");
由于每个操作都达到单个键,因此 StrongCounter 具有更高的争用率。
StrongCounter 接口添加了以下方法:
-
incrementAndGet() 会递增计数器并返回新值。 -
decrementAndGet ()将计数器减一,并返回新值。 -
addAndGet ()在计数器的值中添加 delta 并返回新值。 -
如果当前值是预期的,比较AndSet ()和compareAndSwap ()会原子地设置计数器的值。
当完成 CompletableFuture 时,操作被视为已完成。
compare-and-set 和 compare-and-swap 之间的区别在于,如果操作成功,则前者返回 true,而后续返回前面的值。如果返回值与预期相同,则 compare-and-swap 可以成功。
5.3.1.1. Bounded StrongCounter 复制链接链接已复制到粘贴板!
绑定后,上面的所有更新方法都会抛出 CounterOutOfBoundsException,当它们达到下限或上限时。这个例外有以下方法来检查已达到哪个侧绑定:
public boolean isUpperBoundReached(); public boolean isLowerBoundReached();
public boolean isUpperBoundReached();
public boolean isLowerBoundReached();
5.3.1.2. 用例 复制链接链接已复制到粘贴板!
在以下用例中,强计数器更适合:
- 每次更新后需要计数器的值(例如,集群 ID 生成器或序列)
- 需要绑定计数器时(例如,速率限制)
5.3.1.3. 使用示例 复制链接链接已复制到粘贴板!
下面有一个使用绑定计数器的另一个示例:
compare-and-set 与 Compare-and-swap 示例:
使用 compare-and-swap 时,它会保存一个调用计数器调用(counter.getValue ())
要将强计数器用作速率限制,请配置 上限和 lifetime span 参数,如下所示:
lifespan 参数是一个实验性功能,可能在以后的版本中删除。
5.3.2. WeakCounter 接口:需要速度时 复制链接链接已复制到粘贴板!
WeakCounter 将计数器的值存储在 Data Grid 缓存中的多个键中。创建的键数量由 concurrency-level 属性配置。每个密钥存储计数器值的部分状态,并可同时更新。它比 StrongCounter 的主要优点是缓存中的竞争。另一方面,其值的读取更为昂贵,不允许绑定。
reset 操作应谨慎处理。它不是 原子的,它会生成中间值。这些值可以被读取操作以及注册的任何监听程序查看。
可以使用 get 方法从 WeakCounter ()CounterManager 检索 WeakCounter。例如:
CounterManager counterManager = ...
StrongCounter aCounter = counterManager.getWeakCounter("my-counter);
CounterManager counterManager = ...
StrongCounter aCounter = counterManager.getWeakCounter("my-counter);
5.3.2.1. 弱计数器接口 复制链接链接已复制到粘贴板!
WeakCounter 添加以下方法:
它们与"StrongCounter 的方法类似,但它们不会返回新值。
5.3.2.2. 用例 复制链接链接已复制到粘贴板!
弱计数器最适合用例,因为不需要更新操作的结果,或者不需要计数器的值。收集统计数据是这样的用例的良好示例。
5.3.2.3. 例子 复制链接链接已复制到粘贴板!
下面有一个弱计数器用法的示例。
5.4. 通知和事件 复制链接链接已复制到粘贴板!
强和弱计数器都支持监听程序接收其更新事件。侦听器必须实现 CounterListener,并可使用以下方法注册:
<T extends CounterListener> Handle<T> addListener(T listener);
<T extends CounterListener> Handle<T> addListener(T listener);
CounterListener 有以下接口:
public interface CounterListener {
void onUpdate(CounterEvent entry);
}
public interface CounterListener {
void onUpdate(CounterEvent entry);
}
返回的 Handle 对象具有在不再需要时删除 CounterListener 的主要目标。另外,它还允许访问它处理的 CounterListener 实例。它有以下接口:
public interface Handle<T extends CounterListener> {
T getCounterListener();
void remove();
}
public interface Handle<T extends CounterListener> {
T getCounterListener();
void remove();
}
最后,CounterEvent 具有以前的值和状态。它有以下接口:
该状态始终是 State.VALID,用于未绑定的计数器和弱计数器。state.LOWER_BOUND_REACHED 和 State.UPPER_BOUND_REACHED 仅对绑定的强大计数器有效。
弱计数器 reset () 操作将触发带有中间值的多个通知。
第 6 章 使用 CDI 扩展 复制链接链接已复制到粘贴板!
Data Grid 提供了一个与 CDI (Context 和依赖注入)编程模型集成的扩展,并允许您:
- 配置缓存并注入到 CDI Beans 和 Java EE 组件。
- 配置缓存管理器。
- 接收缓存和缓存管理器级别事件。
- 使用 JCache 注解控制数据存储和检索。
6.1. CDI 依赖项 复制链接链接已复制到粘贴板!
使用以下依赖项之一更新 pom.xml,以便在项目中包含 Data Grid CDI 扩展:
嵌入式(库)模式
<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-cdi-embedded</artifactId> </dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-cdi-embedded</artifactId>
</dependency>
服务器模式
<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-cdi-remote</artifactId> </dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-cdi-remote</artifactId>
</dependency>
6.2. 注入嵌入式缓存 复制链接链接已复制到粘贴板!
设置 CDI Bean 以注入嵌入式缓存。
流程
创建缓存限定器注解。
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - 1
- 创建
@GreetingCache限定符。
添加定义缓存配置的制作者方法。
Copy to Clipboard Copied! Toggle word wrap Toggle overflow 添加创建集群缓存管理器的制作者方法(如果需要)
Copy to Clipboard Copied! Toggle word wrap Toggle overflow 注意缓存管理器是高权重的对象。在应用程序中运行多个缓存管理器可能会降低性能。在注入多个缓存时,将每个缓存的限定符添加到缓存管理器制作者方法,或者不添加任何限定符。
将
@GreetingCache限定符添加到您的缓存注入点。Copy to Clipboard Copied! Toggle word wrap Toggle overflow
6.3. 注入远程缓存 复制链接链接已复制到粘贴板!
设置 CDI Bean 以注入远程缓存。
流程
创建缓存限定器注解。
Copy to Clipboard Copied! Toggle word wrap Toggle overflow 将
@RemoteGreetingCache限定符添加到您的缓存注入点。Copy to Clipboard Copied! Toggle word wrap Toggle overflow
注入远程缓存的提示
您可以注入远程缓存,而无需使用限定符。
... @Inject @Remote("greetingCache") private RemoteCache<String, String> cache;... @Inject @Remote("greetingCache") private RemoteCache<String, String> cache;Copy to Clipboard Copied! Toggle word wrap Toggle overflow 如果您有多个数据中心集群,您可以为集群创建单独的远程缓存管理器制作者。
Copy to Clipboard Copied! Toggle word wrap Toggle overflow
6.4. JCACHE 缓存注解 复制链接链接已复制到粘贴板!
当 JCache 工件位于 classpath 中时,您可以在 CDI 管理的 Bean 中使用以下 JCache 缓存注解:
@CacheResult- 缓存方法调用的结果。
@CachePut- 缓存方法参数。
@CacheRemoveEntry- 从缓存中删除条目。
@CacheRemoveAll- 从缓存中删除所有条目。
目标类型: 您只能在方法中使用这些 JCache 缓存注解。
要使用 JCache 缓存注解,请在应用程序的 Bean.xml 文件中声明拦截器。
受管环境(应用服务器)
非受管环境(独立)
JCACHE 缓存注解示例
以下示例演示了 @CacheResult 注释如何缓存 GreetingService.greet () 方法的结果:
使用 JCache 注解时,默认缓存使用注释方法的完全限定名称及其参数类型,例如:org.infinispan.example.GreetingService.greet (java.lang.String)
要使用非默认缓存,请使用 cacheName 属性来指定 cache 名称,如下例所示:
@CacheResult(cacheName = "greeting-cache")
@CacheResult(cacheName = "greeting-cache")
6.5. 接收缓存和缓存管理器事件 复制链接链接已复制到粘贴板!
您可以使用 CDI 事件接收缓存和缓存管理器级别事件。
-
使用
@Observes注释,如下例所示:
第 7 章 locking 和 Concurrency 复制链接链接已复制到粘贴板!
Data Grid 利用多版本的并发控制(MVCC)- 一个与关系数据库和其他数据存储中流行的并发方案。MVCC 比粒度 Java 同步提供了许多优点,甚至 JDK Locks 用于访问共享数据,包括:
- 允许并发读取器和写卡器
- 读者和写者不会阻止另一个操作
- 可以检测和处理写入 skews
- 内部锁定可以是条带的
7.1. 锁定实施详情 复制链接链接已复制到粘贴板!
Data Grid 的 MVCC 实施利用最小锁定和同步,大量地与无锁技术(如 比较和交换 和无锁定数据结构)中实现,这有助于对多 CPU 和多核环境进行优化。
特别是,数据中心 MVCC 实现主要针对读卡器进行了优化。读取器线程不会为条目获得显式锁定,而是直接读取问题中的条目。
另一方面,写者需要获取写入锁定。这样可确保每个条目只有一个并发写入器,从而导致并发写入程序队列更改条目。
要允许并发读取,写者通过将条目嵌套在 MVCCEntry 中来制作它们要修改的条目的副本。此副本隔离并发读取器与查看部分修改的状态。写入完成后,MV CCEntry.commit () 将刷新对数据容器的更改,后续的读取器会看到写入的更改。
7.1.1. 集群缓存和锁定 复制链接链接已复制到粘贴板!
在 Data Grid 集群中,主所有者节点负责锁定密钥。
对于非事务缓存,Data Grid 会将写入操作转发到密钥的主所有者,以便它可以尝试锁定它。然后,数据中心会将写入操作转发到其他所有者,如果无法锁定密钥,则抛出异常。
如果操作有条件且在主所有者上失败,Data Grid 不会将其转发到其他所有者。
对于事务缓存,主所有者可以使用选择和模拟锁定模式锁定密钥。数据中心还支持不同的隔离级别来控制事务之间的并发读取。
7.1.2. LockManager 复制链接链接已复制到粘贴板!
LockManager 是一个负责锁定写入条目的组件。LockManager 使用 LockContainer 来查找/保留/创建锁定。LockContainers 分为两个广泛的 flavours,支持锁定条带,支持每个条目有一个锁定。
7.1.3. 锁定条带 复制链接链接已复制到粘贴板!
锁定需要整个缓存的固定大小共享锁集合,以及根据条目的密钥哈希代码分配给条目的锁定。与 JDK 的 ConcurrentHashMap 分配锁的方式类似,这允许在交换中实现高度可扩展、固定头锁定机制,以交换处理可能被同一锁阻断的相关条目。
另一种方法是禁用锁定条带 - 这意味着 每个条目都会创建新的 锁定。这种方法 可能会 为您提供更大的并发吞吐量,但将以额外内存使用量、垃圾收集时间等成本为代价。
默认情况下,锁定条带被禁用,因为当不同密钥的锁在同一锁定条带中最终时可能会出现潜在的死锁。
可以使用 < locking /> 配置元素的 concurrencyLevel 属性调整锁定条带使用的共享锁定集合的大小。
配置示例:
<locking striping="false|true"/>
<locking striping="false|true"/>
或者
new ConfigurationBuilder().locking().useLockStriping(false|true);
new ConfigurationBuilder().locking().useLockStriping(false|true);
7.1.4. 并发级别 复制链接链接已复制到粘贴板!
除了确定条带锁定容器的大小外,此并发级别还用于调整任何基于 JDK ConcurrentHashMap 的集合(如内部到 DataContainers)。如需了解并发级别的详细讨论,请参阅 JDK ConcurrentHashMap Javadocs,因为这个参数在 Data Grid 中完全相同。
配置示例:
<locking concurrency-level="32"/>
<locking concurrency-level="32"/>
或者
new ConfigurationBuilder().locking().concurrencyLevel(32);
new ConfigurationBuilder().locking().concurrencyLevel(32);
7.1.5. 锁定超时 复制链接链接已复制到粘贴板!
锁定超时指定等待内容锁定的时间(以毫秒为单位)。
配置示例:
<locking acquire-timeout="10000"/>
<locking acquire-timeout="10000"/>
或者
new ConfigurationBuilder().locking().lockAcquisitionTimeout(10000); //alternatively new ConfigurationBuilder().locking().lockAcquisitionTimeout(10, TimeUnit.SECONDS);
new ConfigurationBuilder().locking().lockAcquisitionTimeout(10000);
//alternatively
new ConfigurationBuilder().locking().lockAcquisitionTimeout(10, TimeUnit.SECONDS);
7.1.6. 一致性 复制链接链接已复制到粘贴板!
单个所有者被锁住(而不是所有所有者被锁住)实际上不会破坏以下一致性保证:如果键 K 被哈希成节点 {A、B} 和事务 TX1 来获取 K 的锁定,请参阅 A。如果另一个事务在 B (或任何其他节点)上启动, 会尝试锁定 TX2 K,那么它将失败并显示超时,因为已由 TX1 保留。这样做的原因是,无论事务的来源是什么,都会始终确定地获取密钥 K 的锁定。
7.2. 数据版本控制 复制链接链接已复制到粘贴板!
数据中心支持两种数据版本控制:简单和外部。简单的版本控制用于事务缓存进行写入 skew 检查。
外部版本控制用于封装 Data Grid 中的数据版本的外部来源,例如在使用带有 Hibernate 的 Data Grid 时,后者直接从数据库获取其数据版本信息。
在这个方案中,需要传递版本的机制变得是必需的,并在 AdvancedCache 中提供 和 putForExternalRead ()来采用外部数据版本。然后,这存储在 putForExternalRead () InvocationContext 上,并在提交时应用到该条目。
写入 skew 检查无法进行,在外部数据版本时不会执行。
第 8 章 Transactions 复制链接链接已复制到粘贴板!
Data Grid 可以配置为使用并参与 JTA 兼容事务。
或者,如果禁用事务支持,它等同于在 JDBC 调用中使用 autocommit,每次更改后可能会复制修改(如果启用了复制)。
在每个缓存操作 Data Grid 中执行以下操作:
- 检索与线程关联的当前 事务
- 如果尚未完成,请在事务提交或回滚时将 XAResource 注册到事务管理器以通知。
为此,必须提供对环境的 TransactionManager 的引用。这通常是通过使用 TransactionManagerLookup 接口的实现的类名称配置缓存来实现。缓存启动时,它将创建一个此类实例,并调用其 getTransactionManager () 方法,它会返回对 TransactionManager 的引用。
Data Grid 附带几个事务管理器查找类:
事务管理器查找实现
- EmbeddedTransactionManagerLookup :这提供了一个基本的事务管理器,该管理器应在没有其他实现可用时用于嵌入式模式。这种实现对并发事务和恢复有一些严重限制。
-
JBossStandaloneJTAManagerLookup :如果您在独立环境中运行 Data Grid,或者在 JBoss AS 7 及更早版本以及 WildFly 8、9 和 10 中运行,则这应该是您的事务管理器的默认选择。它是基于 JBoss 事务的完整交易管理器,它解决了
嵌入式TransactionManager的所有破坏。 - WildflyTransactionManagerLookup :如果您在 WildFly 11 或更高版本中运行 Data Grid,则这应该是您的事务管理器的默认选择。
-
GenericTransactionManagerLookup :这是一个查找类,用于在最流行的 Java EE 应用服务器中查找事务管理器。如果没有找到事务管理器,则在
嵌入式TransactionManager上默认设置。
警告: DummyTransactionManagerLookup 已在 9.0 中弃用,并将在以后的版本中删除。改为使用 EmbeddedTransactionManagerLookup。
初始化后,TransactionManager 也可以从 Cache 本身获取:
//the cache must have a transactionManagerLookupClass defined Cache cache = cacheManager.getCache(); //equivalent with calling TransactionManagerLookup.getTransactionManager(); TransactionManager tm = cache.getAdvancedCache().getTransactionManager();
//the cache must have a transactionManagerLookupClass defined
Cache cache = cacheManager.getCache();
//equivalent with calling TransactionManagerLookup.getTransactionManager();
TransactionManager tm = cache.getAdvancedCache().getTransactionManager();
8.1. 配置事务 复制链接链接已复制到粘贴板!
事务在缓存级别配置。以下是影响事务行为的配置,以及每个配置属性的一小部分描述。
或者以编程方式:
-
isolated- 配置隔离级别。检查部分 隔离级别 以了解更多详细信息。默认为REPEATABLE_READ。 -
locking- 配置缓存是否使用 optimistic 或 pessimistic 锁定。检查 事务锁定 部分以了解更多详细信息。默认为OPTIMISTIC。 -
auto-commit- 如果启用,用户不需要为单个操作手动启动事务。事务会自动启动和提交。默认为true。 -
complete-timeout- 提供有关完成事务的信息的持续时间(毫秒)。默认值为60000。 mode- 配置缓存是否事务。默认为NONE。可用的选项有:-
NONE- 非事务缓存 -
FULL_XA- 启用恢复的 XA 事务缓存。检查部分 事务恢复 以了解更多有关恢复的详细信息。 -
NON_DURABLE_XA- XA 事务缓存禁用恢复。 -
NON_XA- 通过 同步 而不是 XA 集成的事务缓存。检查部分 Enlisting synchronizations 以了解详细信息。 -
BATCH- 使用批处理对操作进行分组的事务缓存。检查 Batching 部分以了解详细信息。
-
-
通知- 启用/禁用在缓存监听器中触发事务事件。默认为true。 -
Reaper-interval - 清理事务完成信息的线程的间隔(以毫秒为单位)。默认值为30000。 -
recovery-cache- 配置缓存名称以存储恢复信息。检查部分 事务恢复 以了解更多有关恢复的详细信息。默认为recoveryInfoCacheName。 -
stop-timeout- 缓存停止时等待持续事务的时间(毫秒)。默认为30000。 -
transaction-manager-lookup- 配置查找对javax.transaction.TransactionManager的引用的类的完全限定域名。Default isorg.infinispan.transaction.lookup.GenericTransactionManagerLookup.
有关如何在 Data Grid 中实施 Two-Phase-Commit (2PC)的更多详细信息,以及如何获取锁。有关配置 参考 中提供了有关配置设置的更多详细信息。
8.2. 隔离级别 复制链接链接已复制到粘贴板!
Data Grid 提供两种隔离级别 - READ_COMMITTED 和 REPEATABLE_READ。
这些隔离级别决定了读者何时看到并发写入,并使用不同的 MVCCEntry 类进行内部实施,它们在如何提交回数据容器时的行为不同。
以下是一个更详细的示例,它可以帮助了解 Data Grid 上下文中 READ_COMMITTED 和 REPEATABLE_READ 之间的区别。使用 READ_COMMITTED 时,如果在同一键的两个连续读取调用之间,键已被另一个事务更新,第二个读取可能会返回新的更新的值:
使用 REPEATABLE_READ 时,最终的 get 仍会返回 v。因此,如果您在事务内多次检索同一密钥,您应该使用 REPEATABLE_READ。
但是,因为 REPEATABLE_READ 也不会获取 read-locks,所以可能会出现这个缺陷:
8.3. 事务锁定 复制链接链接已复制到粘贴板!
8.3.1. pessimistic 事务缓存 复制链接链接已复制到粘贴板!
从锁定的角度的角度来看,在写入密钥时,pessimistic 事务会获取密钥的锁定。
- 锁定请求发送到主所有者(可以是显式锁定请求或操作)
主所有者尝试获取锁定:
- 如果成功,它会发回正回复;
- 否则,会发送负回复,并回滚事务。
例如:
transactionManager.begin(); cache.put(k1,v1); //k1 is locked. cache.remove(k2); //k2 is locked when this returns transactionManager.commit();
transactionManager.begin();
cache.put(k1,v1); //k1 is locked.
cache.remove(k2); //k2 is locked when this returns
transactionManager.commit();
当 cache.put (k1,v1) 返回时,k1 会被锁定,且集群中其它任何事务都无法写入它。仍可读取 k1。当事务完成时(提交或回滚)后,k1 上的锁定会被释放。
对于条件操作,验证在 originator 中执行。
8.3.2. 光率事务缓存 复制链接链接已复制到粘贴板!
在事务准备时,会获取特定的事务锁定,且仅保留在事务提交(或回滚)上点。这与 5.0 默认锁定模型不同,本地锁定在写时获得,并在准备期间获取集群锁定。
- 准备发送到所有所有者。
主所有者会尝试获取所需的锁定:
- 如果锁定成功,它将执行写入偏移检查。
- 如果写入偏移检查成功(或被禁用),请发送正回复。
- 否则,会发送负回复,并回滚事务。
例如:
transactionManager.begin(); cache.put(k1,v1); cache.remove(k2); transactionManager.commit(); //at prepare time, K1 and K2 is locked until committed/rolled back.
transactionManager.begin();
cache.put(k1,v1);
cache.remove(k2);
transactionManager.commit(); //at prepare time, K1 and K2 is locked until committed/rolled back.
对于条件命令,验证仍然在原始卷中发生。
8.3.3. 我需要什么 - pessimistic 或 optimistic 事务? 复制链接链接已复制到粘贴板!
从用例的角度来看,当同时运行的多个事务之间没有竞争时,应使用选择的事务。这是因为,如果数据在读取和提交的时间之间有所变化(启用了 write skew 检查),则最佳事务回滚。
另一方面,当密钥和事务回滚有高竞争时,pessimistic 事务可能更加适合。Pessimistic 事务本身更经济:每个写入操作可能涉及 RPC 进行锁定。
8.4. 写入 Skews 复制链接链接已复制到粘贴板!
当两个事务独立,同时读取和写入同一密钥时,会发生写入 skews。写入 skew 的结果是,两个事务都成功向同一键提交更新,但具有不同值。
Data Grid 自动执行写入框架检查,以确保特定事务中的 REPEATABLE_READ 隔离级别的数据一致性。这允许数据仓库检测并回滚其中一个事务。
在 LOCAL 模式下运行时,写入 skew 检查依赖于 Java 对象引用来比较区别,它提供了可靠的技术来检查写入 skews。
8.4.1. 在 pessimitic 事务中的密钥强制写入锁定 复制链接链接已复制到粘贴板!
为避免使用 pessimistic 事务写入 skews,使用 Flag.FORCE_WRITE_LOCK 在启动时锁定密钥。
-
在非事务缓存中,
Flag.FORCE_WRITE_LOCK无法正常工作。get ()调用读取键值,但不远程获取锁定。 -
您应该将
Flag.FORCE_WRITE_LOCK与后续在同一事务中更新实体的事务一起使用。
比较 Flag.FORCE_WRITE_LOCK 示例的代码片段:
8.5. 处理例外 复制链接链接已复制到粘贴板!
如果 CacheException (或它的子类)通过 JTA 事务范围内的缓存方法抛出,则事务会自动标记为回滚。
8.6. Enlisting synchronizations 复制链接链接已复制到粘贴板!
默认情况下,Data Grid 通过 XAResource 将自身注册为分布式事务中的第一个类。在某些情况下,Data Grid 不需要在事务中成为参与,但只能由其生命周期通知(准备和完整):例如,在 Hibernate 中将 Data Grid 用作第二级缓存。
Data Grid 允许通过同步进行事务 加入。要启用它只是使用 NON_XA 事务模式。
同步具有优势,它们允许 TransactionManager 使用 1PC 优化 2PC,其中只有一个其他资源被该事务列入(最后资源提交优化)。例如Hibernate 第二级缓存:如果 Data Grid 将自身注册到 TransactionManager 作为 XAResource,则 TransactionManager 会看到两个 XAResource (缓存和数据库),且不会进行这种优化。必须在两个资源之间协调,需要将 tx 日志写入磁盘。另一方面,将 Data Grid 注册为 同步 会导致 TransactionManager 跳过将日志写入磁盘(性能改进)。
8.7. 批处理 复制链接链接已复制到粘贴板!
批量允许事务的原子性和一些特征,但不能完全合并 JTA 或 XA 功能。与全用事务相比,批处理通常是更轻便且更便宜的事务。
通常,每当事务的唯一参与都是 Data Grid 集群时,应使用批处理 API。另一方面,每当事务涉及多个系统时,应使用 JTA 事务(涉及 TransactionManager)。例如,考虑到"Hello world!"的交易:从一个企业账户传输到其他账户。如果两个帐户都存储在 Data Grid 中,可以使用批处理。如果一个帐户位于数据库中,另一个是 Data Grid,则需要分布式事务。
您不必 定义事务管理器来使用批处理。
8.7.1. API 复制链接链接已复制到粘贴板!
将缓存配置为使用批处理后,您可以通过调用 Cache 上的 startBatch () 和 endBatch () 来使用它。E.g.,
8.7.2. 批处理和 JTA 复制链接链接已复制到粘贴板!
在评分后面,批处理功能会启动 JTA 事务,并且该范围内的所有调用都与其关联。为此,请使用非常简单(例如,没有恢复)内部 TransactionManager 实现。使用批处理时,您可以获取:
- 在批处理完成前,您获取在调用期间获取的锁定
- 作为批处理完成过程的一部分,更改均在批处理中复制。减少批处理中每个更新的复制时间。
- 如果使用同步复制或无效,复制/无效中的故障将导致批处理回滚。
- 所有与事务相关的配置也适用于批处理。
8.8. 事务恢复 复制链接链接已复制到粘贴板!
恢复是 XA 事务的一个功能,它处理资源的最终性,甚至可能事务管理器失败,并从此类情况下进行相应恢复。
8.8.1. 何时使用恢复 复制链接链接已复制到粘贴板!
考虑从存储在外部数据库中的帐户传输的分布式事务到数据存储在 Data Grid 中的帐户。调用 TransactionManager.commit () 时,两个资源都准备成功(1st 阶段)。在提交(2nd)阶段,数据库在从事务管理器接收提交请求前成功应用 whilst Data Grid 的更改。此时,系统处于不一致的状态:从外部数据库中的帐户获取负担,但还没有在 Data Grid 中看到(因为锁定只在两阶段提交协议的第二阶段发布)。恢复会处理这种情况,以确保数据库和数据仓库中的数据都处于一致状态。
8.8.2. 它如何工作 复制链接链接已复制到粘贴板!
恢复由事务管理器协调。事务管理器与 Data Grid 合作,确定需要人工干预的事务列表,并告知系统管理员(通过电子邮件、日志警报等)。这个过程特定于事务管理器,但通常需要在事务管理器上进行一些配置。
了解 in-doubt transaction ids,系统管理员现在可以连接到 Data Grid 集群,并重播事务的提交或强制回滚。Data Grid 提供了 JMX 工具 - 这在 交易恢复和协调 部分中进行了广泛说明。
8.8.3. 配置恢复 复制链接链接已复制到粘贴板!
在 Data Grid 中不默认启用恢复。如果禁用,TransactionManager 将无法与 Data Grid 合作来确定 in-doubt 事务。事务配置 部分演示了如何启用它。
注意: restore-cache 属性不是强制的,它为每个缓存进行配置。
要使恢复正常工作,必须将 模式设置为 FULL_XA,因为需要全用 XA 事务。
8.8.3.1. 启用 JMX 支持 复制链接链接已复制到粘贴板!
为了可以使用 JMX 管理恢复 JMX 支持,必须明确启用。
8.8.4. 恢复缓存 复制链接链接已复制到粘贴板!
为了跟踪 in-doubt 事务并能够回复它们,Data Grid 会缓存所有事务状态以供将来使用。此状态仅针对 in-doubt 事务而保留,在提交/注册阶段完成后为成功完成的事务被删除。
这个 in-doubt 事务数据保存在本地缓存中:这允许一个通过缓存加载程序将这个信息转换为磁盘,以防它太大。此缓存可以通过 recovery-cache 配置属性来指定。如果没有指定 Data Grid,则会为您配置本地缓存。
(尽管并不强制)在所有启用了恢复的 Data Grid 缓存间共享相同的恢复缓存。如果默认恢复缓存被覆盖,则指定的恢复缓存必须使用 TransactionManagerLookup 返回与缓存本身使用的不同事务管理器。
8.8.5. 与事务管理器集成 复制链接链接已复制到粘贴板!
虽然这特定于事务管理器,但通常事务管理器需要引用 XAResource 实现才能在其上调用 XAResource.recover ()。要获取对 Data Grid XAResource 的引用,可以使用以下 API:
XAResource xar = cache.getAdvancedCache().getXAResource();
XAResource xar = cache.getAdvancedCache().getXAResource();
常见做法是在与运行事务的不同进程中运行恢复。
8.8.6. 协调 复制链接链接已复制到粘贴板!
事务管理器以专有的方式告知系统管理员在不做的事务中。在这个阶段,系统管理员知道事务的 XID (字节阵列)。
正常恢复流程是:
- STEP 1: 系统管理员通过 JMX 连接到 Data Grid 服务器,并列出模糊的事务。下图展示了 JConsole 连接到具有模糊事务的 Data Grid 节点。
图 8.1. 显示 in-doubt 事务
此时会显示每个 in-doubt 事务的状态(在本例中为" PREPARED ")。status 字段中可能有多个元素,例如:在特定节点上提交事务但没有在所有节点上提交时,"PREPARED"和"COMMITTED"。
- STEP 2: 系统管理员可视化地将从事务管理器收到的 XID 映射到数据中心内部 ID,以数字表示。此步骤是必需的,因为 XID (字节阵列)无法方便地传递给 JMX 工具(如 JConsole),然后在 Data Grid 端重新编译。
- STEP 3 :系统管理员根据内部 ID 强制通过对应的 jmx 操作强制事务的提交/注册。以下镜像通过强制根据其内部 ID 提交事务来获取。
图 8.2. 强制提交
无论事务的来源是什么,都可以在任何节点上执行上述所有 JMX 操作。
8.8.6.1. 根据 XID 强制提交/注册 复制链接链接已复制到粘贴板!
用于强制进行事务的提交/回滚的基于 XID 的 JMX 操作也可以可用:这些方法收到描述 XID 而不是与事务关联的数字(如前面在第 2 步所述)。它们很有用,例如,如果一个希望为特定未做的事务设置自动完成作业。这个过程被插入到事务管理器的恢复中,并可访问事务管理器的 XID 对象。
第 9 章 在网格中执行代码 复制链接链接已复制到粘贴板!
缓存的主要优点是能够快速为其键查找值,甚至跨机器也是如此。实际上,仅使用这个用途可能是许多用户使用 Data Grid 的原因。但是,数据中心可以提供许多无法立即出现的好处。由于 Data Grid 通常在一台机器集群中使用,因此我们也有可用功能,可帮助利用整个集群来执行用户所需的工作负载。
本节仅涵盖使用嵌入式缓存在网格中执行代码,如果您使用远程缓存,您应该查看在远程网格中执行代码的详细信息。
9.1. Cluster Executor 复制链接链接已复制到粘贴板!
由于您拥有一组计算机,因此利用它们组合的计算能力在所有这些机器上执行代码。缓存管理器附带一个 一 个实用程序,可让您在集群中执行任意代码。请注意,这个功能不需要使用缓存。此 Cluster Executor 可以通过调用 EmbeddedCacheManager 上的 executor ()来检索。这个 executor 可在集群和非集群配置中检索。
ClusterExecutor 是专门执行代码,代码在缓存中的数据不会依赖,而是用作帮助用户在集群中轻松执行代码的方法。
此管理器专门使用 Java 8 构建,因此所有方法都使用 Java 8,因此所有方法都使用一个功能接口作为参数。此外,由于这些参数将发送到需要序列化的其他节点。我们甚至使用一种相似的欺骗来确保我们的 lambdas 会立即被序列化。通过具有参数实现 Serializable 和 real 参数类型(例如,可运行或功能)。在确定要调用的方法时,JRE 将选择最具体的类,因此您的 lambdas 将始终可以被序列化。也可以使用 Externalizer 来进一步减小消息大小。
管理器默认会将给定命令提交到集群中的所有节点,包括从中提交的节点。您可以使用 filterTargets 方法来控制在哪些节点上执行任务,如 部分所述。
9.1.1. 过滤执行节点 复制链接链接已复制到粘贴板!
可以在哪个节点上运行命令。例如,您可能只想在同一机架的机器上运行计算。或者,您可能希望在本地站点和不同站点上执行一次操作。集群执行器可以限制其在相同或不同机器、机架或站点级别发送请求的节点。
SameRack.java
EmbeddedCacheManager manager = ...; manager.executor().filterTargets(ClusterExecutionPolicy.SAME_RACK).submit(...)
EmbeddedCacheManager manager = ...;
manager.executor().filterTargets(ClusterExecutionPolicy.SAME_RACK).submit(...)
要使用这个拓扑基础过滤,您必须通过 Server Hinting 来启用拓扑了解一致的哈希。
您还可以根据节点的地址使用 predicate 来过滤。这也可以选择与之前的代码片段中基于拓扑的过滤合并。
我们还允许任何方法选择目标节点,使用 Predicate 来过滤哪些节点可以被考虑执行。请注意,这也可以与 Topology 过滤结合使用,以便更精细地控制集群中执行代码的位置。
Predicate.java
EmbeddedCacheManager manager = ...; // Just filter manager.executor().filterTargets(a -> a.equals(..)).submit(...) // Filter only those in the desired topology manager.executor().filterTargets(ClusterExecutionPolicy.SAME_SITE, a -> a.equals(..)).submit(...)
EmbeddedCacheManager manager = ...;
// Just filter
manager.executor().filterTargets(a -> a.equals(..)).submit(...)
// Filter only those in the desired topology
manager.executor().filterTargets(ClusterExecutionPolicy.SAME_SITE, a -> a.equals(..)).submit(...)
9.1.2. Timeout(超时) 复制链接链接已复制到粘贴板!
Cluster Executor 允许在每次调用设置超时。默认为传输配置中配置的分布式同步超时。这个超时可在集群和非集群缓存管理器中工作。当超时过期时,executor 可能会或可能无法中断执行任务的线程。但是,当超时发生任何 消费者 或 将来 时,将完成后一个 TimeoutException。此值可通过 ivoking timeout 方法并提供所需的持续时间来覆盖。
9.1.3. 单一节点子传输 复制链接链接已复制到粘贴板!
Cluster Executor 也可以在单一节点提交模式下运行,而不是向所有节点提交命令,而是选择通常收到该命令的节点,而是将其提交给一个节点。每个提交都可能会使用不同的节点来执行该任务。这对使用 ClusterExecutor 作为 java.util.concurrent.Executor 非常有用,您可能会注意到 ClusterExecutor 实现。
SingleNode.java
EmbeddedCacheManager manager = ...; manager.executor().singleNodeSubmission().submit(...)
EmbeddedCacheManager manager = ...;
manager.executor().singleNodeSubmission().submit(...)
9.1.3.1. 故障切换 复制链接链接已复制到粘贴板!
在单节点提交中运行时,可能还希望允许 Cluster Executor 处理在处理给定命令的过程中发生异常的情况,方法是再次重试命令。当发生这种情况时,Cluster Executor 将再次选择一个节点,以重新提交命令,以最多重新提交所需的故障切换尝试。请注意,所选节点可以是通过拓扑或 predicate 检查的任何节点。通过调用覆盖的 singleNodeSubmission 方法来启用故障转移。指定的命令将重新提交到单一节点,直到命令完成且无例外,或者总提交数等于提供的故障转移数。
9.1.4. 示例: PI Approximation 复制链接链接已复制到粘贴板!
本例演示了如何使用 ClusterExecutor 来估算 PI 的值。
从通过 Cluster Executor 的并行分布式执行中受益。重新调用方括号的区域是 Sa = 4r2,circle 的区域是 Ca=pi598r2。将 r2 从第二个 equation 取代成第一个,它关闭了前者 = 4114 Ca/Sa。现在,我们可以将大量 darts 放入一个方括号中的镜像;如果我们花费了在总圆圈数上,我们会有一个大约 Ca/Sa 值的 darts 比率。由于我们知道,像像 = 4114 Ca/Sa,我们可以轻松派生出实际价值。我们还可以获得更好的应用方案。在以下示例中,我们执行 1 亿的 dart,而不是"kooting"它们,我们可在整个 Data Grid 集群中并行化工作。请注意,这会在 1 的集群中正常工作,但会较慢。
第 10 章 流 复制链接链接已复制到粘贴板!
您可能希望处理缓存中的子集或所有数据来生成结果。这可能会对映射带来影响。Data Grid 允许用户执行非常相似,但使用标准的 JRE API 这样做。Java 8 引入了一个 流 的概念,允许对集合进行功能风格的操作,而不必自行迭代数据。流操作可以通过与 MapReduce 非常相似来实现。与 MapReduce 一样,流允许您对整个缓存执行处理,可能是一个非常大的数据集,但效率更高。
在处理缓存中存在的数据时,流是首选的方法,因为流会自动调整集群拓扑更改。
此外,我们还可以控制如何在什么情况下迭代条目,如果您希望同时对集群执行所有操作,则该缓存中可以更有效地执行操作。
通过调用 stream 或 parallelStream 方法,从 entrySet、keySet 或 value 集合检索流。https://access.redhat.com/webassets/avalon/d/red-hat-data-grid/8.3/api/org/infinispan/Cache.html#values--
10.1. 常见流操作 复制链接链接已复制到粘贴板!
本节重点介绍了各种选项,无论您使用的底层缓存类型是什么。
10.2. 键盘过滤 复制链接链接已复制到粘贴板!
可以过滤流,使其仅在给定的键子集上运行。这可以通过在 CacheStream 上调用 filterKeys 方法来完成。这应该始终通过 Predicate 过滤器使用,如果 predicate 包含所有密钥,则速度会更快。
如果您熟悉了 高级Cache 界面,您可能会省略您甚至通过这个 keyFilter 使用 getAll 的原因。如果您需要条目,且需要本地节点中的所有内存中,则使用 getAll 有一些小的好处(大的有效负载)。但是,如果您需要在这些元素上处理流,因为您将获得分布式和线程并行。
10.3. 基于片段的过滤 复制链接链接已复制到粘贴板!
这是一个高级功能,应该只用于对数据中心段和散列技术的深入知识。如果您需要将数据划分为单独的调用,基于这些片段的过滤会很有用。这在与其他工具(如 Apache Spark )集成时非常有用。
这个选项仅支持复制和分布式缓存。这允许用户在由 KeyPartitioner 决定的数据子集上运行。可以通过在 CacheStream 上调用 filterKeySegments 方法来过滤片段。这在密钥过滤器后应用,但在执行任何中间操作之前。
10.4. local/Invalidation 复制链接链接已复制到粘贴板!
与本地或无效缓存一起使用的流只能与在常规集合上使用流的方式相同。如果需要,Data Grid 处理所有翻译,并处理所有更值得关注的选项(例如 storeAsBinary 和一个缓存加载程序)。只有执行流操作的节点的数据才会被使用,例如无效只使用本地条目。
10.5. 示例 复制链接链接已复制到粘贴板!
以下代码采用缓存并返回映射,其中包含所有缓存条目的映射,其值包含字符串 "JBoss"
Map<Object, String> jbossValues =
cache.entrySet().stream()
.filter(e -> e.getValue().contains("JBoss"))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Map<Object, String> jbossValues =
cache.entrySet().stream()
.filter(e -> e.getValue().contains("JBoss"))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
10.6. distribution/Replication/Scattered 复制链接链接已复制到粘贴板!
这是流进入它们的位置。执行流操作时,它将向具有特定数据的每个节点发送各种中间和终端操作。这允许在拥有数据的节点中处理中间值,并且只能将最终结果发送到原始节点,从而提高性能。
10.6.1. Rehash Aware 复制链接链接已复制到粘贴板!
数据内部被分段,每个节点仅在它拥有为主所有者的数据上执行操作。这允许平均处理数据,假设片段足以提供每个节点上的相同数据。
当您使用分布式缓存时,当新节点加入或离开时,可以在节点间重新生成数据。分布式流会自动处理这些数据,因此您不必在节点离开或加入集群时担心监控。Reshuffled 条目可能会第二次处理,并且我们在密钥级别或网段级别(取决于终端操作)跟踪已处理的条目,以限制重复处理的数量。
有可能禁用对流的重新哈希感知。只有在您的请求只能看到重新哈希时,才应考虑这一点。这可以通过调用 CacheStream.disableRehashAware () 来达到大多数操作的性能,当无法完全发生重新哈希时。唯一的例外是迭代器和各自使用较少的内存,因为它们不必跟踪处理的密钥。
请参考禁用重新哈希感知,除非您真正知道您的功能。
10.6.2. 序列化 复制链接链接已复制到粘贴板!
由于操作发送到其他节点,它们必须可以被 Data Grid marshalling 序列化。这允许将操作发送到其他节点。
最简单的方法是使用 CacheStream 实例,并像通常一样使用 lambda。Data Grid 会覆盖所有各种流中间和终端方法,以获取参数的 Serializable 版本(如 SerializableFunction、SerializablePredicate…)您可以在 CacheStream 中找到这些方法。这依赖于 spec 来选择 此处定义的 最具体的方法。
在上例中,我们使用 Collector 将所有结果收集到 映射中。不幸的是 Collectors 类不会生成 Serializable 实例。因此,如果您需要使用这些方法,可以通过两种方式完成此操作:
一个选项是使用 CacheCollectors 类,允许提供一个 Supplier<Collector& gt;。然后,此实例可以使用 Collector 提供没有序列化的 Collector。https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html
Map<Object, String> jbossValues = cache.entrySet().stream()
.filter(e -> e.getValue().contains("Jboss"))
.collect(CacheCollectors.serializableCollector(() -> Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
Map<Object, String> jbossValues = cache.entrySet().stream()
.filter(e -> e.getValue().contains("Jboss"))
.collect(CacheCollectors.serializableCollector(() -> Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
或者,您可以避免使用 CacheCollectors,而是使用使用 Supplier<Collector > 的超载 收集 方法。这些超载 收集 方法只能通过 CacheStream 接口提供。
Map<Object, String> jbossValues = cache.entrySet().stream()
.filter(e -> e.getValue().contains("Jboss"))
.collect(() -> Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Map<Object, String> jbossValues = cache.entrySet().stream()
.filter(e -> e.getValue().contains("Jboss"))
.collect(() -> Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
但是,如果您无法使用 Cache 和 CacheStream 接口,则无法使用 参数,您必须通过将 lambdas 广播到多个接口来手动分配 lambda。它并不是一个用户友善的,但会完成该作业。
Serializable
Map<Object, String> jbossValues = map.entrySet().stream()
.filter((Serializable & Predicate<Map.Entry<Object, String>>) e -> e.getValue().contains("Jboss"))
.collect(CacheCollectors.serializableCollector(() -> Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
Map<Object, String> jbossValues = map.entrySet().stream()
.filter((Serializable & Predicate<Map.Entry<Object, String>>) e -> e.getValue().contains("Jboss"))
.collect(CacheCollectors.serializableCollector(() -> Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
建议和最常见的方法是使用 AdvancedExternalizer,因为这提供了最小的有效负载。不幸的是,在手动之前,您无法使用 lamdbas 作为高级外部化器需要定义类。
您可以使用高级外部化程序,如下所示:
您还可以将高级外部化器用于收集器供应商,以进一步减小有效负载大小。
10.7. 并行编译 复制链接链接已复制到粘贴板!
默认情况下,分发流会尝试并行化。最终用户可以控制这一点,实际上它们始终必须控制其中一个选项。这些流是并行化的方法。
从缓存集合创建 流到每个节点的本地,最终用户可以在调用流或 并行Stream 方法之间进行选择。https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html#stream--根据是否已选择并行流,将在本地为每个节点启用多个线程。请注意,一些操作(如重新哈希)了解迭代器和每个操作始终在本地使用顺序流。这在某个时间点上可以增强,以允许在本地并行流。
在使用本地并行性时,用户应小心,因为它需要有大量条目或操作可以更快地计算。另外,请注意,如果用户使用并行流,每个流 都不应该阻止操作,因为该操作将在通用池中执行,通常为计算操作保留。
当存在多个 节点时,可能需要控制远程请求是否同时处理,还是一次处理。默认情况下,除 iterator 外的所有终端操作都会执行并发请求。迭代器,可以降低本地节点上的总体内存压力的方法,仅执行实际执行稍好的连续请求。
如果用户希望更改此默认值,但可以通过调用 CacheStream 上的 sequentialDistribution 或 parallelDistribution 方法来实现。
10.8. 任务超时 复制链接链接已复制到粘贴板!
可以为操作请求设置超时值。这个超时仅用于远程请求超时,它基于每个请求。前者意味着本地执行不会超时,后者意味着如果您有一个故障转移的场景,如后续请求上面所述,各自有新的超时。如果没有指定超时,它将使用复制超时作为默认超时。您可以通过执行以下操作在任务中设置超时:
CacheStream<Map.Entry<Object, String>> stream = cache.entrySet().stream(); stream.timeout(1, TimeUnit.MINUTES);
CacheStream<Map.Entry<Object, String>> stream = cache.entrySet().stream();
stream.timeout(1, TimeUnit.MINUTES);
有关此问题的更多信息,请查看 java doc in timeout javadoc。
10.9. 注入 复制链接链接已复制到粘贴板!
Stream 有一个终端操作,每个 操作都调用,允许对数据运行某种副作用操作。在这种情况下,可能需要获取支持此流的缓存的引用。如果您的 Consumer 实现 CacheAware 接口,则在 Consumer 接口的 accept 方法之前调用 injectCache 方法。
10.10. 分布式流执行 复制链接链接已复制到粘贴板!
分布式流执行的方式与映射减少非常相似。除这种情况外,我们向许多中间操作(映射、过滤等)以及单个终端操作向不同的节点发送零。该操作主要分为以下内容:
- 所需的片段通过哪个节点是给定片段的主所有者分组
生成一个请求来发送到包含中间和终端操作的每个远程节点,包括它应该处理的片段
- 如有必要,将在本地执行终端操作
- 每个远程节点都将接收此请求并运行操作,然后重新发送响应
- 然后,本地节点将收集本地响应和远程响应,执行操作本身所需的任何类型的减少。
- 然后,最终减少的响应会返回用户
在大多数情况下,所有操作都会完全分发,因为操作完全应用于每个远程节点上,通常仅重新应用与最后一个操作或一些相关的操作,以减少来自多个节点的结果。务必要注意的是,中间值实际上不必序列化,它是发送的最后一个值,这是需要的部分(除了各种操作除外)。
终端 operator 分布式结果 减少了以下段落描述了分布式减少如何工作各种终端操作器。其中一些是特别注意,一个中间值可能需要可以被序列化而不是最终的结果。
- allMatch noneMatch anyMatch
- allMatch 操作在每个节点上运行,然后所有结果都以逻辑方式,并在本地一起获取适当的值。noneMatch 和 anyMatch 操作都使用逻辑或者使用。这些方法还有早期终止支持,在已知最终结果后停止远程和本地操作。
- collect
- collect 方法值得注意,它可以执行一些额外的步骤。远程节点会正常执行所有内容,除了结果时不会执行 最终完成,而是返回完全合并的结果。然后,本地线程 将 远程和本地结果合并到最终完成的值中。这里的键值是最终的值不必可以被序列化,而是从供应商 和组合器方法生成的值。https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collector.html#supplier--
- 数�
- count 方法只从每个节点添加数字。
- findAny findFirst
- findAny 操作只返回它们找到的第一个值,无论是从远程节点还是本地找到。请注意,这支持早期终止,一旦找到一个值,它将不会处理其他值。请注意 findFirst 方法是特殊的,因为它需要排序的中间操作,这在 例外 部分中详细介绍。
- Max min
- max 和 min 方法在每个节点上找到对应的 min 或 max 值,然后在本地执行最终减少,以确保只返回所有节点的 min 或 max。
- 减少
- 各种减少方法 1、2,3 将最终对结果进行序列化,只要累积器可以做大量操作。然后,如果您提供了这一点,它会在本地组合本地和远程结果。请注意,来自组合器的值不必是 Serializable。
10.11. 基于密钥的重新哈希感知 operator 复制链接链接已复制到粘贴板!
迭代器、分割 器和每个 操作都与其他终端运算符不同,因为重新哈希感知必须跟踪每个片段已处理哪些键,而不是只是网段。这是为了保证一次(迭代和分割器)或至少一次(每个行为(每个)在集群成员资格更改时,也是如此。
远程节点上调用时 迭代器和 运算符将返回条目批处理,其中下一个批处理仅在最后使用后发回发送。完成此批处理,以限制给定时间在内存中有多少个条目。用户节点将保存在其处理的密钥以及给定片段完成后,它将从内存中释放这些密钥。这就是为什么首选使用迭代方法进行后续处理,因此一次仅在内存中保存一小部分片段密钥,而不是从所有节点保存。
分割 器
for every () 方法也会返回批处理,但它会在至少处理完成密钥后返回批量密钥。这样,原始节点可以知道已经处理哪些密钥,以减少处理同一条目的几率。不幸的是,当节点意外停机时,至少有可能有至少一次的行为。在这种情况下,节点可以处理批处理,但还没有完成,这些条目在重新哈希失败操作时将再次运行,但不会在完成批处理中再次运行。请注意,添加节点不会导致这个问题,因为重新哈希故障转移只有在收到所有响应前不会发生。
这些操作批处理大小都由相同的值控制,可以通过在 CacheStream 上调用 distributedBatchSize 方法来配置。这个值将默认为以状态传输配置的 chunkSize。不幸的是,这个值对内存用量与至少一次的性能造成权衡,并且您的鼠标可能会不同。
使用带有复制和分布式缓存的 迭代器
当节点是分布式流的所有请求片段的主或备份所有者时,Data Grid 会在本地执行 迭代 或 分割 器终端操作,这会优化性能,因为远程迭代更为密集型。
这种优化适用于复制和分布式缓存。但是,当使用 共享 并启用了 write-behind 的缓存存储时,Data Grid 会远程执行迭代。在这种情况下,执行迭代可以远程确保一致性。
10.12. 中间操作例外 复制链接链接已复制到粘贴板!
有些带有特殊例外的中间操作 会跳过、peek、排序 1和 不同的。https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#sorted--所有这些方法在流处理中有某种形式的攻击者,以保证正确性,它们记录如下。请注意,这些操作可能会导致严重的性能下降。
- skip
- artificial iterator 被认为是中间跳过操作。然后,结果会在本地引入,以便可以跳过适当的元素。
- 排序
- 警告:此操作需要在本地节点上拥有所有条目。artificial iterator 被认为是中间排序的操作。所有结果都在本地排序。可以计划有一个分布式排序,它返回批处理元素,但还没有实施。
- 不同的
- 警告:此操作需要在本地节点上的内存中具有全部或几乎所有条目。每个远程节点上执行不同的不同,然后一个人为 iterator 会返回这些不同值。最后,所有这些结果都有不同的操作。
其余中间操作会如预期一样完全分发。
10.13. 例子 复制链接链接已复制到粘贴板!
单词计数
单词数(如果过度使用)是映射/缩减示例。假设我们有一个键 → 发送的映射,存储在 Data Grid 节点上。key 是一个字符串,每个句子都是字符串,且必须在所有可用的句子范围内出现所有词语。此类分布式任务的实现可定义如下:
在这种情况下,执行上例中的单词计数非常简单。
但是,如果我们希望在示例中找到最频繁的单词,该怎么样?如果您需要第二个内容来考虑这种情况,您需要首先拥有所有词语计算并在本地可用。因此,我们实际上有几个选项。
我们可以在收集器上使用一个完成器,它会在收集所有结果后在用户线程上调用。上例中删除了一些冗余行。
不幸的是,最后一步只在单个线程中运行,如果我们有大量词语可能非常慢。可能是通过 Streams 并行化此操作的另一种方法。
我们在处理后在本地节点之前提到,因此我们可以实际对映射结果使用流。因此,我们可以在结果上使用并行流。
这样,在计算最频繁的元素时,您仍然可以在本地利用所有内核。
删除特定条目
分布式流也可以用作修改其所在的数据的方法。例如,您可能想要删除包含特定单词的缓存中的所有条目。
如果我们仔细注意了什么被序列化的内容,我们注意到,只有词语与操作一起被序列化到其他 nods,因为它被 lambda 捕获。但是,实际节省的是,缓存操作是在主所有者上执行,从而减少了从缓存中删除这些值所需的网络流量数量。lambda 不会捕获缓存,因为我们提供了一个特殊的 BiConsumer 方法覆盖,在每个节点上调用缓存到 BiConsumer
以这种方式记住每个命令的一个 事项 是,底层流不会获得锁定。缓存删除操作仍会在自然上获得锁定,但该值可能会从流的意义中有所变化。这意味着,在流读取后可能会更改该条目,但删除实际被删除。
我们专门添加了一个称为 LockedStream 的新变体。
其它示例
Streams API 是一个 JRE 工具,有大量使用它的示例。请记住,您的操作需要以某种方式进行序列化。
第 11 章 JCache (JSR-107) API 复制链接链接已复制到粘贴板!
Data Grid 提供 JCache 1.0 API 的实现(js -107 )。JCACHE 指定用于在内存中缓存临时 Java 对象的标准 Java API。缓存 java 对象有助于瓶颈源自使用比较昂贵的数据(例如 DB 或 Web 服务),或难以计算的数据。在内存中缓存这些对象有助于直接从内存检索数据,而不必进行昂贵的往返或重新计算。本文档论述了如何将 JCache 与规范的 Data Grid 实现一起使用,并解释了 API 的关键方面。
11.1. 创建嵌入式缓存 复制链接链接已复制到粘贴板!
先决条件
-
确保
cache-api位于您的 classpath 上。 在
pom.xml中添加以下依赖项:<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-jcache</artifactId> </dependency>
<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-jcache</artifactId> </dependency>Copy to Clipboard Copied! Toggle word wrap Toggle overflow
流程
- 创建使用默认 JCache API 配置的嵌入式缓存,如下所示:
11.1.1. 配置嵌入式缓存 复制链接链接已复制到粘贴板!
-
将自定义 Data Grid 配置的 URI 传递给 cache
Provider.getCacheManager (URI)调用,如下所示:
默认情况下,JCache API 指定数据应存储为 storeByValue,因此缓存外的对象的状态变异不会对存储在缓存中的对象产生影响。数据科学家目前使用序列化/合并来制作存储在缓存中的副本,并遵循 spec。因此,如果在 Data Grid 中使用默认 JCache 配置,则存储的数据必须是 marshallable。
或者,JCache 可以配置为通过参考(如 Data Grid 或 JDK Collections 工作)存储数据。要做到这一点,只需调用:
Cache<String, String> cache = cacheManager.createCache("namedCache",
new MutableConfiguration<String, String>().setStoreByValue(false));
Cache<String, String> cache = cacheManager.createCache("namedCache",
new MutableConfiguration<String, String>().setStoreByValue(false));
11.2. 创建远程缓存 复制链接链接已复制到粘贴板!
先决条件
-
确保
cache-api位于您的 classpath 上。 在
pom.xml中添加以下依赖项:<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-jcache-remote</artifactId> </dependency>
<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-jcache-remote</artifactId> </dependency>Copy to Clipboard Copied! Toggle word wrap Toggle overflow
流程
- 在远程 Data Grid 服务器上创建缓存,并使用默认的 JCache API 配置,如下所示:
11.2.1. 配置远程缓存 复制链接链接已复制到粘贴板!
热 Rod 配置文件包括 infinispan.client.hotrod.cache the 属性,可用于自定义远程缓存。
-
将
hotrod-client.properties文件的 URI 传递给 cacheProvider.getCacheManager (URI)调用,如下所示:
11.3. 存储和检索数据 复制链接链接已复制到粘贴板!
虽然 JCache API 没有扩展 java.util.Map not java.util.concurrent.ConcurrentMap,但它供应商一个键/值 API 来存储和检索数据:
与标准 java.util.Map,javax.cache.Cache 的比较程序提供了两个称为 put 和 getAndPut 的基本放置方法。前者返回 void,后者会返回之前与键关联的值。因此,JCache 中的 java.util.Map.put (K) 等同于 javax.cache.Cache.getAndPut (K)。
虽然 JCache API 只涵盖独立缓存,但它可以使用持久性存储进行插入,并被设计为集群或分发。javax.cache.Cache 提供了两种放置方法的原因是,标准 java.util.Map 会调用强制实现器来计算以前的值。当使用持久性存储或缓存被分发时,返回之前的值可能是一个昂贵的操作,通常在不使用返回值的情况下调用标准 java.util.Map.put (K)。因此,JCache 用户需要考虑返回值是否与它们相关,在这种情况下,它们需要调用 javax.cache.Cache.getAndPut (K),否则他们可以调用 java.util.Map.put (K, V),以避免返回之前值的可能昂贵的操作。
以下是 java.util.concurrent.ConcurrentMap 和 javax.cache.Cache API 提供的数据操作 API 的简要比较。
| 操作 | java.util.concurrent.ConcurrentMap<K, V> | javax.cache.Cache<K, V> |
|---|---|---|
| 存储且没有返回 | 不适用 |
|
| 存储和返回之前的值 |
|
|
| 如果不存在存储 |
|
|
| 检索 |
|
|
| 如果存在删除 |
|
|
| 删除并返回之前的值 |
|
|
| 删除条件 |
|
|
| 如果存在,替换 |
|
|
| 替换并返回之前的值 |
|
|
| 替换条件 |
|
|
比较两个 API,最好看到:在可能的情况下,JCache 避免返回之前的值以避免操作昂贵的网络或 IO 操作。这是 JCache API 设计中的覆盖原则。实际上,java.util.concurrent.ConcurrentMap 中存在一组操作,但没有出现在 javax.cache.Cache 中,因为它们在分布式缓存中进行计算的成本。唯一的例外是迭代缓存的内容:
| 操作 | java.util.concurrent.ConcurrentMap<K, V> | javax.cache.Cache<K, V> |
|---|---|---|
| 计算缓存的大小 |
| 不适用 |
| 返回缓存中的所有密钥 |
| 不适用 |
| 返回缓存中的所有值 |
| 不适用 |
| 返回缓存中的所有条目 |
| 不适用 |
| 迭代缓存 |
在 keySet、value 或 entrySet 上使用 |
|
11.5. 集群 JCache 实例 复制链接链接已复制到粘贴板!
Data Grid JCache 实施超出了规格,以便使用标准 API 进行集群缓存。假设配置为复制缓存的数据网格配置文件,如下所示:
infinispan.xml
您可以使用这个代码创建缓存集群:
第 12 章 多映射缓存 复制链接链接已复制到粘贴板!
MutimapCache 是一种 Data Grid Cache 类型,它将键映射到每个键可以包含多个值的值。
12.1. 安装和配置 复制链接链接已复制到粘贴板!
pom.xml
<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-multimap</artifactId> </dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-multimap</artifactId>
</dependency>
12.2. MultimapCache API 复制链接链接已复制到粘贴板!
MultimapCache API 公开几种方法与多映射缓存交互。在大多数情况下,这些方法是非阻塞的方法;如需更多信息,请参阅 限制。
CompletableFuture<Void> put (K key, V value)
将键值对放在多映射缓存中。
这个代码的输出如下:
Marie is a girl name Oihana is a girl name
Marie is a girl name
Oihana is a girl name
CompletableFuture<Collection<V>> get (K key)
异步返回与此多映射缓存中与键关联的值的视图集合(若有)。对检索到的集合的任何更改都不会更改此多映射缓存中的值。当此方法返回空集合时,这意味着未找到密钥。
CompletableFuture<Boolean> remove (K key)
异步从多映射缓存中删除与密钥关联的条目(如果存在)。
CompletableFuture<Boolean> remove (K key, V value)
异步从多映射缓存中删除键值对(如果存在的话)。
CompletableFuture<Void> remove (Predicate<? super V> p)
异步方法。删除与给定 predicate 匹配的每个值。
CompletableFuture<Boolean> containsKey (K key)
如果此多映射包含密钥,则返回 true 的异步。
CompletableFuture<Boolean> 包含Value (V 值)
如果此多映射在至少一个键中包含值,则返回 true 的异步。
CompletableFuture<Boolean> 包含Entry (K key, V value)
如果此多映射至少包含一个键值对,则返回 true。
CompletableFuture<Long> size()
异步返回多映射缓存中键值对数。它不会返回不同的键数。
布尔值 supportsDuplicates ()
如果多映射缓存支持重复,则返回 true 的异步。这意味着 multimap 的内容可以是 'a' → ['1', '1', '2']。现在,此方法总是返回 false,因为尚不支持重复。给定值的存在由 'equals' 和 'hashcode' 方法的合同决定。
12.3. 创建多映射缓存 复制链接链接已复制到粘贴板!
目前 MultimapCache 配置为常规缓存。这可以通过代码或 XML 配置来完成。请参阅如何在 [configure a cache] 部分配置常规缓存。
12.3.1. 嵌入式模式 复制链接链接已复制到粘贴板!
12.4. 限制 复制链接链接已复制到粘贴板!
几乎每个情况下,多映射缓存都将充当常规缓存,但当前版本中存在一些限制,如下所示:
12.4.1. 支持重复副本 复制链接链接已复制到粘贴板!
尚不支持重复。这意味着 multimap 不包含任何重复的键值对。每当调用放置方法时,如果键值对已存在,则不会添加此键值对。用于检查 Multimap 中是否存在键值对的方法,是 等号 和 散列码。
12.4.2. 驱除 复制链接链接已复制到粘贴板!
现在,驱除每个键可以正常工作,而不是每个键对。这意味着,每当键被驱除时,与键关联的所有值也会被驱除。
12.4.3. Transactions 复制链接链接已复制到粘贴板!
通过自动提交支持隐式事务,所有方法都不会被阻断。在大多数情况下,显式事务可以正常工作,而不会阻断。块的大小为,包含Entry 和 remove (Predicate<? super V> p)
第 13 章 Red Hat JBoss EAP 的数据仓库模块 复制链接链接已复制到粘贴板!
要在部署到 Red Hat JBoss EAP 的应用程序内使用 Data Grid,您应该安装以下数据中心模块:
- 允许您在 WAR 或 EAR 文件中在没有打包数据网格 JAR 文件的情况下部署应用程序。
- 允许您使用独立于 Red Hat JBoss EAP 捆绑的数据仓库版本。
Red Hat JBoss EAP (EAP)应用程序可以直接处理 infinispan 子系统,而无需单独安装 Data Grid 模块。自 EAP 版本 7.4 起,红帽提供了对此功能的支持。但是,您的部署要求 EAP 模块使用索引和查询等高级功能。
13.1. 安装 Data Grid 模块 复制链接链接已复制到粘贴板!
为 Red Hat JBoss EAP 下载并安装 Data Grid 模块。
先决条件
- JDK 8 或更高版本。
- 现有红帽 JBoss EAP 安装。
流程
- 登录到红帽客户门户。
- 从 Data Grid 软件下载模块的 ZIP 存档。
提取 ZIP 存档并将
模块内容复制到 Red Hat JBoss EAP 安装的模块目录中,以便您可以获得生成的结构:$EAP_HOME/modules/system/add-ons/rhdg/org/infinispan/rhdg-8.3
13.2. 将应用程序配置为使用 Data Grid 模块 复制链接链接已复制到粘贴板!
为 Red Hat JBoss EAP 安装 Data Grid 模块后,将应用程序配置为使用 Data Grid 功能。
流程
-
在项目
pom.xml文件中,将所需的 Data Grid 依赖项标记为 provided。 -
配置工件归档器,以生成适当的
MANIFEST.MF文件。
pom.xml
Data Grid 功能打包为一个模块 org.infinispan,您可以将其作为条目添加到应用程序清单中,如下所示:
MANIFEST.MF
Manifest-Version: 1.0 Dependencies: org.infinispan:rhdg-8.3 services
Manifest-Version: 1.0
Dependencies: org.infinispan:rhdg-8.3 services
AWS 依赖项
如果您需要 AWS 依赖项,如 S3_PING,请在应用程序的清单中添加以下模块:
Manifest-Version: 1.0 Dependencies: com.amazonaws.aws-java-sdk:rhdg-8.3 services
Manifest-Version: 1.0
Dependencies: com.amazonaws.aws-java-sdk:rhdg-8.3 services
通过 org.apache.catalina.Manager 接口将 HTTP 会话数据从外部化到 Data Grid Server 集群来实现高可用性。
14.1. 安装 Tomcat 会话客户端 复制链接链接已复制到粘贴板!
安装 Tomcat 会话客户端,将 Red Hat JBoss Web Server 应用程序的 HTTP 会话外部化到 Red Hat Data Grid。
流程
-
从 Data Grid Software Downloads 下载
redhat-datagrid-8.1.1-tomcat<$version>-session-client.zip存档。 - 将存档提取到您的文件系统中。
-
将提取的存档中的
lib/目录的内容复制到$CATALINA_HOME/lib中。
14.2. 配置会话管理器 复制链接链接已复制到粘贴板!
为会话管理器配置 HotRodManager 类,以定义 Tomcat 会话客户端如何连接到 Red Hat Data Grid Server,并将数据存储在远程缓存中。
先决条件
- 安装 Tomcat 会话客户端。
- 至少一个 Data Grid 服务器实例。
- 在 Data Grid 服务器上创建一个缓存,以用作存储 HTTP 会话数据的模板。
流程
-
打开
$CATALINA_HOME/conf/context.xml或/WEB-INF/context.xml进行编辑。 -
指定
org.wildfly.clustering.tomcat.hotrod.HotRodManager作为className属性的值。 -
指定要用作带有
configurationName属性的模板的缓存名称。 -
根据需要为
HotRodManager类定义任何其他配置属性。 设置 Hot Rod 客户端配置属性,而不设置
infinispan.client.hotrod.前缀。-
使用
server_list属性指定 Data Grid Server 节点列表。 -
使用
auth_username和auth_password属性指定 Data Grid 凭证。
-
使用
- 根据需要为 Tomcat 会话管理器指定通用属性。
-
保存并关闭
context.xml。
配置示例
验证
要验证 Tomcat 会话客户端是否在远程缓存中存储数据,请执行以下操作:
在任何浏览器中打开 Data Grid 控制台。
默认情况下,控制台位于
http://127.0.0.1:11222/console/。- 检查 Tomcat 会话客户端是否已为每个部署的应用程序创建了缓存。
14.2.1. 热随机管理器配置属性 复制链接链接已复制到粘贴板!
下表列出了并描述了 HotRodManager 类的配置属性:
| 属性 | 描述 |
|---|---|
|
|
指定 |
|
| 指定 Data Grid Server 上的远程缓存,用作存储 HTTP 会话数据的模板。 |
|
| 定义会话如何映射到缓存中的条目。
|
|
| 定义存储在缓存中的最大会话数。默认值为没有最大值(无限)。 |
第 15 章 自定义拦截器 复制链接链接已复制到粘贴板!
自定义拦截器在 Data Grid 中已弃用,并将在以后的版本中删除。
自定义拦截器是通过能够影响或响应对缓存的任何修改来扩展数据中心的方法。这种修改示例包括添加/删除/更新或事务提交。
15.1. 声明性添加自定义拦截器 复制链接链接已复制到粘贴板!
自定义拦截器可以针对每个命名的缓存添加。这是因为每个命名缓存都有自己的拦截器堆栈。以下 xml 片段描述了添加自定义拦截器的方法。
15.2. 以编程方式添加自定义拦截器 复制链接链接已复制到粘贴板!
为此,您需要获取对 AdvancedCache 的引用。这可以按如下方式完成:
CacheManager cm = getCacheManager();//magic
Cache aCache = cm.getCache("aName");
AdvancedCache advCache = aCache.getAdvancedCache();
CacheManager cm = getCacheManager();//magic
Cache aCache = cm.getCache("aName");
AdvancedCache advCache = aCache.getAdvancedCache();
然后,应使用 addInterceptor () 方法之一来添加实际拦截器。有关更多文档,请参阅 AdvancedCache javadoc。
15.3. 自定义拦截器设计 复制链接链接已复制到粘贴板!
在编写自定义拦截器时,您需要根据以下规则识别。
- 自定义拦截器必须声明一个公共的空构造器才能启用构造。
- 自定义拦截器将具有通过 XML 配置中使用的属性标签定义的任何属性的 setters。