参考指南
4. 文档结构
以下章节介绍了 Spring Data for Apache Geode 提供的核心功能:
-
使用 Spring 容器引导 Apache Geode 描述了用于配置、初始化和访问 Apache Geode 缓存(Caches)、区域(Regions)及相关分布式系统组件所提供的配置支持。
-
使用 Apache Geode API 介绍了 Apache Geode API 与 Spring 中各种数据访问功能之间的集成, 例如基于模板的数据访问、异常转换、事务管理和缓存。
-
使用 Apache Geode 序列化 描述了对 Apache Geode 管理对象的序列化和反序列化功能的增强。
-
POJO 映射 描述了使用 Spring Data 将存储在 Apache Geode 中的 POJO 进行持久化映射的方式。
-
用于 Apache Geode 的 Spring Data 仓库 描述了如何创建和使用 Spring Data 仓库,通过基本的 CRUD 操作和简单查询操作来访问存储在 Apache Geode 中的数据。
-
函数执行的注解支持 描述了如何使用注解创建和使用 Apache Geode 函数,以在数据所在的位置执行分布式计算。
-
连续查询(CQ) 描述了如何使用 Apache Geode 的连续查询(CQ)功能, 根据通过 Apache Geode 的 OQL(对象查询语言)定义并注册的兴趣点来处理事件流。
-
在 Apache Geode 中引导 Spring ApplicationContext 描述了如何使用
ApplicationContext配置并引导运行在 Apache Geode 服务器中的 SpringGfsh。 -
示例应用程序 描述了随发行版提供的示例,用于说明 Apache Geode 的 Spring Data 中可用的各种功能。
5. 使用 Spring 容器引导 Apache Geode
Spring Data for Apache Geode 利用 Spring IoC 容器,提供了对 Apache Geode 内存数据网格(IMDG)的完整配置与初始化。该框架包含多个类,用于简化 Apache Geode 组件的配置,包括:缓存(Caches)、区域(Regions)、索引(Indexes)、磁盘存储(DiskStores)、函数(Functions)、WAN 网关(WAN Gateways)、持久化备份,以及其他多种分布式系统组件,从而以最小的工作量支持各种应用程序使用场景。
| 本节假定您已具备 Apache Geode 的基本知识。如需了解更多信息,请参阅 Apache Geode 的产品文档。 |
5.1. 使用 Spring 而非 Apache Geode 的优势cache.xml
Spring Data for Apache Geode 的 XML 命名空间支持对 Apache Geode 内存数据网格(IMDG)进行完整配置。 XML 命名空间是在 Spring 上下文中配置 Apache Geode 的两种方式之一,用于在 Spring 容器内正确管理 Apache Geode 的生命周期。另一种在 Spring 上下文中配置 Apache Geode 的方式是使用基于注解的配置。
尽管出于兼容旧版的原因,Apache Geode 原生的 cache.xml 仍被支持,但使用 XML 配置的 Apache Geode 应用程序开发者被鼓励将所有配置都放在 Spring XML 中,以充分利用 Spring 提供的诸多强大功能,例如模块化的 XML 配置、属性占位符与覆盖机制、SpEL(Spring 表达式语言)以及环境配置文件。
在 XML 命名空间的背后,Spring Data for Apache Geode 大量使用了 Spring 的 FactoryBean 模式,以简化 Apache Geode 组件的创建、配置和初始化。
Apache Geode 提供了多个回调接口,例如 CacheListener、CacheLoader 和 CacheWriter,
允许开发者添加自定义事件处理器。利用 Spring 的 IoC 容器,您可以将这些回调配置为普通的 Spring Bean,
并将其注入到 Apache Geode 组件中。这相比原生 cache.xml 是一个重大改进,因为后者提供的配置选项相对有限,
并且要求回调实现 Apache Geode 的 Declarable 接口(请参阅 连接 Declarable 组件,了解如何在 Spring 容器中仍可使用 Declarables)。
此外,诸如 Spring Tool Suite(STS)等集成开发环境(IDE)为 Spring XML 命名空间提供了出色的支持,包括代码自动补全、弹出式注解以及实时验证。
5.2. 使用核心命名空间
为了简化配置,Spring Data for Apache Geode 提供了一个专用的 XML 命名空间,用于配置核心的 Apache Geode 组件。虽然也可以直接使用 Spring 的标准 <bean> 定义来配置 Bean,
但所有 Bean 属性都已通过该 XML 命名空间暴露出来,因此使用原始的 Bean 定义几乎没有优势。
| 有关 Spring 中基于 XML Schema 的配置的更多信息,请参阅 Spring Framework 参考文档中的附录。 |
| Spring Data Repository 支持使用独立的 XML 命名空间。有关如何配置 Spring Data for Apache Geode Repositories 的更多信息,请参阅Spring Data for Apache Geode Repositories。 |
要使用 Spring Data for Apache Geode 的 XML 命名空间,请在您的 Spring XML 配置元数据中声明它,如下例所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:gfe="https://www.springframework.org/schema/geode" (1)(2)
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/geode https://www.springframework.org/schema/geode/spring-geode.xsd (3)
">
<bean id ... >
<gfe:cache ...> (4)
</beans>
| 1 | 用于 Apache Geode 的 Spring Data XML 命名空间前缀。可以使用任意名称,但在本参考文档中始终使用 gfe。 |
| 2 | XML 命名空间前缀被映射到该 URI。 |
| 3 | XML 命名空间 URI 的位置。请注意,尽管该位置指向一个外部地址(该地址确实存在且有效),但 Spring 会从本地解析该 schema,因为它已包含在 Spring Data for Apache Geode 库中。 |
| 4 | 使用带有 gfe 前缀的 XML 命名空间的声明示例。 |
|
您可以将默认命名空间从
|
5.3. 使用数据访问命名空间
除了核心 XML 命名空间(gfe)之外,Spring Data for Apache Geode 还提供了一个数据访问 XML 命名空间(gfe-data),
主要用于简化 Apache Geode 客户端应用程序的开发。该命名空间
目前支持 Apache Geode Repository 和函数
执行,同时还提供了一个 <datasource> 标签,用于便捷地连接到
Apache Geode 集群。
5.3.1. 连接 Apache Geode 的简便方法
对于许多应用程序而言,使用默认值与 Apache Geode 数据网格建立基本连接就已足够。
Spring Data for Apache Geode 的 <datasource> 标签提供了一种访问数据的简便方式。该数据源会创建一个 ClientCache
和连接 Pool。此外,它还会向集群服务器查询所有已存在的根 Region,并为每个根 Region 创建一个(空的)客户端 Region 代理。
<gfe-data:datasource>
<locator host="remotehost" port="1234"/>
</gfe-data:datasource>
<datasource> 标签在语法上与 <gfe:pool> 类似。它可以配置一个或多个嵌套的 locator 或 server 元素,以连接到现有的数据网格。此外,所有可用于配置 Pool 的属性也都受支持。此配置会自动为连接到 Locator 的集群成员上定义的每个 Region 创建客户端 Region Bean,从而可以通过 Spring Data 映射注解(GemfireTemplate)无缝引用这些 Region,并自动装配到应用程序类中。
当然,你可以显式地配置客户端 Region。例如,如果你想将数据缓存在本地内存中,如下例所示:
<gfe-data:datasource>
<locator host="remotehost" port="1234"/>
</gfe-data:datasource>
<gfe:client-region id="Example" shortcut="CACHING_PROXY"/>
5.4. 配置缓存
要使用 Apache Geode,您需要创建一个新的缓存,或者连接到一个现有的缓存。在当前版本的 Apache Geode 中,每个虚拟机(更严格地说,是每个 ClassLoader)只能打开一个缓存。在大多数情况下,缓存应仅创建一次。
本节介绍对等(peer)Cache成员的创建与配置,适用于点对点(P2P)拓扑结构和缓存服务器。在独立应用程序和集成测试中,也可以使用Cache成员。
然而,在典型的生产系统中,大多数应用程序进程充当缓存客户端,而是创建ClientCache实例。
相关内容请参见配置 Apache Geode ClientCache 和 客户端 Region 章节。 |
可以通过以下简单的声明创建一个具有默认配置的对等Cache:
<gfe:cache/>
在 Spring 容器初始化期间,任何包含此缓存定义的 ApplicationContext 都会注册一个
CacheFactoryBean,该工厂 Bean 会创建一个名为 gemfireCache 的 Spring Bean,
该 Bean 引用一个 Apache Geode Cache 实例。
此 Bean 要么引用一个已存在的 Cache,要么在尚不存在的情况下创建一个新的 Cache。
由于未指定额外的属性,新创建的 6 将应用默认的缓存配置。
所有依赖于 Cache 的 Apache Geode Spring Data 组件都遵循此命名约定,因此您无需显式声明 Cache 依赖项。如果您愿意,也可以通过各种 SDG XML 命名空间元素提供的 cache-ref 属性来显式指定该依赖。此外,您还可以使用 id 属性覆盖缓存的 bean 名称,如下所示:
<gfe:cache id="myCache"/>
可以使用 Spring 对 Apache Geode 的 Cache 进行完整配置。不过,Apache Geode 原生的 XML 配置文件 cache.xml 也同样受支持。在需要以原生方式配置 Apache Geode 缓存的情况下,您可以使用 cache-xml-location 属性来指定 Apache Geode XML 配置文件的位置,如下所示:
<gfe:cache id="cacheConfiguredWithNativeCacheXml" cache-xml-location="classpath:cache.xml"/>
在此示例中,如果需要创建缓存,它将使用位于类路径根目录下的名为 cache.xml 的文件来配置该缓存。
配置利用 Spring 的 Resource
抽象来定位文件。Resource 抽象允许根据运行时环境或资源位置中指定的前缀(如果有)使用各种搜索模式。 |
除了引用外部 XML 配置文件外,您还可以使用 Spring 的 https://geode.apache.org/docs/guide/19/reference/topics/gemfire_properties.html 支持功能中的任意特性来指定 Apache Geode 系统
属性。
例如,您可以使用 properties 命名空间中定义的 util 元素来直接定义 Properties,
或从属性文件中加载属性,如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:gfe="https://www.springframework.org/schema/geode"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/geode https://www.springframework.org/schema/geode/spring-geode.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd
">
<util:properties id="gemfireProperties" location="file:/path/to/gemfire.properties"/>
<gfe:cache properties-ref="gemfireProperties"/>
</beans>
建议使用属性文件将特定于环境的设置从应用程序配置中外部化。
| 缓存设置仅在需要创建新缓存时生效。如果虚拟机中已存在一个打开的缓存, 这些设置将被忽略。 |
5.4.1. 高级缓存配置
对于高级缓存配置,cache 元素提供了多个配置选项,这些选项以属性或子元素的形式公开,如下列所示:
(1)
<gfe:cache
cache-xml-location=".."
properties-ref=".."
close="false"
copy-on-read="true"
critical-heap-percentage="90"
eviction-heap-percentage="70"
enable-auto-reconnect="false" (2)
lock-lease="120"
lock-timeout="60"
message-sync-interval="1"
pdx-serializer-ref="myPdxSerializer"
pdx-persistent="true"
pdx-disk-store="diskStore"
pdx-read-serialized="false"
pdx-ignore-unread-fields="true"
search-timeout="300"
use-bean-factory-locator="true" (3)
use-cluster-configuration="false" (4)
>
<gfe:transaction-listener ref="myTransactionListener"/> (5)
<gfe:transaction-writer> (6)
<bean class="org.example.app.gemfire.transaction.TransactionWriter"/>
</gfe:transaction-writer>
<gfe:gateway-conflict-resolver ref="myGatewayConflictResolver"/> (7)
<gfe:jndi-binding jndi-name="myDataSource" type="ManagedDataSource"/> (8)
</gfe:cache>
| 1 | 属性支持各种缓存选项。有关本示例中所示内容的更多信息,
请参阅 Apache Geode 产品文档。
close 属性用于确定在 Spring 应用上下文关闭时是否应同时关闭缓存。
默认值为 true。然而,对于多个应用上下文共享使用同一缓存的场景
(在 Web 应用中较为常见),应将此值设为 false。 |
| 2 | 将 enable-auto-reconnect 属性设置为 true(默认值为 false)可使断开连接的 Apache Geode 成员自动重新连接并重新加入 Apache Geode 集群。
有关更多详细信息,请参阅 Apache Geode 产品文档。 |
| 3 | 将 use-bean-factory-locator 属性设置为 true(默认值为 false)仅在同时使用 Spring(XML)配置元数据和 Apache Geode 的 cache.xml 来配置 Apache Geode 缓存节点(无论是客户端还是对等节点)时才生效。此选项允许在 CacheLoader 中声明的 Apache Geode 组件(例如 cache.xml)自动装配 Spring 应用上下文中定义的 Bean(例如 DataSource)。该选项通常与 cache-xml-location 一起使用。 |
| 4 | 将 use-cluster-configuration 属性设置为 true(默认值为 false)可使 Apache Geode 成员从 Locator 获取通用的、共享的基于集群的配置。
更多详细信息,请参阅 Apache Geode 产品文档。 |
| 5 | 一个使用 Bean 引用的 TransactionListener 回调声明示例。所引用的 Bean 必须实现
TransactionListener。
可以实现 TransactionListener 来处理事务相关事件(例如 afterCommit 和 afterRollback)。 |
| 6 | 使用内部 bean 声明的 TransactionWriter 回调声明示例。该 bean 必须实现
TransactionWriter。
TransactionWriter 是一个可以否决事务的回调。 |
| 7 | 使用 Bean 引用声明 GatewayConflictResolver 回调的示例。所引用的 Bean
必须实现 https://geode.apache.org/releases/latest/javadoc/org/apache/geode/cache/util/GatewayConflictResolver.html
[GatewayConflictResolver]。
GatewayConflictResolver 是一个 Cache 级别的插件,用于决定如何处理
源自其他系统并通过 WAN 网关到达的事件。
它提供了一个分布式 Region 创建服务。 |
| 8 | 声明一个 JNDI 绑定,以将外部 DataSource 注册到 Apache Geode 事务中。 |
启用 PDX 序列化
前面的示例包含多个与 Apache Geode 的增强型序列化框架 PDX 相关的属性。
尽管本参考指南不涉及对 PDX 的完整讨论,但需要注意的是,通过注册一个 PdxSerializer 即可启用 PDX,
而该 pdx-serializer 是通过设置 2 属性来指定的。
Apache Geode 提供了一个使用 Java 反射机制的实现类(org.apache.geode.pdx.ReflectionBasedAutoSerializer)。然而,开发人员通常会提供自己的实现。该属性的值仅是对一个实现了 PdxSerializer 接口的 Spring Bean 的引用。
有关序列化支持的更多信息,请参见使用 Apache Geode 序列化。
启用自动重连
在将 <gfe:cache enable-auto-reconnect="[true|false*]> 属性设置为 true 时,您应当谨慎。
通常,仅当使用 Spring Data for Apache Geode 的 XML 命名空间来配置并引导一个新加入集群的、非应用程序类型的 Apache Geode 服务器时,才应启用“自动重连”(auto-reconnect)功能。换句话说,当使用 Spring Data for Apache Geode 开发和构建一个 Apache Geode 应用程序,并且该应用程序恰好也是 Apache Geode 集群中的一个对等 Cache 成员时,不应启用“自动重连”功能。
此限制的主要原因是,大多数 Apache Geode 应用程序使用对 Apache Geode Cache 或 Region 的引用来执行数据访问操作。这些引用由 Spring 容器“注入”到应用程序组件(例如 Repository)中,供应用程序使用。当某个对等成员被强制从集群其余部分断开连接时(通常是因为该对等成员变得无响应,或者网络分区导致一个或多个对等成员被隔离成一个规模过小、无法作为独立分布式系统运行的组),该对等成员会关闭,所有 Apache Geode 组件引用(包括缓存、Region 等)都将失效。
本质上,当前每个对等成员中的强制断开连接处理逻辑会从底层开始逐步拆除整个系统。 JGroups 堆栈被关闭,分布式系统进入关闭状态,最后缓存也被关闭。 实际上,所有内存引用都会变得无效并丢失。
在与分布式系统断开连接后,一个对等成员会进入“重新连接”(reconnecting)状态,并定期尝试重新加入该分布式系统。如果该对等成员成功重新连接,它将从现有成员重建其对分布式系统的“视图”(view),并获得一个新的分布式系统 ID。此外,所有缓存、Region 以及其他 Apache Geode 组件都将被重新构建。因此,所有先前由 Spring 容器注入到应用程序中的旧引用现在都已失效,不再有效。
Apache Geode 无法保证(即使使用 Apache Geode 的公共 Java API)应用程序缓存、Region 或其他组件引用会在重连操作中自动刷新。因此,Apache Geode 应用程序必须自行负责刷新其引用。
不幸的是,目前无法收到断开连接事件的通知,也无法随后收到重新连接事件的通知。
如果可以做到这一点,你就能明确知道何时调用 ConfigurableApplicationContext.refresh(),
前提是应用程序确实适用此操作。正因如此,Apache Geode 的这一“特性”不建议用于对等(peer)Cache 应用程序。
有关“自动重连”(auto-reconnect)的更多信息,请参阅 Apache Geode 的产品文档。
使用基于集群的配置
Apache Geode 的集群配置服务是一种便捷的方式,使得任何加入集群的对等成员都能通过定位器(Locator)维护的共享、持久化配置,获得集群的“一致视图”。 使用基于集群的配置可确保该对等成员在加入时,其配置与 Apache Geode 分布式系统兼容。
Spring Data for Apache Geode 的这一特性(将 use-cluster-configuration 属性设置为 true)的工作方式与 cache-xml-location 属性类似,不同之处在于 Apache Geode 配置元数据的来源是通过 Locator 从网络获取的,而不是来自本地文件系统中的原生 cache.xml 文件。
所有 Apache Geode 原生配置元数据(无论是来自 cache.xml 还是来自集群配置服务(Cluster Configuration Service))
都会在任何 Spring(XML)配置元数据之前被应用。因此,Spring 的配置用于“增强”
原生的 Apache Geode 配置元数据,并且很可能针对具体的应用程序。
同样,要启用此功能,请在 Spring XML 配置中指定以下内容:
<gfe:cache use-cluster-configuration="true"/>
虽然某些 Apache Geode 工具(例如 Gfsh)在执行类似模式的更改时会“记录”其操作(例如,gfsh>create region --name=Example --type=PARTITION),但 Spring Data for Apache Geode 的配置元数据并不会被记录。直接使用 Apache Geode 的公共 Java API 时也是如此,它同样不会被记录。 |
有关 Apache Geode 的集群配置服务的更多信息,请参阅产品文档。
5.4.2. 配置 Apache Geode CacheServer
Spring Data for Apache Geode 提供了专门的支持来配置 CacheServer, 允许通过 Spring 容器进行完整的配置,如下例所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:gfe="https://www.springframework.org/schema/geode"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
https://www.springframework.org/schema/geode https://www.springframework.org/schema/geode/spring-geode.xsd
">
<gfe:cache/>
<!-- Example depicting serveral Apache Geode CacheServer configuration options -->
<gfe:cache-server id="advanced-config" auto-startup="true"
bind-address="localhost" host-name-for-clients="localhost" port="${gemfire.cache.server.port}"
load-poll-interval="2000" max-connections="22" max-message-count="1000" max-threads="16"
max-time-between-pings="30000" groups="test-server">
<gfe:subscription-config eviction-type="ENTRY" capacity="1000" disk-store="file://${java.io.tmpdir}"/>
</gfe:cache-server>
<context:property-placeholder location="classpath:cache-server.properties"/>
</beans>
上述配置展示了 cache-server 元素及其众多可用选项。
该配置没有硬编码端口号,而是使用了 Spring 的context命名空间来声明一个 property-placeholder。属性占位符(property placeholder)会读取一个或多个属性文件,然后在运行时将属性占位符替换为实际的值。这样,管理员就可以在不修改主应用程序配置的情况下更改这些值。Spring 还提供了SpEL和环境抽象(environment abstraction),以支持将特定于环境的属性从主代码库中抽离出来,从而简化在多台机器上的部署。 |
为了避免初始化问题,由 Spring Data for Apache Geode 启动的 CacheServer 会在 Spring 容器完全初始化之后才启动。这样做可以确保通过声明方式定义的 Region、监听器、写入器或实例化器在服务器开始接受连接之前就已完成初始化并注册。在以编程方式配置这些组件时,请牢记这一点,因为服务器可能会在您的组件初始化完成之前就已启动,从而导致刚连接的客户端无法立即看到这些组件。 |
5.4.3. 配置 Apache Geode ClientCache
除了定义 Apache Geode 对等 Cache,
Spring Data for Apache Geode 还支持在 Spring 容器中定义 Apache Geode ClientCache。
Cache 的 ClientCache 定义在配置和使用上与 Apache Geode 对等类似,
并由 org.springframework.data.gemfire.client.ClientCacheFactoryBean 提供支持。
使用默认配置的 Apache Geode 缓存客户端的最简单定义如下:
<beans>
<gfe:client-cache/>
</beans>
client-cache 支持许多与 Cache 元素相同的选项。然而,与功能完整的对等 Cache 成员不同,缓存客户端通过一个 Pool 连接到远程缓存服务器。默认情况下,会创建一个 Pool 以连接到运行在 localhost 上并监听端口 40404 的服务器。除非区域(Region)被配置为使用特定的 Pool,否则所有客户端区域(Region)都会使用这个默认的 Pool。
可以使用 pool 元素定义连接池。此客户端连接池可用于通过一个或多个定位器(Locator),为单个实体或整个缓存直接配置到服务器的连接。
例如,要自定义 client-cache 所使用的默认连接池(Pool),开发者需要定义一个 Pool 并将其关联到缓存定义中,如下例所示:
<beans>
<gfe:client-cache id="myCache" pool-name="myPool"/>
<gfe:pool id="myPool" subscription-enabled="true">
<gfe:locator host="${gemfire.locator.host}" port="${gemfire.locator.port}"/>
</gfe:pool>
</beans>
The <client-cache> 元素也具有一个 ready-for-events 属性。如果将该属性设置为 true,则客户端缓存初始化将包括对 ClientCache.readyForEvents() 的调用。
客户端区域 更详细地介绍了客户端配置。
Apache Geode 的默认连接池和 Spring Data for Apache Geode 连接池定义
如果 Apache Geode 的 ClientCache 仅为本地使用,则不需要定义 Pool。例如,您可以定义如下内容:
<gfe:client-cache/>
<gfe:client-region id="Example" shortcut="LOCAL"/>
在这种情况下,“示例”区域为 LOCAL,客户端与服务器之间不分布任何数据。因此,不需要连接池。对于由 Apache Geode 定义的任意仅客户端的本地区域(如 ClientRegionShortcut 所示,所有 LOCAL_* 快捷方式),均适用此规则。
然而,如果客户端 Region 是服务器端 Region 的(缓存)代理,则需要一个 Pool。在这种情况下,有多种方式可以定义和使用 Pool。
当定义了 ClientCache、Pool 和基于代理的 Region,但未显式指定其标识时,Spring Data for Apache Geode 会自动解析这些引用,如下例所示:
<gfe:client-cache/>
<gfe:pool>
<gfe:locator host="${geode.locator.host}" port="${geode.locator.port}"/>
</gfe:pool>
<gfe:client-region id="Example" shortcut="PROXY"/>
在前面的示例中,ClientCache 被标识为 gemfireCache,连接池(Pool)被标识为 gemfirePool,
而客户端 Region 的名称为“Example”。然而,ClientCache 会基于 DEFAULT 初始化 Apache Geode 的 gemfirePool 连接池,
并且客户端 Region 在客户端与服务器之间分发数据时会使用 gemfirePool。
基本上,Spring Data for Apache Geode 会将上述配置解析为以下内容:
<gfe:client-cache id="gemfireCache" pool-name="gemfirePool"/>
<gfe:pool id="gemfirePool">
<gfe:locator host="${geode.locator.host}" port="${geode.locator.port}"/>
</gfe:pool>
<gfe:client-region id="Example" cache-ref="gemfireCache" pool-name="gemfirePool" shortcut="PROXY"/>
Apache Geode 仍然会创建一个名为 DEFAULT 的 Pool。Spring Data for Apache Geode 会使该 DEFAULT Pool 从 gemfirePool 进行初始化。这种做法在定义了多个 Pool,并且客户端 Region 使用各自独立的 Pool,或者根本没有声明 Pool 的情况下非常有用。
请考虑以下内容:
<gfe:client-cache pool-name="locatorPool"/>
<gfe:pool id="locatorPool">
<gfe:locator host="${geode.locator.host}" port="${geode.locator.port}"/>
</gfe:pool>
<gfe:pool id="serverPool">
<gfe:server host="${geode.server.host}" port="${geode.server.port}"/>
</gfe:pool>
<gfe:client-region id="Example" pool-name="serverPool" shortcut="PROXY"/>
<gfe:client-region id="AnotherExample" shortcut="CACHING_PROXY"/>
<gfe:client-region id="YetAnotherExample" shortcut="LOCAL"/>
在此配置中,Apache Geode 的 client-cache DEFAULT 连接池会根据 locatorPool 属性所指定的 pool-name 进行初始化。
由于两个连接池(gemfirePool 和 locatorPool)都已被显式命名,因此不会创建由 Spring Data for Apache Geode 定义的 serverPool。
“Example”区域显式引用并独占使用serverPool。AnotherExample区域则使用Apache Geode的DEFAULT连接池,该连接池同样是根据客户端缓存bean定义中的locatorPool属性,由pool-name配置而来。
最后,YetAnotherExample 区域不使用池(Pool),因为它是 LOCAL 类型的。
AnotherExample 区域会首先查找名为 gemfirePool 的 Pool bean,但这需要定义一个匿名的 Pool bean(即 <gfe:pool/>)或一个显式命名为 gemfirePool 的 Pool bean(例如,<gfe:pool id="gemfirePool"/>)。 |
如果我们把 locatorPool 的名称改为 gemfirePool,或者将 Pool bean 定义设为匿名,其效果将与前面的配置相同。 |
5.5. 配置区域
Region 是从缓存中存储和检索数据所必需的。org.apache.geode.cache.Region 是一个扩展了 java.util.Map 的接口,使用熟悉的键值语义来实现基本的数据访问。Region 接口被注入到需要它的应用程序类中,从而将实际的 Region 类型与编程模型解耦。通常,每个 Region 与一个领域对象相关联,类似于关系型数据库中的表。
Apache Geode 实现了以下类型的 Region:
-
REPLICATE - 数据会在集群中所有定义了该 Region 的缓存成员之间进行复制。这种方式提供了极高的读取性能,但写入操作由于需要执行复制而耗时更长。
-
分区(PARTITION) - 数据在定义该 Region 的集群中的多个缓存成员之间划分为多个桶(分片)。这种方式提供了高读写性能,适用于单个节点无法容纳的大型数据集。
-
LOCAL - 数据仅存在于本地节点上。
-
客户端(Client) - 从技术上讲,客户端 Region 是一种 LOCAL(本地)Region,它充当集群中缓存服务器上托管的 REPLICATE(复制)或 PARTITION(分区)Region 的代理(PROXY)。它可以保存在本地创建或获取的数据,也可以为空。 本地的更新会同步到缓存服务器。此外,客户端 Region 还可以订阅事件,以便与来自远程进程对同一服务器 Region 所做的更改保持最新(同步)。
有关各种 Region 类型及其功能以及配置选项的更多信息,请参阅 Apache Geode 关于 Region 类型 的文档。
5.5.1. 使用外部配置的 Region
要引用已在 Apache Geode 原生 cache.xml 文件中配置好的 Region,请使用 lookup-region 元素。
只需通过 name 属性声明目标 Region 的名称即可。例如,若要为一个名为 ordersRegion 的现有 Region 声明一个标识为 Orders 的 bean 定义,您可以使用以下 bean 定义:
<gfe:lookup-region id="ordersRegion" name="Orders"/>
如果未指定 name,则 bean 的 id 将用作 Region 的名称。上述示例将变为:
<!-- lookup for a Region called 'Orders' -->
<gfe:lookup-region id="Orders"/>
| 如果 Region 不存在,将抛出一个初始化异常。要配置新的 Regions,请继续阅读下面的相应章节。 |
在前面的示例中,由于没有显式定义缓存名称,因此使用了默认的命名约定(gemfireCache)。或者,也可以通过 cache-ref 属性引用缓存 bean:
<gfe:cache id="myCache"/>
<gfe:lookup-region id="ordersRegion" name="Orders" cache-ref="myCache"/>
lookup-region 允许您检索已存在且预先配置好的 Region,而无需暴露 Region 的语义或设置基础设施。
5.5.2. 自动区域查找
auto-region-lookup 允许您在 cache.xml 元素上使用 ApplicationContext 属性时,将 Apache Geode 原生 cache-xml-location 文件中定义的所有 Region 导入到 Spring <gfe:cache> 中。
例如,考虑以下 cache.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns="https://geode.apache.org/schema/cache"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://geode.apache.org/schema/cache https://geode.apache.org/schema/cache/cache-1.0.xsd"
version="1.0">
<region name="Parent" refid="REPLICATE">
<region name="Child" refid="REPLICATE"/>
</region>
</cache>
您可以按如下方式导入前面的 cache.xml 文件:
<gfe:cache cache-xml-location="cache.xml"/>
然后,您可以使用 <gfe:lookup-region> 元素(例如,<gfe:lookup-region id="Parent"/>)在 Spring 容器中将特定的 Region 引用为 bean,或者也可以选择通过以下方式导入 cache.xml 中定义的所有 Region:
<gfe:auto-region-lookup/>
Spring Data for Apache Geode 会自动为 cache.xml 中定义的所有 Apache Geode Region 创建 Bean,前提是这些 Region 尚未通过显式的 <gfe:lookup-region> Bean 声明被明确添加到 Spring 容器中。
需要注意的是,Apache Geode 的 Spring Data 使用了一个 Spring
BeanPostProcessor
在缓存创建并初始化之后对其进行后处理,以确定 Apache Geode 中定义的 Region,并将其作为 Bean 添加到 Spring ApplicationContext 中。
您可以像注入 Spring ApplicationContext 中定义的任何其他 Bean 一样注入这些“自动查找”的 Region,
但有一个例外:您可能需要定义一个与 ‘gemfireCache’ Bean 的 depends-on 关联,如下所示:
package example;
import ...
@Repository("appDao")
@DependsOn("gemfireCache")
public class ApplicationDao extends DaoSupport {
@Resource(name = "Parent")
private Region<?, ?> parent;
@Resource(name = "/Parent/Child")
private Region<?, ?> child;
...
}
前面的示例仅在您使用 Spring 的 component-scan 功能时适用。
如果你使用 Spring XML 配置来声明你的组件,那么你需要执行以下操作:
<bean class="example.ApplicationDao" depends-on="gemfireCache"/>
这样做可确保在使用 cache.xml 元素时,Apache Geode 缓存以及在 <gfe:auto-region-lookup> 中定义的所有 Region 都会在任何具有自动装配引用的组件之前被创建。
5.5.3. 配置区域
Spring Data for Apache Geode 通过以下元素为配置任何类型的 Region 提供全面支持:
-
本地区域:
<local-region> -
分区区域:
<partitioned-region> -
REPLICATE 区域:
<replicated-region> -
客户端区域:
<client-region>
有关区域类型的全面说明,请参阅 Apache Geode 文档。
公共区域属性
下表列出了所有 Region 类型可用的属性:
| 姓名 | 值 | 描述 |
|---|---|---|
cache-ref |
Apache Geode 缓存 Bean 引用 |
定义 Apache Geode 缓存的 Bean 名称(默认为 'gemfireCache')。 |
cloning-enabled |
boolean(默认值: |
当为 |
关闭 |
boolean(默认值: |
确定在关闭时是否应关闭该区域。 |
concurrency-checks-enabled |
boolean(默认值: |
确定成员是否执行检查,以对分布式区域的并发或乱序更新提供一致的处理。 |
data-policy |
参见 Apache Geode 的数据策略。 |
区域的数据策略。请注意,并非所有区域类型都支持全部的数据策略。 |
销毁 |
boolean(默认值: |
确定该区域是否应在关闭时被销毁。 |
disk-store-ref |
已配置的磁盘存储的名称。 |
对通过 |
disk-synchronous |
boolean(默认值: |
确定磁盘存储写入是否为同步操作。 |
id |
任意有效的 Bean 名称。 |
如果未指定 |
ignore-if-exists |
boolean(默认值: |
如果缓存中已存在该区域,则忽略此 Bean 定义,转而执行查找操作。 |
ignore-jta |
boolean(默认值: |
确定此 Region 是否参与 JTA(Java Transaction API)事务。 |
index-update-type |
|
确定在创建条目时索引是同步更新还是异步更新。 |
initial-capacity |
整数(默认值:16) |
区域条目数量的初始内存分配。 |
key-constraint |
任何有效的、完全限定的 Java 类名。 |
预期的键类型。 |
load-factor |
float(默认值:.75) |
在底层用于存储区域条目的 |
姓名 |
任何有效的区域名称。 |
区域的名称。如果未指定,则采用 |
持久的 |
*布尔值(默认: |
确定该区域是否将条目持久化到本地磁盘(磁盘存储)。 |
快捷方式 |
参见 https://geode.apache.org/releases/latest/javadoc/org/apache/geode/cache/RegionShortcut.html |
此区域的 |
统计信息 |
boolean(默认值: |
确定该区域是否报告统计信息。 |
模板 |
区域模板的名称。 |
对通过某个 |
value-constraint |
任何有效的、完全限定的 Java 类名。 |
期望的值类型。 |
CacheListener实例
CacheListener 个实例已注册到 Region,用于处理 Region 事件,例如条目创建、更新、销毁等。
CacheListener 可以是任何实现了
CacheListener 接口的 Bean。
Region 可以有多个监听器,它们通过嵌套在包含的
*-region 元素中的 cache-listener 元素进行声明。
以下示例声明了两个 CacheListener’s。第一个引用了一个已命名的顶层 Spring Bean,
第二个是一个匿名内部 Bean 定义。
<bean id="myListener" class="org.example.app.geode.cache.SimpleCacheListener"/>
<gfe:replicated-region id="regionWithListeners">
<gfe:cache-listener>
<!-- nested CacheListener bean reference -->
<ref bean="myListener"/>
<!-- nested CacheListener bean definition -->
<bean class="org.example.app.geode.cache.AnotherSimpleCacheListener"/>
</gfe:cache-listener>
</gfe:replicated-region>
以下示例使用了带有 cache-listener 属性的 ref 元素的另一种形式。
这样在定义单个 CacheListener 时,可使配置更加简洁。
注意:XML 命名空间只允许使用一个 cache-listener 元素,因此必须使用前面示例中所示的样式或以下示例中的样式。
<beans>
<gfe:replicated-region id="exampleReplicateRegionWithCacheListener">
<gfe:cache-listener ref="myListener"/>
</gfe:replicated-region>
<bean id="myListener" class="example.CacheListener"/>
</beans>
在 ref 元素中同时使用 cache-listener 和嵌套声明是非法的。
这两种选项互斥,在同一个元素中同时使用会导致异常。 |
|
Bean 引用约定
|
缓存加载器和缓存写入器
与 cache-listener 类似,XML 命名空间提供了 cache-loader 和 cache-writer 元素,用于为 Region 注册这些 Apache Geode 组件。
当缓存未命中时,会调用 CacheLoader,以便从外部数据源(例如数据库)加载条目。
在创建或更新条目之前,会调用 CacheWriter,以便将该条目同步到外部数据源。
主要区别在于,Apache Geode 在每个 Region 中最多支持一个 CacheLoader 和一个 CacheWriter 实例。
不过,这两种声明方式均可使用。
以下示例声明了一个同时包含 CacheLoader 和 CacheWriter 的 Region:
<beans>
<gfe:replicated-region id="exampleReplicateRegionWithCacheLoaderAndCacheWriter">
<gfe:cache-loader ref="myLoader"/>
<gfe:cache-writer>
<bean class="example.CacheWriter"/>
</gfe:cache-writer>
</gfe:replicated-region>
<bean id="myLoader" class="example.CacheLoader">
<property name="dataSource" ref="mySqlDataSource"/>
</bean>
<!-- DataSource bean definition -->
</beans>
请参阅 CacheLoader
和 CacheWriter
Apache Geode 文档以获取更多详细信息。
5.5.4. 压缩
Apache Geode 的 Region 也可以进行压缩,以减少 JVM 的内存占用和压力,从而可能避免全局垃圾回收(GC)。当您为某个 Region 启用压缩功能后,该 Region 中存储在内存中的所有值都会被压缩,而键(keys)和索引(indexes)则保持未压缩状态。新值在存入 Region 时会被压缩,而从 Region 中读取时所有值会自动解压缩。需要注意的是,值在持久化到磁盘或通过网络传输给其他对等成员或客户端时不会被压缩。
以下示例展示了一个启用了压缩的 Region:
<beans>
<gfe:replicated-region id="exampleReplicateRegionWithCompression">
<gfe:compressor>
<bean class="org.apache.geode.compression.SnappyCompressor"/>
</gfe:compressor>
</gfe:replicated-region>
</beans>
有关Region 压缩的更多信息,请参阅 Apache Geode 的文档。
5.5.5. 堆外内存
Apache Geode 的 Region 也可以配置为将 Region 的值存储在堆外内存(off-heap memory)中,堆外内存是 JVM 内存的一部分,不受垃圾回收(GC)的影响。通过避免昂贵的 GC 周期,您的应用程序可以将更多时间用于处理真正重要的事情,例如处理请求。
使用堆外内存非常简单,只需声明要使用的内存量,然后启用您的 Region 以使用堆外内存,如下列配置所示:
<util:properties id="gemfireProperties">
<prop key="off-heap-memory-size">200G</prop>
</util:properties>
<gfe:cache properties-ref="gemfireProperties"/>
<gfe:partitioned-region id="ExampleOffHeapRegion" off-heap="true"/>
你可以通过使用 <gfe:cache> 元素设置以下 Apache Geode 配置属性,来控制堆外内存管理的其他方面:
<gfe:cache critical-off-heap-percentage="90" eviction-off-heap-percentage"80"/>
Apache Geode 的 ResourceManager 将使用这两个阈值(critical-off-heap-percentage 和 eviction-off-heap-percentage),以类似于 JVM 管理堆内存的方式,更有效地管理堆外内存。Apache Geode 的 ResourceManager 会通过驱逐旧数据来防止缓存占用过多的堆外内存。如果堆外内存管理器无法及时释放内存,ResourceManager 将拒绝向缓存中添加新数据,直到堆外内存管理器释放出足够数量的内存为止。
有关堆内存和堆外内存管理的更多信息,请参阅 Apache Geode 的文档。
请特别阅读管理堆外内存一节。
5.5.6. 子区域
Spring Data for Apache Geode 还支持子区域(Sub-Regions),允许将区域(Regions)以层次关系进行组织。
例如,Apache Geode 允许存在一个 /Customer/Address 区域(Region)和另一个不同的 /Employee/Address 区域。
此外,子区域(Sub-Region)可以拥有自己的子区域和配置。子区域不会继承其父区域的属性。
区域类型可以根据 Apache Geode 的约束进行混合搭配使用。子区域自然地作为区域的一个子元素进行声明。
子区域的 name 属性为其简单名称。上述示例可以按如下方式配置:
<beans>
<gfe:replicated-region name="Customer">
<gfe:replicated-region name="Address"/>
</gfe:replicated-region>
<gfe:replicated-region name="Employee">
<gfe:replicated-region name="Address"/>
</gfe:replicated-region>
</beans>
请注意,Monospaced ([id]) 属性不允许用于子区域(Sub-Region)。在此例中,子区域是通过 Bean 名称创建的(分别为 /Customer/Address 和 /Employee/Address)。因此,它们可以通过区域的完整路径名称注入到其他应用程序 Bean 中,例如需要它们的 GemfireTemplate。区域的完整路径名称也应在 OQL 查询字符串中使用。
5.5.7. 区域模板
Spring Data for Apache Geode 还支持 Region 模板。
此功能允许开发人员一次性定义通用的 Region 配置和属性,并在 Spring ApplicationContext 中声明的多个 Region Bean 定义之间重用该配置。
Apache Geode 的 Spring Data 在其命名空间中包含了五个 Region 模板标签:
| 标签名称 | 描述 |
|---|---|
|
定义通用的 Region 属性。扩展 XML 命名空间中的 |
|
定义通用的“本地”Region属性。在XML命名空间中扩展 |
|
定义通用的“PARTITION”区域属性。在 XML 命名空间中扩展 |
|
定义通用的“REPLICATE”区域(Region)属性。在 XML 命名空间中扩展 |
|
定义通用的“Client”区域属性。扩展 XML 命名空间中的 |
除了这些标签之外,具体的 <gfe:*-region> 元素(以及抽象的 <gfe:*-region-template> 元素)
还具有一个 template 属性,用于定义该 Region 从中继承其配置的 Region 模板。
Region 模板甚至可以继承自其他 Region 模板。
以下示例展示了一种可能的配置:
<beans>
<gfe:async-event-queue id="AEQ" persistent="false" parallel="false" dispatcher-threads="4">
<gfe:async-event-listener>
<bean class="example.AeqListener"/>
</gfe:async-event-listener>
</gfe:async-event-queue>
<gfe:region-template id="BaseRegionTemplate" initial-capacity="51" load-factor="0.85" persistent="false" statistics="true"
key-constraint="java.lang.Long" value-constraint="java.lang.String">
<gfe:cache-listener>
<bean class="example.CacheListenerOne"/>
<bean class="example.CacheListenerTwo"/>
</gfe:cache-listener>
<gfe:entry-ttl timeout="600" action="DESTROY"/>
<gfe:entry-tti timeout="300 action="INVLIDATE"/>
</gfe:region-template>
<gfe:region-template id="ExtendedRegionTemplate" template="BaseRegionTemplate" load-factor="0.55">
<gfe:cache-loader>
<bean class="example.CacheLoader"/>
</gfe:cache-loader>
<gfe:cache-writer>
<bean class="example.CacheWriter"/>
</gfe:cache-writer>
<gfe:async-event-queue-ref bean="AEQ"/>
</gfe:region-template>
<gfe:partitioned-region-template id="PartitionRegionTemplate" template="ExtendedRegionTemplate"
copies="1" load-factor="0.70" local-max-memory="1024" total-max-memory="16384" value-constraint="java.lang.Object">
<gfe:partition-resolver>
<bean class="example.PartitionResolver"/>
</gfe:partition-resolver>
<gfe:eviction type="ENTRY_COUNT" threshold="8192000" action="OVERFLOW_TO_DISK"/>
</gfe:partitioned-region-template>
<gfe:partitioned-region id="TemplateBasedPartitionRegion" template="PartitionRegionTemplate"
copies="2" local-max-memory="8192" persistent="true" total-buckets="91"/>
</beans>
区域模板同样适用于子区域。请注意,'TemplateBasedPartitionRegion' 继承自 'PartitionRegionTemplate', 而 'PartitionRegionTemplate' 继承自 'ExtendedRegionTemplate',后者又继承自 'BaseRegionTemplate'。在后续继承的区域 Bean 定义中声明的属性和子元素会覆盖父类中的相应内容。
模板的工作原理
Spring Data for Apache Geode 在解析 Spring ApplicationContext 配置元数据时会应用 Region 模板,因此,Region 模板必须按照继承顺序进行声明。换句话说,父模板必须在子模板之前定义。这样做可确保正确应用配置,特别是在元素属性或子元素被覆盖时。
同样重要的是要记住,Region 类型只能继承自其他相同类型的 Region。
例如,<gfe:replicated-region> 不能从 <gfe:partitioned-region-template> 继承。 |
| 区域模板采用单继承。 |
关于区域、子区域和查找的注意事项
此前,在 Spring Data for Apache Geode 的 XML 命名空间中,replicated-region、partitioned-region、local-region 和 client-region 元素的一个底层属性是:在尝试创建 Region 之前先执行查找操作。这样做的原因是,如果该 Region 已经存在(例如,该 Region 已在导入的 Apache Geode 原生 cache.xml 配置文件中定义),则无需再次创建。因此,首先执行查找操作是为了避免出现任何错误。这一行为是设计使然,但可能会发生变化。
此行为已被修改,默认行为现在是首先创建 Region。如果该 Region 已存在,则创建逻辑会快速失败(fail-fast)并抛出相应的异常。然而,与 CREATE TABLE IF NOT EXISTS … DDL 语法类似,Apache Geode 的 Spring Data 中的 <gfe:*-region> XML 命名空间元素现在包含了一个 ignore-if-exists 属性。通过在尝试创建 Region 之前先根据名称查找是否已存在同名 Region,该属性可以恢复旧有行为。如果根据名称找到了已存在的 Region,并且 ignore-if-exists 被设置为 true,那么 Spring 配置中定义的该 Region 的 Bean 定义将被忽略。
Spring 团队强烈建议,replicated-region、partitioned-region、local-region 和 client-region 这些 XML 命名空间元素仅用于定义新的 Region。如果由这些元素定义的 Region 已经存在,并且这些 Region 元素会先执行查找操作,则可能会引发一个问题:如果您在应用程序配置中为驱逐(eviction)、过期(expiration)、订阅(subscription)等行为定义了不同的 Region 语义和特性,那么实际查找到的 Region 定义可能与预期不符,从而表现出与应用程序所需行为相悖的特性。更糟糕的是,您可能希望将该 Region 定义为分布式 Region(例如 PARTITION),而实际上已存在的 Region 定义却仅为本地(local)类型。 |
推荐做法 —— 仅使用 replicated-region、partitioned-region、local-region 和 client-region XML 命名空间元素来定义新的 Region。 |
请考虑以下原生的 Apache Geode cache.xml 配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns="https://geode.apache.org/schema/cache"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://geode.apache.org/schema/cache https://geode.apache.org/schema/cache/cache-1.0.xsd"
version="1.0">
<region name="Customers" refid="REPLICATE">
<region name="Accounts" refid="REPLICATE">
<region name="Orders" refid="REPLICATE">
<region name="Items" refid="REPLICATE"/>
</region>
</region>
</region>
</cache>
此外,请考虑你可能已按如下方式定义了一个应用程序 DAO:
public class CustomerAccountDao extends GemDaoSupport {
@Resource(name = "Customers/Accounts")
private Region customersAccounts;
...
}
在这里,我们在应用程序的 DAO 中注入了对 Customers/Accounts 区域的引用。因此,开发人员在 Spring XML 配置元数据中为其中部分或全部区域定义 Bean 的情况并不少见,如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:gfe="https://www.springframework.org/schema/geode"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/geode https://www.springframework.org/schema/geode/spring-geode.xsd
">
<gfe:cache cache-xml-location="classpath:cache.xml"/>
<gfe:lookup-region name="Customers/Accounts"/>
<gfe:lookup-region name="Customers/Accounts/Orders"/>
</beans>
Customers/Accounts 和 Customers/Accounts/Orders 区域在 Spring 容器中分别被引用为名为 Customers/Accounts 和 Customers/Accounts/Orders 的 bean。使用 lookup-region 元素及其相应语法(如前所述)的好处在于,它允许你直接引用一个子区域,而无需为父区域(本例中为 Customers)不必要地定义一个 bean。
考虑以下不良示例,它将配置元数据语法更改为使用嵌套格式:
<gfe:lookup-region name="Customers">
<gfe:lookup-region name="Accounts">
<gfe:lookup-region name="Orders"/>
</gfe:lookup-region>
</gfe:lookup-region>
现在考虑另一个不良示例,该示例使用顶层的 replicated-region 元素,并设置 ignore-if-exists 属性以首先执行查找:
<gfe:replicated-region name="Customers" persistent="true" ignore-if-exists="true">
<gfe:replicated-region name="Accounts" persistent="true" ignore-if-exists="true">
<gfe:replicated-region name="Orders" persistent="true" ignore-if-exists="true"/>
</gfe:replicated-region>
</gfe:replicated-region>
在 Spring ApplicationContext 中定义的 Region Bean 包含以下内容:
{ "Customers", "/Customers/Accounts", "/Customers/Accounts/Orders" }. 这意味着前面示例中所示的依赖注入引用(即 @Resource(name = "Customers/Accounts"))现在已失效,因为实际上并未定义名为 Customers/Accounts 的 Bean。因此,你不应按照前面两个示例所示的方式配置 Region。
Apache Geode 在引用父 Region 和子 Region 时非常灵活,无论是否带有开头的正斜杠(/)均可。例如,父 Region 可以表示为 /Customers 或 Customers,而子 Region 可以表示为 /Customers/Accounts 或 Customers/Accounts。然而,Spring Data for Apache Geode 在根据 Region 命名 Bean 时则有明确的规定:它始终使用正斜杠(/)来表示子 Region(例如,/Customers/Accounts)。
因此,您应使用前面所示的非嵌套 lookup-region 语法,或使用以正斜杠(/)开头的直接引用,如下所示:
<gfe:lookup-region name="/Customers/Accounts"/>
<gfe:lookup-region name="/Customers/Accounts/Orders"/>
前面的例子中使用了嵌套的 replicated-region 元素来引用子区域(Sub-Regions),这正体现了之前提到的问题:Customers、Accounts 和 Orders 这些区域及其子区域是否是持久化的?
它们并不是持久化的,因为这些区域在原生的 Apache Geode cache.xml 配置文件中被定义为 REPLICATE 类型,并且在缓存 Bean 初始化之前就已经存在(即在处理 <gfe:cache> 元素之前)。
5.5.8. 数据驱逐(带溢出)
根据各种约束条件,每个区域(Region)都可以配置一种驱逐策略,用于从内存中驱逐数据。 目前,在 Apache Geode 中,驱逐操作适用于最近最少使用的条目(也称为 LRU)。被驱逐的条目要么被销毁, 要么被分页到磁盘(称为“溢出到磁盘”)。
Spring Data for Apache Geode 通过使用嵌套的 eviction 元素,支持 PARTITION 区域、REPLICATE 区域以及客户端本地区域的所有驱逐策略(条目数量、内存和堆使用量)。
例如,要配置一个 PARTITION 区域,当内存大小超过 512 MB 时溢出到磁盘, 您可以指定以下配置:
<gfe:partitioned-region id="examplePartitionRegionWithEviction">
<gfe:eviction type="MEMORY_SIZE" threshold="512" action="OVERFLOW_TO_DISK"/>
</gfe:partitioned-region>
副本不能使用 local destroy 驱逐策略,因为这会使它们失效。
有关更多信息,请参阅 Apache Geode 文档。 |
在为溢出(overflow)配置 Region 时,您应通过 disk-store 元素来配置存储,以实现最高效率。
有关逐出策略的详细说明,请参阅 Apache Geode 文档中的逐出部分。
5.5.9. 数据过期
Apache Geode 允许您控制缓存中的条目存在多长时间。过期是由经过的时间驱动的,而驱逐(eviction)则是由条目数量或堆内存/内存使用量驱动的。一旦某个条目过期,就无法再从缓存中访问它。
Apache Geode 支持以下过期类型:
-
生存时间(TTL):对象在最后一次创建或更新后,可在缓存中保留的秒数。对于条目(entries),在执行创建(create)和放入(put)操作时,其计数器会被重置为零。区域(Region)的计数器则在其被创建时以及其中某个条目的计数器被重置时进行重置。
-
空闲超时(TTI):对象在最后一次被访问后,可在缓存中保留的秒数。 每当对象的 TTL 计时器被重置时,其空闲超时计时器也会被重置。此外,每当通过 get 操作或
netSearch访问某个条目时,该条目的空闲超时计时器也会被重置。 当某个区域(Region)中的任一条目的空闲超时被重置时,该区域的空闲超时计时器也会被重置。
这些设置均可应用于 Region 本身或 Region 中的条目。Spring Data for Apache Geode 提供了 <region-ttl>、
<region-tti>、<entry-ttl> 和 <entry-tti> 等 Region 子元素,用于指定超时值和过期操作。
以下示例展示了一个设置了过期值的 PARTITION 区域:
<gfe:partitioned-region id="examplePartitionRegionWithExpiration">
<gfe:region-ttl timeout="30000" action="INVALIDATE"/>
<gfe:entry-tti timeout="600" action="LOCAL_DESTROY"/>
</gfe:replicated-region>
有关过期策略的详细说明,请参阅 Apache Geode 文档中的过期部分。
基于注解的数据过期
使用 Spring Data for Apache Geode,您可以在单个 Region 条目值(或者换句话说,直接在应用程序领域对象)上定义过期策略和设置。例如,您可以按如下方式在基于会话(Session)的应用程序领域对象上定义过期策略:
@Expiration(timeout = "1800", action = "INVALIDATE")
public class SessionBasedApplicationDomainObject {
...
}
您还可以通过使用 @IdleTimeoutExpiration 和 @TimeToLiveExpiration 注解分别为 Region 条目指定空闲超时(TTI)和生存时间(TTL)的过期类型特定设置,如下例所示:
@TimeToLiveExpiration(timeout = "3600", action = "LOCAL_DESTROY")
@IdleTimeoutExpiration(timeout = "1800", action = "LOCAL_INVALIDATE")
@Expiration(timeout = "1800", action = "INVALIDATE")
public class AnotherSessionBasedApplicationDomainObject {
...
}
当指定了多个过期注解类型时(如前面示例所示),@IdleTimeoutExpiration 和 @TimeToLiveExpiration 均优先于通用的 @Expiration 注解。
@IdleTimeoutExpiration 与 @TimeToLiveExpiration 彼此之间不会相互覆盖,而是相互补充,
用于配置不同的 Region 条目过期策略,例如 TTL(生存时间)和 TTI(空闲超时)。
|
所有基于
|
Spring Data for Apache Geode 的 @Expiration 注解支持是通过 Apache Geode 的
CustomExpiry 接口实现的。
有关更多详细信息,请参阅 Apache Geode 关于 配置数据过期 的文档
Spring Data for Apache Geode 的 AnnotationBasedExpiration 类(以及 CustomExpiry 实现)负责处理 SDG 的 @Expiration 注解,并在请求时为 Region 条目过期正确地应用过期策略配置。
要使用 Spring Data for Apache Geode 来配置特定的 Apache Geode 区域(Region),以便对使用 @Expiration 相关注解标记的应用程序领域对象正确应用过期策略,您必须:
-
通过使用适当的构造函数或其中一个便捷的工厂方法,在 Spring
ApplicationContext中定义一个类型为AnnotationBasedExpiration的 Bean。在为特定的过期类型(例如空闲超时(TTI)或生存时间(TTL))配置过期策略时,应使用AnnotationBasedExpiration类中的某个工厂方法,如下所示:<bean id="ttlExpiration" class="org.springframework.data.gemfire.expiration.AnnotationBasedExpiration" factory-method="forTimeToLive"/> <gfe:partitioned-region id="Example" persistent="false"> <gfe:custom-entry-ttl ref="ttlExpiration"/> </gfe:partitioned-region>若要改为配置空闲超时(TTI)过期,请使用
forIdleTimeout工厂方法, 并结合<gfe:custom-entry-tti ref="ttiExpiration"/>元素来设置 TTI。 -
(可选)使用 Spring Data for Apache Geode 提供的
@Expiration注解之一(@Expiration、@IdleTimeoutExpiration或@TimeToLiveExpiration),为存储在 Region 中的应用程序领域对象添加过期策略和自定义设置。 -
(可选)在某些情况下,特定的应用领域对象完全没有使用 Spring Data for Apache Geode 的
@Expiration注解进行标注,但 Apache Geode Region 已配置为使用 SDG 的自定义AnnotationBasedExpiration类来确定存储在 Region 中对象的过期策略和设置。此时,你可以通过以下方式在AnnotationBasedExpirationbean 上设置“默认”过期属性:
<bean id="defaultExpirationAttributes" class="org.apache.geode.cache.ExpirationAttributes">
<constructor-arg value="600"/>
<constructor-arg value="#{T(org.apache.geode.cache.ExpirationAction).DESTROY}"/>
</bean>
<bean id="ttiExpiration" class="org.springframework.data.gemfire.expiration.AnnotationBasedExpiration"
factory-method="forIdleTimeout">
<constructor-arg ref="defaultExpirationAttributes"/>
</bean>
<gfe:partitioned-region id="Example" persistent="false">
<gfe:custom-entry-tti ref="ttiExpiration"/>
</gfe:partitioned-region>
你可能已经注意到,Apache Geode 的 Spring Data(SDG)中的 @Expiration 注解使用 String 作为属性类型,而不是采用更合适的强类型方式——例如,对 'timeout' 使用 int,对 'action' 使用 SDG 的 ExpirationActionType。这是为什么呢?
那么,不妨了解一下 Spring Data for Apache Geode 的其他特性之一:利用 Spring 的核心基础设施来简化配置,例如属性占位符和 Spring 表达式语言(SpEL)表达式。
例如,开发人员可以在 @Expiration 注解的属性中使用属性占位符,同时指定过期的“超时时间(timeout)”和“操作(action)”,如下例所示:
@TimeToLiveExpiration(timeout = "${geode.region.entry.expiration.ttl.timeout}"
action = "${geode.region.entry.expiration.ttl.action}")
public class ExampleApplicationDomainObject {
...
}
然后,在您的 Spring XML 配置文件或 JavaConfig 中,您可以声明以下 Bean:
<util:properties id="expirationSettings">
<prop key="geode.region.entry.expiration.ttl.timeout">600</prop>
<prop key="geode.region.entry.expiration.ttl.action">INVALIDATE</prop>
...
</util:properties>
<context:property-placeholder properties-ref="expirationProperties"/>
当多个应用程序域对象可能共享相似的过期策略,以及您希望将配置外部化时,这种方式都非常方便。
然而,你可能希望根据运行系统的状态来实现更动态的过期配置。这正是 SpEL(Spring 表达式语言)大显身手的地方,实际上这也是推荐的做法。你不仅可以引用 Spring 容器中的 bean、访问 bean 的属性、调用方法等,而且过期配置中的“timeout”(超时时间)和“action”(操作)值还可以是强类型的。请考虑以下示例(该示例基于前面的例子):
<util:properties id="expirationSettings">
<prop key="geode.region.entry.expiration.ttl.timeout">600</prop>
<prop key="geode.region.entry.expiration.ttl.action">#{T(org.springframework.data.gemfire.expiration.ExpirationActionType).DESTROY}</prop>
<prop key="geode.region.entry.expiration.tti.action">#{T(org.apache.geode.cache.ExpirationAction).INVALIDATE}</prop>
...
</util:properties>
<context:property-placeholder properties-ref="expirationProperties"/>
然后,您可以在应用程序的领域对象上定义超时时间和操作,如下所示:
@TimeToLiveExpiration(timeout = "@expirationSettings['geode.region.entry.expiration.ttl.timeout']"
action = "@expirationSetting['geode.region.entry.expiration.ttl.action']")
public class ExampleApplicationDomainObject {
...
}
你可以想象,'expirationSettings' 这个 bean 可能比一个简单的 java.util.Properties 实例更加有趣且有用。在前面的示例中,properties 元素(即 expirationSettings)使用 SpEL 表达式,将 action 值基于实际的 ExpirationAction 枚举类型进行设置,这样一旦该枚举类型发生变更,就能迅速发现并定位故障。
例如,所有这些内容都已在 Apache Geode 的 Spring Data 测试套件中进行了演示和验证。有关更多详细信息,请参见源码。
5.5.10. 数据持久化
Region 可以是持久化的。Apache Geode 会确保所有写入到已配置为持久化的 Region 中的数据,都以可恢复的方式写入磁盘,以便在下次重新创建该 Region 时能够恢复数据。这样即使发生机器或进程故障,甚至在 Apache Geode 数据节点正常关闭并随后重新启动后,数据也能够被恢复。
要启用 Apache Geode 的 Spring Data 持久化功能,请在任意 persistent 元素上将 true 属性设置为 <*-region>,如下例所示:
<gfe:partitioned-region id="examplePersitentPartitionRegion" persistent="true"/>
持久化也可以通过设置 data-policy 属性来配置。为此,请将该属性的值设为
Apache Geode 的 DataPolicy 设置之一,
如下例所示:
<gfe:partitioned-region id="anotherExamplePersistentPartitionRegion" data-policy="PERSISTENT_PARTITION"/>
DataPolicy 必须与 Region 类型相匹配,并且如果显式设置了 persistent 属性,也必须与该属性保持一致。如果将 persistent 属性设置为 false,但指定了持久化的 DataPolicy(例如 PERSISTENT_REPLICATE 或 PERSISTENT_PARTITION),则会抛出初始化异常。
为了在持久化 Region 时获得最高效率,您应通过 disk-store 元素配置存储。
DiskStore 通过使用 disk-store-ref 属性进行引用。此外,Region 可以同步或异步执行磁盘写入操作。以下示例展示了一个同步的 DiskStore:
<gfe:partitioned-region id="yetAnotherExamplePersistentPartitionRegion" persistent="true"
disk-store-ref="myDiskStore" disk-synchronous="true"/>
这一点在配置 DiskStore中有进一步讨论。
5.5.11. 订阅策略
Apache Geode 允许配置点对点(P2P)事件消息传递,
以控制 Region 接收的条目事件。Spring Data for Apache Geode 提供了 <gfe:subscription/> 子元素,
用于将 REPLICATE 和 PARTITION 类型 Region 的订阅策略设置为 ALL 或 CACHE_CONTENT。
以下示例展示了一个订阅策略被设为 CACHE_CONTENT 的 Region:
<gfe:partitioned-region id="examplePartitionRegionWithCustomSubscription">
<gfe:subscription type="CACHE_CONTENT"/>
</gfe:partitioned-region>
5.5.12. 本地区域
Spring Data for Apache Geode 提供了一个专用的 local-region 元素用于创建本地 Region。顾名思义,本地 Region 是独立的,意味着它们不会与其他分布式系统成员共享数据。除此之外,所有通用的 Region 配置选项均适用。
以下示例展示了一个最小化的声明(同样,该示例依赖于 Apache Geode 的 Spring Data 所提供的 XML 命名空间命名约定来装配缓存):
<gfe:local-region id="exampleLocalRegion"/>
在前面的示例中,会创建一个本地 Region(如果尚未存在同名的 Region)。该 Region 的名称与 Bean 的 ID 相同(exampleLocalRegion),并且该 Bean 假定存在一个名为 gemfireCache 的 Apache Geode 缓存。
5.5.13. 复制区域
一种常见的 Region 类型是 REPLICATE Region(即“副本”)。简而言之,当一个 Region 被配置为 REPLICATE 时,每个托管该 Region 的成员都会在本地存储该 Region 所有条目的副本。对 REPLICATE Region 的任何更新都会分发到该 Region 的所有副本。当创建一个副本时,它会经历一个初始化阶段,在此阶段中,它会发现其他副本并自动复制所有条目。
在一个副本初始化期间,您仍然可以继续使用其他副本。
所有通用的配置选项都适用于 REPLICATE(复制)Region。Spring Data for Apache Geode 提供了 replicated-region 元素。
以下示例展示了一个最简声明:
<gfe:replicated-region id="exampleReplica"/>
有关更多详细信息,请参阅 Apache Geode 关于 分布式区域和复制区域 的文档。
5.5.14. 分区区域
Apache Geode 的 Spring Data XML 命名空间也支持 PARTITION 区域。
引用 Apache Geode 文档中的话:
“分区区域是一种将数据在托管该区域的对等服务器之间进行划分的区域,使得每个对等服务器仅存储该区域中数据的一个子集。使用分区区域时,应用程序看到的是该区域的逻辑视图,看起来就像一个包含区域中所有数据的单一映射(map)。对该映射的读取或写入操作会自动透明地路由到托管目标条目的对等服务器。Apache Geode 将哈希码的域划分为多个桶(bucket),每个桶被分配给一个特定的对等服务器,但为了优化整个集群的资源利用率,这些桶可随时被重新定位到其他对等服务器。”
通过使用 PARTITION 元素来创建一个 partitioned-region 区域。其配置选项与 replicated-region 类似,并增加了分区特有的功能,例如冗余副本数量、总最大内存、桶(bucket)数量、分区解析器(partition resolver)等。
以下示例展示了如何设置一个具有两个冗余副本的 PARTITION 区域:
<gfe:partitioned-region id="examplePartitionRegion" copies="2" total-buckets="17">
<gfe:partition-resolver>
<bean class="example.PartitionResolver"/>
</gfe:partition-resolver>
</gfe:partitioned-region>
有关更多详情,请参阅 Apache Geode 关于 分区区域 的文档。
分区区域属性
下表简要概述了专用于 PARTITION 区域的配置选项。
这些选项是在前面所述的通用区域配置选项之外的附加选项。
| 姓名 | 值 | 描述 |
|---|---|---|
副本 |
0..4 |
每个分区用于高可用性的副本数量。默认情况下,不会创建任何副本, 这意味着没有冗余。每个副本都会提供额外的备份,但会占用额外的存储空间。 |
colocated-with |
有效的区域名称 |
此新创建的 |
local-max-memory |
正整数 |
该区域在本进程中使用的最大内存量(以兆字节为单位)。 |
total-max-memory |
任意整数值 |
该区域在所有进程中使用的最大内存量(以兆字节为单位)。 |
partition-listener |
Bean 名称 |
该区域用于处理分区事件的 |
partition-resolver |
Bean 名称 |
此区域用于自定义分区的 |
recovery-delay |
任意长整型值 |
现有成员在另一个成员崩溃后,等待满足冗余的延迟时间(以毫秒为单位)。 -1(默认值)表示在发生故障后不恢复冗余。 |
startup-recovery-delay |
任意长整型值 |
新成员在满足冗余之前等待的延迟时间(以毫秒为单位)。 -1 表示添加新成员不会触发冗余恢复。默认情况下,当添加新成员时会立即恢复冗余。 |
5.5.15. 客户端区域
Apache Geode 支持多种部署拓扑结构来管理和分发数据。Apache Geode 拓扑结构的主题超出了本文档的范围。然而,简要回顾一下,Apache Geode 支持的拓扑结构可分为:点对点(p2p)、客户端-服务器(client-server)和广域网(WAN)。在后两种配置中,通常会声明连接到缓存服务器的客户端 Region。
Spring Data for Apache Geode 通过其 client-cache 元素为每种配置提供专门的支持:
client-region 和 pool。顾名思义,client-region 用于定义客户端 Region,而 pool 则用于定义
由各个客户端 Region 使用和共享的连接池。
以下示例展示了一个典型的客户端 Region 配置:
<bean id="myListener" class="example.CacheListener"/>
<!-- client Region using the default SDG gemfirePool Pool -->
<gfe:client-region id="Example">
<gfe:cache-listener ref="myListener"/>
</gfe:client-region>
<!-- client Region using its own dedicated Pool -->
<gfe:client-region id="AnotherExample" pool-name="myPool">
<gfe:cache-listener ref="myListener"/>
</gfe:client-region>
<!-- Pool definition -->
<gfe:pool id="myPool" subscription-enabled="true">
<gfe:locator host="remoteHost" port="12345"/>
</gfe:pool>
与其他 Region 类型一样,client-region 支持 CacheListener 实例,以及 CacheLoader 和 CacheWriter。它还需要一个连接 Pool,用于连接到一组 Locator 或服务器。
每个客户端 Region 可以拥有自己的 Pool,也可以共享同一个 6。如果未指定 Pool,则将使用“DEFAULT” Pool。
在前面的示例中,Pool 被配置了一个 Locator。Locator 是一个独立的进程,用于在分布式系统中发现缓存服务器和对等数据成员,建议在生产系统中使用。也可以通过使用 Pool 元素将 server 配置为直接连接到一个或多个缓存服务器。 |
有关可在客户端上(尤其是 Pool 上)设置的完整选项列表,请参阅 Spring Data for Apache Geode 的 schema(“Spring Data for Apache Geode Schema”)以及 Apache Geode 关于客户端-服务器配置的文档。
客户利益
为了最小化网络流量,每个客户端可以分别定义自己的“兴趣”策略,向 Apache Geode 指明其实际需要的数据。在 Spring Data for Apache Geode 中,可以为每个客户端 Region 单独定义“兴趣”。系统同时支持基于键(key-based)和基于正则表达式(regular expression-based)的兴趣类型。
以下示例展示了基于键(key-based)和基于正则表达式(regular expression-based)的 interest 类型:
<gfe:client-region id="Example" pool-name="myPool">
<gfe:key-interest durable="true" result-policy="KEYS">
<bean id="key" class="java.lang.String">
<constructor-arg value="someKey"/>
</bean>
</gfe:key-interest>
<gfe:regex-interest pattern=".*" receive-values="false"/>
</gfe:client-region>
一个特殊的键 ALL_KEYS 表示对所有键都注册了“兴趣”。通过使用正则表达式 ".\*" 也可以实现相同的效果。
<gfe:*-interest> 键和正则表达式元素支持三个属性:durable、receive-values 和 result-policy。
durable 表示当客户端连接到集群中的一个或多个服务器时,为该客户端创建的“兴趣”策略和订阅队列是否在客户端会话之间保持不变。如果客户端断开连接后重新连接,在客户端断开期间,服务器上为其保留的durable订阅队列仍然存在。当客户端重新连接时,将接收到其断开连接期间在集群服务器中发生的所有事件。
在集群中的服务器上,会为客户端中定义的每个连接Pool(池)维护一个订阅队列,前提是该Pool已启用订阅功能。该订阅队列用于存储(并可能合并)发送给客户端的事件。如果订阅队列是持久化的(durable),则它会在客户端会话(即连接)之间持续存在,最长可维持到指定的超时时间。如果客户端在给定的时间范围内未重新连接,则为了减少集群服务器上的资源消耗,该客户端连接池的订阅队列将被销毁。如果订阅队列不是durable(持久化的),则在客户端断开连接时会立即被销毁。您需要决定您的客户端是否应接收断开连接期间发生的事件,还是仅需在重新连接后接收最新的事件。
receive-values 属性指示是否在创建和更新事件中接收条目的值。
如果为 true,则会接收值;如果为 false,则仅接收失效事件。
最后,KEYS 是一个枚举值,可选值包括:KEYS_VALUE、NONE 和 KEYS_VALUES。默认值为 result-policy。
5 控制客户端首次连接时用于初始化本地缓存的初始数据转储,
本质上是向客户端提供与兴趣策略匹配的所有条目的事件种子。
如果不像前面提到的那样在Pool上启用订阅功能,客户端的兴趣注册就没什么实际作用。
事实上,在未启用订阅的情况下尝试进行兴趣注册是一种错误。以下示例展示了如何正确操作:
<gfe:pool ... subscription-enabled="true">
...
</gfe:pool>
除了 subscription-enabled 之外,您还可以设置 subscription-ack-interval、
subscription-message-tracking-timeout 和 subscription-redundancy。subscription-redundancy 用于控制集群中的服务器应维护多少份订阅队列的副本。如果冗余度大于
一,并且“主”订阅队列(即该服务器)发生宕机,则“备用”订阅队列
将接管工作,从而在高可用(HA)场景下避免客户端丢失事件。
除了 Pool 设置之外,服务器端的 Region 还使用一个额外的属性 enable-subscription-conflation,
用于控制发送给客户端的事件合并。这也有助于进一步减少网络流量,
在应用程序只关心条目最新值的情况下非常有用。然而,当应用程序需要保留事件的时间序列时,
合并操作会妨碍该使用场景。该属性的默认值为 false。
以下示例展示了一个服务器端的 Region 配置,其对应的客户端包含一个
[CACHING_]PROXY Region,并对该服务器 Region 中的键注册了兴趣:
<gfe:partitioned-region name="ServerSideRegion" enable-subscription-conflation="true">
...
</gfe:partitioned-region>
要控制在客户端与集群中的服务器断开连接后,“持久化”订阅队列保留的时间(以秒为单位),请在 durable-client-timeout 元素上设置 <gfe:client-cache> 属性,如下所示:
<gfe:client-cache durable-client-timeout="600">
...
</gfe:client-cache>
关于客户端关注点的工作原理和功能的全面深入讨论超出了本文档的范围。
有关更多详细信息,请参阅 Apache Geode 关于 客户端到服务器的事件分发 的文档。
5.5.16. JSON 支持
Apache Geode 支持在 Region 中缓存 JSON 文档,并能够使用 Apache Geode OQL(对象查询语言)对存储的 JSON 文档进行查询。JSON 文档在内部以 PdxInstance 类型存储,通过 JSONFormatter 类实现与 JSON 文档(作为 String)之间的相互转换。
Spring Data for Apache Geode 提供了 <gfe-data:json-region-autoproxy/> 元素,用于启用一个
AOP 组件,以对适当的、经过代理的 Region 操作进行增强,
从而有效地封装了 JSONFormatter,使您的应用程序能够直接处理 JSON 字符串。
此外,写入配置为 JSON 的 Region 的 Java 对象会使用 Jackson 的 ObjectMapper 自动转换为 JSON。当这些值被读取回来时,它们将以 JSON 字符串的形式返回。
默认情况下,<gfe-data:json-region-autoproxy/> 会对所有 Region 执行转换。若要将此功能应用于特定的 Region,请在 region-refs 属性中提供一个以逗号分隔的 Region Bean ID 列表。
其他属性包括一个 pretty-print 标志(默认值为 false)和 convert-returned-collections。
此外,默认情况下,getAll() 和 values() Region 操作的结果会针对已配置的 Region 进行转换。
这是通过在本地内存中创建一个并行的数据结构来实现的。对于大型集合,这可能会带来显著的开销,
因此,如果您希望禁用这些 Region 操作的自动转换功能,请将 convert-returned-collections 设置为 false。
某些 Region 操作(特别是那些使用 Apache Geode 特有的 Region.Entry 的操作,例如:entries(boolean)、entrySet(boolean) 和 getEntry() 类型)不会被 AOP 通知所拦截。此外,entrySet() 方法(返回 Set<java.util.Map.Entry<?, ?>>)也不会受到影响。 |
以下示例配置展示了如何设置 pretty-print 和 convert-returned-collections 属性:
<gfe-data:json-region-autoproxy region-refs="myJsonRegion" pretty-print="true" convert-returned-collections="false"/>
此功能还可与 GemfireTemplate 操作无缝协作,前提是该模板已声明为 Spring Bean。目前尚不支持原生的 QueryService 操作。
5.6. 配置索引
Apache Geode 允许在 Region 数据上创建索引(有时也称为索引的复数形式 indices),以提升 OQL(对象查询语言)查询的性能。
在用于 Apache Geode 的 Spring Data 中,索引通过 index 元素声明,如下例所示:
<gfe:index id="myIndex" expression="someField" from="/SomeRegion" type="HASH"/>
在 Apache Geode 的 Spring Data(也称为 SDG XML 命名空间)XML 模式中,index Bean 声明并不像 Apache Geode 原生的 cache.xml 那样绑定到某个 Region。相反,它们是顶层元素,类似于 <gfe:cache> 元素。这使得你可以为任意 Region 声明任意数量的索引,无论这些 Region 是刚刚创建的还是已经存在的——这是对 Apache Geode 原生 cache.xml 格式的一项显著改进。
Index 必须具有一个名称。您可以使用 Index 属性为 name 显式指定一个名称。
否则,将使用 id bean 定义的 bean 名称(即 index 属性的值)作为
Index 的名称。
expression 和 from 子句构成了 Index 的主要组成部分,用于标识要建立索引的数据(即在 from 子句中指定的 Region),以及用于对数据建立索引的条件(即 expression)。该 expression 应基于应用程序域对象中用于谓词的字段,这些字段出现在应用程序定义的 OQL 查询中,用于查询和查找存储在 Region 中的对象。
考虑以下示例,它包含一个 lastName 属性:
@Region("Customers")
class Customer {
@Id
Long id;
String lastName;
String firstName;
...
}
现在考虑以下示例,其中包含一个应用程序定义的 SDG Repository,用于查询 Customer 对象:
interface CustomerRepository extends GemfireRepository<Customer, Long> {
Customer findByLastName(String lastName);
...
}
SDG Repository 的查找/查询方法会生成并执行以下 OQL 语句:
SELECT * FROM /Customers c WHERE c.lastName = '$1'
因此,你可能希望使用类似于以下语句来创建一个Index:
<gfe:index id="myIndex" name="CustomersLastNameIndex" expression="lastName" from="/Customers" type="HASH"/>
from 子句必须引用一个有效且已存在的 Region,这也是 Index 应用于 Region 的方式。
这并非 Spring Data for Apache Geode 所特有的功能,而是 Apache Geode 自身的一项特性。
The Index type 可以是 Spring Data for Apache Geode 定义的枚举值之一:IndexType 枚举:
FUNCTIONAL、HASH 和 PRIMARY_KEY。
每个枚举值都对应于在创建(或“定义”——关于如何“定义”索引的更多信息,请参阅下一节)实际 Index 时调用的其中一个 QueryService
create[|Key|Hash]Index 方法。例如,如果 IndexType 为 PRIMARY_KEY,则调用
QueryService.createKeyIndex(..)
来创建一个 KEY Index。
默认值为 FUNCTIONAL,将导致调用其中一个 QueryService.createIndex(..) 方法。有关完整的选项集,请参阅 Apache Geode 的 Spring Data XML 模式。
有关 Apache Geode 中索引的更多信息,请参阅 Apache Geode 用户指南中的“使用索引”。
5.6.1. 定义索引
除了在 Spring 容器初始化时,由 Spring Data for Apache Geode 在处理 Index Bean 定义的过程中提前创建索引之外,您还可以通过使用 define 属性,在创建索引之前定义应用程序的所有索引,如下所示:
<gfe:index id="myDefinedIndex" expression="someField" from="/SomeRegion" define="true"/>
当 define 设置为 true 时(默认值为 false),它并不会立即创建 Index。
所有“已定义”的索引会在 Spring ApplicationContext“刷新”时一次性创建,或者换句话说,当 Spring 容器发布 ContextRefreshedEvent 时创建。Spring Data for Apache Geode 会注册自身为一个 ApplicationListener,用于监听 ContextRefreshedEvent。当触发时,Spring Data for Apache Geode 会调用
QueryService.createDefinedIndexes()。
一次性定义索引并同时创建它们,可以在创建索引时提升速度和效率。
有关更多详情,请参阅“一次性创建多个索引”。
5.6.2. IgnoreIfExists 和 Override
有两个适用于 Apache Geode 的 Spring Data Index 配置选项值得特别说明:ignoreIfExists 和 override。
这些选项分别对应于 Spring Data for Apache Geode XML 命名空间中 ignore-if-exists 元素的 override 和 <gfe:index> 属性。
在使用这两个选项之前,请务必完全理解你正在做什么。这些选项可能会影响你的应用程序在运行时的性能和资源(例如内存)消耗。因此,在 Spring Data GemFire(SDG)中,这两个选项默认都是禁用的(设置为 false)。 |
| 这些选项仅在 Spring Data for Apache Geode 中可用,用于规避 Apache Geode 中已知的限制。 Apache Geode 本身没有等效的选项或功能。 |
每个选项的行为差异显著,并且完全取决于所抛出的 Apache Geode Index 异常类型。
这也意味着,如果没有抛出 Apache Geode 索引类型的异常,则这两个选项均不会产生任何效果。
这些选项专门用于处理 Apache Geode 的 IndexExistsException 和 IndexNameConflictException
异常实例,这些异常可能由于各种(有时是晦涩难懂的)原因而发生。这些异常的成因如下:
-
当尝试创建
Index时,如果存在另一个具有相同定义但名称不同的Index,则会抛出IndexExistsException。 -
当尝试创建
Index时,如果存在另一个具有相同名称但定义可能不同的Index,则会抛出IndexNameConflictException。
Spring Data for Apache Geode 的默认行为始终是快速失败(fail-fast)。因此,默认情况下,Index 异常不会被“处理”。
这些 Index 异常会被包装在 SDG 的 GemfireIndexException 中并重新抛出。如果您希望 Spring Data for Apache Geode
为您处理这些异常,可以将以下任一 Index Bean 定义选项设置为 true。
IgnoreIfExists 始终优先于 Override,主要是因为它使用的资源更少,原因很简单:在两种异常情况下,它都会直接返回“已存在的”Index。
IgnoreIfExists 行为
当抛出 IndexExistsException 异常且 ignoreIfExists 设置为 true(或 <gfe:index ignore-if-exists="true">)时,
此 Index Bean 定义或声明本应创建的 index 将被忽略,
并直接返回已存在的 Index。
返回现有的Index几乎不会产生任何影响,因为index bean 的定义是相同的,这一点是由 Apache Geode 自身决定的,而非 Spring Data for Apache Geode(SDG)。
然而,这也意味着从 Apache Geode 的角度来看,在您的 index Bean 定义或声明中指定了“名称”的 Index 实际上并不存在(即带有
QueryService.getIndexes())。
因此,在编写使用查询提示的 OQL 查询语句时应格外小心,尤其是那些引用应用程序 Index 被忽略的查询提示。这些查询提示需要被修改。
当抛出 IndexNameConflictException 异常且 ignoreIfExists 设置为 true(或 <gfe:index ignore-if-exists="true">)时,
本应由该 Index bean 定义或声明所创建的 index 也会被忽略,
并再次返回“已存在的”Index,这与抛出 IndexExistsException 异常时的行为一致。
然而,当抛出 Index 时,返回现有的 Index 并忽略应用程序对 IndexNameConflictException 的定义会带来更大的风险。对于 IndexNameConflictException 而言,尽管冲突索引的名称相同,但它们的定义可能不同。这种情况可能会对应用程序特定的 OQL 查询产生影响,因为您通常会假定这些索引是根据应用程序的数据访问模式和查询需求专门定义的。然而,如果同名索引在定义上存在差异,这一假设可能就不成立了。因此,您应当仔细验证您的 Index 名称。
SDG 会尽最大努力在用户忽略的 Index 与现有 Index 在定义上存在显著差异时通知用户。然而,为了实现这一点,SDG 必须能够找到现有的 Index,而该查找是通过使用 Apache Geode API(唯一可用的方式)完成的。 |
Override 行为
当抛出 IndexExistsException 异常且 override 设置为 true(或 <gfe:index override="true">)时,
该 Index 实际上会被重命名。请注意,当存在多个具有相同定义但名称不同的索引时,会抛出 IndexExistsExceptions 异常。
Spring Data for Apache Geode 只能通过使用 Apache Geode 的 API 来实现这一点,即先删除现有的Index,
然后使用新名称重新创建Index。删除操作或后续的创建调用都有可能失败。
目前无法以原子方式执行这两个操作,并在任一操作失败时回滚整个联合操作。
然而,如果操作成功,那么你就会遇到与之前使用 ignoreIfExists 选项时相同的问题。任何现有的 OQL 查询语句,如果其中的查询提示(query hints)通过名称引用了旧的 Index,都必须进行修改。
当抛出 IndexNameConflictException 异常且 override 设置为 true(或使用 <gfe:index override="true">)时,
现有的 Index 可能会被重新定义。我们之所以说“可能”,是因为在抛出 Index 异常时,
同名的现有 IndexNameConflictException 可能具有完全相同的定义和名称。
如果是这种情况,SDG 会很智能地直接返回现有的 Index,即使在 override 模式下也是如此。这种行为不会造成任何损害,因为名称和定义完全相同。当然,SDG 只有在能够通过 Apache Geode 的 API 找到现有 Index 的情况下才能实现这一点。如果找不到,则不会执行任何操作,并抛出一个包装了 GemfireIndexException 的 SDG IndexNameConflictException。
然而,当现有的 Index 定义不同时,SDG 会尝试使用 Index bean 定义中指定的 Index 定义来重新创建该 index。
请确保这是您期望的行为,并确保 index bean 定义符合您的预期和应用程序需求。
IndexNameConflictExceptions 究竟是如何发生的?
抛出 IndexExistsExceptions 异常的情况可能并不少见,尤其是在使用多种配置源来配置 Apache Geode 时(例如 Spring Data for Apache Geode、Apache Geode 集群配置、Apache Geode 原生的 cache.xml、API 等)。您应当明确选择其中一种配置方式,并坚持使用该方式。
然而,IndexNameConflictException 会在什么时候被抛出呢?
一个特定情况是在 Index 上定义的 PARTITION 区域(PR)。当在 Index 上定义了 PARTITION 区域
(例如,X),Apache Geode 会将 Index 定义(和名称)分发给集群中其他也托管相同 PARTITION 区域的
对等成员(即“X”)。此 Index 定义的分发以及随后由对等成员创建该 Index 是基于“按需知晓”原则进行的(即由托管相同 PR 的对等成员执行),并且是异步执行的。
在此时间段内,这些待处理的 PR Indexes 可能无法被 Apache Geode 识别——例如通过调用 QueryService.getIndexes()
配合 QueryService.getIndexes(:Region),
甚至通过 QueryService.getIndex(:Region, indexName:String)。
因此,SDG 或其他不涉及 Spring 的 Apache Geode 缓存客户端应用程序要确切知道这一点的唯一方法是尝试创建 Index。如果操作因 IndexNameConflictException 甚至 IndexExistsException 而失败,应用程序便知道存在问题。这是因为 QueryService 的 Index 创建会等待待处理的 Index 定义,而其他 Apache Geode API 调用则不会。
无论如何,Spring Data for Apache Geode(SDG)都会尽最大努力尝试告知您发生了什么或正在发生什么,并告诉您应采取的纠正措施。鉴于所有 Apache Geode 的 QueryService.createIndex(..) 方法均为同步阻塞操作,因此在抛出上述任一索引类型异常后,Apache Geode 的状态应当是一致且可访问的。因此,SDG 可以根据您的配置检查系统状态并相应地采取行动。
在所有其他情况下,SDG 采用快速失败(fail-fast)策略。
5.7. 配置 DiskStore
Spring Data for Apache Geode 通过 DiskStore 元素支持 disk-store 的配置与创建,如下例所示:
<gfe:disk-store id="Example" auto-compact="true" max-oplog-size="10"
queue-size="50" time-interval="9999">
<gfe:disk-dir location="/disk/location/one" max-size="20"/>
<gfe:disk-dir location="/disk/location/two" max-size="20"/>
</gfe:disk-store>
DiskStore 实例被 Region 用于文件系统的持久化备份和溢出被逐出的条目,
同时也用于 WAN 网关的持久化备份。多个 Apache Geode 组件可以共享同一个 DiskStore。
此外,如上例所示,单个 DiskStore 可以定义多个文件系统目录。
有关持久化与溢出以及DiskStore实例的配置选项的完整说明,请参阅 Apache Geode 的文档。
5.8. 配置快照服务
Spring Data for Apache Geode 通过使用 Apache Geode 的快照服务(Snapshot Service) 来支持缓存(Cache)和区域(Region)的快照。 开箱即用的快照服务支持提供了多项便捷功能,以简化 Apache Geode 的 缓存(Cache) 和 区域(Region) 快照服务 API 的使用。
正如Apache Geode 文档所述, 快照功能允许您保存缓存的数据,并在之后重新加载,这对于在不同环境之间迁移数据非常有用, 例如从生产环境迁移到预发布或测试环境,以便在受控环境中复现与数据相关的问题。 您可以将 Spring Data for Apache Geode 的快照服务支持 与Spring 的 Bean 定义配置文件 结合起来,根据需要加载特定于当前环境的快照数据。
Spring Data for Apache Geode 对 Apache Geode 快照服务(Snapshot Service)的支持始于 <gfe-data:snapshot-service> 元素,该元素来自 <gfe-data> XML 命名空间。
例如,你可以通过使用几个快照导入和一个数据导出定义,来定义缓存范围的快照,以便同时加载和保存,如下所示:
<gfe-data:snapshot-service id="gemfireCacheSnapshotService">
<gfe-data:snapshot-import location="/absolute/filesystem/path/to/import/fileOne.snapshot"/>
<gfe-data:snapshot-import location="relative/filesystem/path/to/import/fileTwo.snapshot"/>
<gfe-data:snapshot-export
location="/absolute/or/relative/filesystem/path/to/export/directory"/>
</gfe-data:snapshot-service>
您可以根据需要定义任意数量的导入和导出。您可以仅定义导入,也可以仅定义导出。文件位置和目录路径可以是绝对路径,也可以是相对于 Apache Geode 的 Spring Data 应用程序(即 JVM 进程的工作目录)的相对路径。
前面的示例相当简单,此处定义的 Snapshot Service 引用了默认名称为 gemfireCache 的 Apache Geode 缓存实例(如配置缓存中所述)。如果你将缓存 bean 定义命名为非默认名称,则可以使用 cache-ref 属性按名称引用该缓存 bean,如下所示:
<gfe:cache id="myCache"/>
...
<gfe-data:snapshot-service id="mySnapshotService" cache-ref="myCache">
...
</gfe-data:snapshot-service>
您也可以通过指定 region-ref 属性来为特定区域定义一个快照服务,如下所示:
<gfe:partitioned-region id="Example" persistent="false" .../>
...
<gfe-data:snapshot-service id="gemfireCacheRegionSnapshotService" region-ref="Example">
<gfe-data:snapshot-import location="relative/path/to/import/example.snapshot/>
<gfe-data:snapshot-export location="/absolute/path/to/export/example.snapshot/>
</gfe-data:snapshot-service>
当指定 region-ref 属性时,Apache Geode 的 Spring Data 的 SnapshotServiceFactoryBean 会将 region-ref 属性值解析为 Spring 容器中定义的 Region Bean,并创建一个
RegionSnapshotService。
快照导入和导出定义的功能方式相同。但是,location 必须引用导出操作中的文件。
| Apache Geode 在引用导入的快照文件之前,会严格检查这些文件是否真实存在。 对于导出操作,Apache Geode 会创建快照文件。如果用于导出的快照文件已存在, 则其中的数据将被覆盖。 |
Spring Data for Apache Geode 在 suppress-import-on-init 元素上提供了一个 <gfe-data:snapshot-service> 属性,
用于禁止配置的快照服务在初始化时尝试将数据导入缓存或 Region。
例如,当从一个 Region 导出的数据被用于填充另一个 Region 的导入操作时,此功能非常有用。 |
5.8.1. 快照位置
使用基于缓存的快照服务
(即 CacheSnapshotService)
时,您通常会传递一个包含所有要加载的快照文件的目录,而不是单个快照文件,
正如 CacheSnapshotService API 中重载的 load
方法所指示的那样。
当然,你可以使用重载的 load(:File[], :SnapshotFormat, :SnapshotOptions) 方法,明确指定要加载到 Apache Geode 缓存中的快照文件。 |
然而,Spring Data for Apache Geode 认识到,典型的开发人员工作流程可能是从一个环境中提取并导出数据到多个快照文件中,将所有这些文件打包成一个 ZIP 文件,然后方便地将该 ZIP 文件移动到另一个环境进行导入。
因此,Apache Geode 的 Spring Data 允许您在导入时为基于 cache 的快照服务指定一个 jar 或 zip 文件,如下所示:
<gfe-data:snapshot-service id="cacheBasedSnapshotService" cache-ref="gemfireCache">
<gfe-data:snapshot-import location="/path/to/snapshots.zip"/>
</gfe-data:snapshot-service>
Spring Data for Apache Geode 会方便地解压所提供的 zip 文件,并将其视为目录导入(加载)。
5.8.2. 快照过滤器
定义多个快照导入和导出的真正威力是通过使用快照过滤器实现的。
快照过滤器实现了 Apache Geode 的 SnapshotFilter 接口,
用于在导入时筛选区域条目以包含到区域中,以及在导出时筛选以包含到快照中。
Spring Data for Apache Geode 允许您在导入和导出时使用快照过滤器,可通过 filter-ref 属性或匿名的嵌套 Bean 定义来实现,如下例所示:
<gfe:cache/>
<gfe:partitioned-region id="Admins" persistent="false"/>
<gfe:partitioned-region id="Guests" persistent="false"/>
<bean id="activeUsersFilter" class="example.gemfire.snapshot.filter.ActiveUsersFilter/>
<gfe-data:snapshot-service id="adminsSnapshotService" region-ref="Admins">
<gfe-data:snapshot-import location="/path/to/import/users.snapshot">
<bean class="example.gemfire.snapshot.filter.AdminsFilter/>
</gfe-data:snapshot-import>
<gfe-data:snapshot-export location="/path/to/export/active/admins.snapshot" filter-ref="activeUsersFilter"/>
</gfe-data:snapshot-service>
<gfe-data:snapshot-service id="guestsSnapshotService" region-ref="Guests">
<gfe-data:snapshot-import location="/path/to/import/users.snapshot">
<bean class="example.gemfire.snapshot.filter.GuestsFilter/>
</gfe-data:snapshot-import>
<gfe-data:snapshot-export location="/path/to/export/active/guests.snapshot" filter-ref="activeUsersFilter"/>
</gfe-data:snapshot-service>
此外,您还可以使用 ComposableSnapshotFilter 类来表达更复杂的快照过滤器。
该类实现了 Apache Geode 的 SnapshotFilter 接口,
同时也实现了 Composite 软件设计模式。
简而言之,组合(Composite)软件设计模式允许你将多个相同类型的对象组合在一起,并将该聚合体视为该对象类型的一个单一实例——这是一种强大而有用的抽象。
ComposableSnapshotFilter 提供了两个工厂方法:and 和 or。它们分别允许你使用逻辑 AND 和 OR 运算符将多个快照过滤器进行逻辑组合。这些工厂方法接收一个 SnapshotFilters 列表作为参数。
以下示例展示了一个 ComposableSnapshotFilter 的定义:
<bean id="activeUsersSinceFilter" class="org.springframework.data.gemfire.snapshot.filter.ComposableSnapshotFilter"
factory-method="and">
<constructor-arg index="0">
<list>
<bean class="org.example.app.gemfire.snapshot.filter.ActiveUsersFilter"/>
<bean class="org.example.app.gemfire.snapshot.filter.UsersSinceFilter"
p:since="2015-01-01"/>
</list>
</constructor-arg>
</bean>
然后,你可以使用 activesUsersSinceFilter 将 or 与其他过滤器组合起来,如下所示:
<bean id="covertOrActiveUsersSinceFilter" class="org.springframework.data.gemfire.snapshot.filter.ComposableSnapshotFilter"
factory-method="or">
<constructor-arg index="0">
<list>
<ref bean="activeUsersSinceFilter"/>
<bean class="example.gemfire.snapshot.filter.CovertUsersFilter"/>
</list>
</constructor-arg>
</bean>
5.8.3. 快照事件
默认情况下,Spring Data for Apache Geode 在启动时使用 Apache Geode 的快照服务导入数据,在关闭时导出数据。然而,您可能希望在 Spring 应用程序内部触发定期的、基于事件的快照,用于导入或导出数据。
为此,Spring Data for Apache Geode 定义了两个额外的 Spring 应用程序事件,分别扩展了 Spring 的
ApplicationEvent
类以用于导入和导出:
ImportSnapshotApplicationEvent 和 ExportSnapshotApplicationEvent。
这两个应用程序事件可以针对整个 Apache Geode 缓存,也可以针对单个 Apache Geode 区域(Region)。这些类的构造函数接受一个可选的区域路径名(例如 /Example),以及零个或多个 SnapshotMetadata 实例。
SnapshotMetadata 数组会覆盖由 <gfe-data:snapshot-import> 和 <gfe-data:snapshot-export> 子元素定义的快照元数据,后者用于快照应用事件未显式提供 SnapshotMetadata 的情况。每个单独的 SnapshotMetadata 实例都可以定义自己的 location 和 filters 属性。
在 Spring ApplicationContext 中定义的所有快照服务 Bean 都会接收到导入和导出快照的应用事件。然而,只有匹配的快照服务 Bean 才会处理导入和导出事件。
基于 Region 的 [Import|Export]SnapshotApplicationEvent 事件匹配的条件是:所定义的快照服务(Snapshot Service)Bean 为 RegionSnapshotService,并且其 Region 引用(由 region-ref 属性确定)与快照应用事件中指定的 Region 路径名相匹配。
基于缓存的 [Import|Export]SnapshotApplicationEvent(即不带 Region 路径名的快照应用事件)
会触发所有快照服务 Bean(包括任何 RegionSnapshotService Bean)分别执行导入或导出操作。
您可以使用 Spring 的
ApplicationEventPublisher
接口,从您的应用程序中触发导入和导出快照应用事件,如下所示:
@Component
public class ExampleApplicationComponent {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Resource(name = "Example")
private Region<?, ?> example;
public void someMethod() {
...
File dataSnapshot = new File(System.getProperty("user.dir"), "/path/to/export/data.snapshot");
SnapshotFilter myFilter = ...;
SnapshotMetadata exportSnapshotMetadata =
new SnapshotMetadata(dataSnapshot, myFilter, null);
ExportSnapshotApplicationEvent exportSnapshotEvent =
new ExportSnapshotApplicationEvent(this, example.getFullPath(), exportSnapshotMetadata)
eventPublisher.publishEvent(exportSnapshotEvent);
...
}
}
在前面的示例中,只有 /Example 区域的快照服务 Bean 会捕获并处理导出事件,
将经过筛选的“/Example”区域的数据保存到应用程序工作目录的子目录中的 data.snapshot 文件里。
使用 Spring 应用程序事件和消息子系统是保持应用程序松耦合的一种良好方式。 你还可以使用 Spring 的调度(Scheduling)服务, 定期触发快照应用程序事件。
5.9. 配置函数服务
Spring Data for Apache Geode 提供了注解支持,用于实现、注册和执行 Apache Geode 函数。
Spring Data for Apache Geode 还提供了 XML 命名空间支持,用于注册 Apache Geode 函数 以进行远程函数执行。
有关函数执行框架的更多信息,请参阅 Apache Geode 的文档。
Apache Geode 函数被声明为 Spring Bean,且必须实现 org.apache.geode.cache.execute.Function 接口,或继承 org.apache.geode.cache.execute.FunctionAdapter 类。
该命名空间使用一种熟悉的模式来声明函数,如下例所示:
<gfe:function-service>
<gfe:function>
<bean class="example.FunctionOne"/>
<ref bean="function2"/>
</gfe:function>
</gfe:function-service>
<bean id="function2" class="example.FunctionTwo"/>
5.10. 配置 WAN 网关
WAN 网关提供了一种跨地理位置同步 Apache Geode 分布式系统的方法。 Spring Data for Apache Geode 提供了 XML 命名空间支持,用于配置 WAN 网关,如下列示例所示。
5.10.1. Apache Geode 7.0 中的 WAN 配置
在以下示例中,通过向 Region 添加子元素(GatewaySenders 和 PARTITION)来为 gateway-sender Region 配置 gateway-sender-ref。GatewaySender 可以注册 EventFilters 和 TransportFilters。
以下示例还展示了一个 AsyncEventQueue 的示例配置,该配置也必须自动装配(auto-wired)到一个 Region 中(未显示):
<gfe:partitioned-region id="region-with-inner-gateway-sender" >
<gfe:gateway-sender remote-distributed-system-id="1">
<gfe:event-filter>
<bean class="org.springframework.data.gemfire.example.SomeEventFilter"/>
</gfe:event-filter>
<gfe:transport-filter>
<bean class="org.springframework.data.gemfire.example.SomeTransportFilter"/>
</gfe:transport-filter>
</gfe:gateway-sender>
<gfe:gateway-sender-ref bean="gateway-sender"/>
</gfe:partitioned-region>
<gfe:async-event-queue id="async-event-queue" batch-size="10" persistent="true" disk-store-ref="diskstore"
maximum-queue-memory="50">
<gfe:async-event-listener>
<bean class="example.AsyncEventListener"/>
</gfe:async-event-listener>
</gfe:async-event-queue>
<gfe:gateway-sender id="gateway-sender" remote-distributed-system-id="2">
<gfe:event-filter>
<ref bean="event-filter"/>
<bean class="org.springframework.data.gemfire.example.SomeEventFilter"/>
</gfe:event-filter>
<gfe:transport-filter>
<ref bean="transport-filter"/>
<bean class="org.springframework.data.gemfire.example.SomeTransportFilter"/>
</gfe:transport-filter>
</gfe:gateway-sender>
<bean id="event-filter" class="org.springframework.data.gemfire.example.AnotherEventFilter"/>
<bean id="transport-filter" class="org.springframework.data.gemfire.example.AnotherTransportFilter"/>
在 GatewaySender 的另一端,有一个对应的 GatewayReceiver 用于接收网关事件。
GatewayReceiver 也可以配置 EventFilters 和 TransportFilters,如下所示:
<gfe:gateway-receiver id="gateway-receiver" start-port="12345" end-port="23456" bind-address="192.168.0.1">
<gfe:transport-filter>
<bean class="org.springframework.data.gemfire.example.SomeTransportFilter"/>
</gfe:transport-filter>
</gfe:gateway-receiver>
有关所有配置选项的详细说明,请参阅 Apache Geode 文档。
6. 使用注解通过 Spring 容器引导 Apache Geode
Spring Data for Apache Geode(SDG)2.0 引入了一种基于注解的全新配置模型,用于通过 Spring 容器配置和引导 Apache Geode。
在 Spring 上下文中引入基于注解的 Apache Geode 配置方法的主要动机,是使 Spring 应用程序开发人员能够最快速且最轻松地上手并开始使用。
让我们开始吧!
| 如果你想更快地开始,请参阅快速入门部分。 |
6.1. 简介
鉴于 Apache Geode 拥有众多的配置属性和不同的配置选项,正确地设置和使用它可能会比较困难:
进一步的复杂性源于所支持的不同拓扑结构:
基于注解的配置模型旨在简化所有这些操作,甚至更多。
基于注解的配置模型是使用 Spring Data for Apache Geode 的 XML 命名空间进行基于 XML 配置的一种替代方案。
使用 XML 时,您可以使用 gfe XML schema 进行配置,以及使用 gfe-data XML schema 进行数据访问。
更多详情请参见“使用 Spring 容器引导 Apache Geode”。
| 从 Spring Data for Apache Geode(SDG)2.0 起,基于注解的配置模型尚不支持 Apache Geode 的 WAN 组件和拓扑结构的配置。 |
与 Spring Boot 类似,Apache Geode 的 Spring Data 也采用了一种基于注解的配置模型,该模型是一种“约定优于配置”的、带有明确主张(opinionated)的方式来使用 Apache Geode。事实上,这种基于注解的配置模型不仅受到 Spring Boot 的启发,也借鉴了其他多个 Spring 和 Spring Data 项目。
按照约定,所有注解为其所有配置属性都提供了合理且恰当的默认值。 给定注解属性的默认值直接对应于 Apache Geode 中相同配置属性所提供的默认值。
其目的是让您通过在 Spring 的 @Configuration 或 @SpringBootApplication 类上声明相应的注解,即可启用 Apache Geode 功能或嵌入式服务,而无需为了使用该功能或服务而额外配置大量属性。
同样,快速入门、迅速且尽可能简单是首要目标。
然而,如果您需要自定义 Apache Geode 的配置元数据和行为,这一选项是可用的,而 Spring Data for Apache Geode 的基于注解的配置会自动退让。您只需指定希望调整的配置属性即可。此外,正如本文档稍后将介绍的那样,有多种方式可以通过注解来配置 Apache Geode 的某项功能或嵌入式服务。
你可以在 Annotations 包中找到所有新的 SDG Java org.springframework.data.gemfire.config.annotation。
6.2. 使用 Spring 配置 Apache Geode 应用程序
与所有通过在应用程序类上添加 @SpringBootApplication 注解来启动的 Spring Boot 应用程序一样,
Spring Boot 应用程序只需声明以下三个主要注解中的任意一个,即可轻松转变为 Apache Geode 缓存应用程序:
-
@ClientCacheApplication -
@PeerCacheApplication -
@CacheServerApplication
这三个注解是 Spring 应用开发者在使用 Apache Geode 时的起点。
要实现这些注解背后的意图,您必须了解使用 Apache Geode 可以创建两种类型的缓存实例:客户端缓存(client cache)或对等缓存(peer cache)。
你可以通过一个 ClientCache 实例,将 Spring Boot 应用程序配置为 Apache Geode 缓存客户端,
该客户端能够与现有的 Apache Geode 服务器集群进行通信,以管理应用程序的数据。
客户端-服务器拓扑结构是使用 Apache Geode 时最常见的系统架构,
你只需在 Spring Boot 应用程序上添加 ClientCache 注解,
即可通过 @ClientCacheApplication 实例将其变为缓存客户端。
或者,Spring Boot 应用程序也可以作为 Apache Geode 集群的一个对等成员。也就是说,该应用程序本身只是管理数据的服务器集群中的另一台服务器。当你在应用程序类上使用 Cache 注解时,Spring Boot 应用程序会创建一个“嵌入式”的对等 @PeerCacheApplication 实例。
此外,一个对等缓存(peer cache)应用程序也可以同时充当CacheServer,允许缓存客户端连接到该服务器并执行数据访问操作。这是通过在应用程序类上使用@CacheServerApplication注解(替代@PeerCacheApplication)来实现的,该注解会创建一个对等Cache实例以及一个允许缓存客户端连接的CacheServer。
Apache Geode 服务器默认情况下并不一定就是缓存服务器。也就是说,一个服务器仅仅因为它是服务器,并不意味着它就一定会被配置为服务缓存客户端。Apache Geode 服务器可以作为集群中的对等成员(数据节点)来管理数据,而无需服务任何客户端;与此同时,集群中的其他对等成员则既可以管理数据,也可以被配置为服务客户端。此外,还可以将集群中的某些对等成员配置为非数据节点,称为数据访问器(data accessors),它们本身不存储数据,但可作为代理以CacheServers的身份为客户端提供服务。Apache Geode 支持多种不同的拓扑结构和集群配置方式,但这些内容超出了本文档的范围。 |
例如,如果你想创建一个 Spring Boot 缓存客户端应用程序,请从以下内容开始:
ClientCache 应用程序@SpringBootApplication
@ClientCacheApplication
class ClientApplication { .. }
或者,如果你想创建一个带有嵌入式对等 Cache 实例的 Spring Boot 应用程序,使你的应用程序成为由 Apache Geode 构成的集群(分布式系统)中的服务器和对等成员,请从以下内容开始:
Cache 应用程序@SpringBootApplication
@PeerCacheApplication
class ServerApplication { .. }
或者,你可以使用 @CacheServerApplication 注解替代 @PeerCacheApplication,以同时创建一个嵌入式的对等 Cache 实例以及一个在 CacheServer 上运行的 localhost,后者将监听默认的缓存服务器端口 40404,如下所示:
Cache 应用程序,包含 CacheServer@SpringBootApplication
@CacheServerApplication
class ServerApplication { .. }
6.3. 客户端/服务器应用程序详解
客户端有多种方式可以连接到 Apache Geode 集群中的服务器并与之通信。 最常见且推荐的方法是使用 Apache Geode 定位器(Locators)。
缓存客户端可以连接到 Apache Geode 集群中的一个或多个定位器(Locator),而不是直接连接到 CacheServer。使用定位器而非直接连接 CacheServer 的优势在于,定位器会向客户端提供其所连接集群的元数据。这些元数据包括诸如哪些服务器包含客户端感兴趣的数据,或者哪些服务器当前负载最低等信息。此外,客户端的 Pool 与定位器结合使用,还能在 CacheServer 崩溃时提供故障转移(fail-over)能力。通过在客户端的 PARTITION 中启用 Pool 区域(PR)的单跳(single-hop)特性,客户端可被直接路由到包含其所需数据的服务器。 |
| 定位器(Locators)同时也是集群中的对等成员。实际上,定位器构成了 Apache Geode 节点集群的基础。也就是说,所有通过定位器连接的节点都是集群中的对等成员,而新加入的成员则通过定位器加入集群并发现其他成员。 |
默认情况下,当创建 ClientCache 实例时,Apache Geode 会设置一个连接到运行在 localhost 上的 CacheServer 的“DEFAULT”Pool,并监听端口 40404。CacheServer 监听端口 40404,接受来自所有系统网络接口卡(NIC)的连接。您无需进行任何特殊操作即可使用客户端 - 服务器拓扑结构。只需将服务器端 Spring Boot 应用程序用 @CacheServerApplication 注解,并将客户端 Spring Boot 应用程序用 @ClientCacheApplication 注解,即可开始使用。
如果愿意,你甚至可以使用 Gfsh 的 start server 命令来启动你的服务器。无论服务器是以何种方式启动的,你的 Spring Boot @ClientCacheApplication 仍然可以连接到该服务器。不过,你可能更倾向于使用 Spring Data for Apache Geode 的方式来配置和启动服务器,因为一个正确添加注解的 Spring Boot 应用程序类更加直观,也更容易调试。
作为一名应用程序开发者,您无疑希望自定义 Apache Geode 设置的“DEFAULT”Pool,以便可能连接到一个或多个 Locator,如下例所示:
ClientCache 应用程序,使用定位器(Locators)@SpringBootApplication
@ClientCacheApplication(locators = {
@Locator(host = "boombox" port = 11235),
@Locator(host = "skullbox", port = 12480)
})
class ClientApplication { .. }
除了 locators 属性外,@ClientCacheApplication 注解还具有一个 servers 属性。
servers 属性可用于指定一个或多个嵌套的 @Server 注解,以便缓存客户端在必要时直接连接到一个或多个服务器。
您可以使用 locators 或 servers 属性,但不能同时使用两者(此限制由 Apache Geode 强制执行)。 |
你还可以使用 Pool 和 Pool 注解来配置额外的 ClientCache 实例(除了在使用 @ClientCacheApplication 注解创建 @EnablePool 实例时,Apache Geode 提供的“DEFAULT”@EnablePools 之外)。
@EnablePools 是一个复合注解,用于将多个嵌套的 @EnablePool 注解聚合到一个类上。Java 8 及更早版本不允许在单个类上声明多个相同类型的注解。 |
以下示例使用了 @EnablePool 和 @EnablePools 注解:
ClientCache 应用程序,使用多个命名的 Pools@SpringBootApplication
@ClientCacheApplication(logLevel = "info")
@EnablePool(name = "VenusPool", servers = @Server(host = "venus", port = 48484),
min-connections = 50, max-connections = 200, ping-internal = 15000,
prSingleHopEnabled = true, readTimeout = 20000, retryAttempts = 1,
subscription-enable = true)
@EnablePools(pools = {
@EnablePool(name = "SaturnPool", locators = @Locator(host="skullbox", port=20668),
subsription-enabled = true),
@EnablePool(name = "NeptunePool", severs = {
@Server(host = "saturn", port = 41414),
@Server(host = "neptune", port = 42424)
}, min-connections = 25))
})
class ClientApplication { .. }
name 属性是 @EnablePool 注解唯一必需的属性。正如我们稍后将看到的,name 属性的值既对应于在 Spring 容器中创建的 Pool bean 的名称,也对应于用于引用相应配置属性的名称。它同时也是 Apache Geode 注册并使用的 Pool 的名称。
同样,在服务器端,您可以配置多个客户端可以连接的CacheServers,如下所示:
CacheServer 应用程序,使用多个命名的 CacheServers@SpringBootApplication
@CacheSeverApplication(logLevel = "info", autoStartup = true, maxConnections = 100)
@EnableCacheServer(name = "Venus", autoStartup = true,
hostnameForClients = "venus", port = 48484)
@EnableCacheServers(servers = {
@EnableCacheServer(name = "Saturn", hostnameForClients = "saturn", port = 41414),
@EnableCacheServer(name = "Neptune", hostnameForClients = "neptune", port = 42424)
})
class ServerApplication { .. }
与 @EnablePools 类似,@EnableCacheServers 是一个组合注解,用于在单个类上聚合多个 @EnableCacheServer 注解。同样地,Java 8 及更早版本不允许在单个类上声明多个相同类型的注解。 |
细心的读者可能会注意到,在所有情况下,您都为所有主机名、端口以及面向配置的注解属性指定了硬编码的值。当应用程序被提升并部署到不同环境(例如从开发环境 DEV 到测试环境 QA,再到预发布环境 STAGING,最后到生产环境 PROD)时,这种做法并不理想。
下一节将介绍如何处理在运行时确定的动态配置。
6.4. 配置和引导定位器
除了 Apache Geode 缓存应用程序外,您还可以创建 Apache Geode 定位器(Locator)应用程序。
Apache Geode Locator 是一个 JVM 进程,允许节点以对等成员的身份加入 Apache Geode 集群。 Locator 还使客户端能够发现集群中的服务器。Locator 向客户端提供元数据,以在集群成员之间均匀地平衡负载,支持单跳数据访问操作,以及其他功能。
关于定位器(Locators)的完整讨论超出了本文档的范围。建议读者阅读 Apache Geode 用户指南,以了解更多有关定位器及其在集群中作用的详细信息。
要配置并启动一个独立的 Locator 进程,请执行以下操作:
@SpringBootApplication
@LocatorApplication(port = 12345)
class LocatorApplication { ... }
你可以在集群中启动多个 Locator。唯一的要求是成员名称在集群中必须唯一。使用 name 注解的 @LocatorApplication 属性来为集群中的 Locator 成员指定相应的名称。或者,你也可以在 Spring Boot 的 spring.data.gemfire.locator.name 文件中设置 application.properties 属性。
此外,如果您在同一台机器上启动多个 Locator,必须确保每个 Locator 使用唯一的端口。请设置 port 注解属性或 spring.data.gemfire.locator.port 属性。
然后,您可以启动一个或多个 Apache Geode 对等缓存成员,这些成员通过 Locator(或多个 Locators)加入集群,并且同样使用 Spring 进行配置和引导,如下所示:
CacheServer 应用程序通过位于 localhost、端口 12345 的 Locator 加入@SpringBootApplication
@CacheServerApplication(locators = "localhost[12345]")
class ServerApplication { ... }
同样,你可以启动任意多个上面通过我们的 Locator 加入的 ServerApplication 类。
你只需确保每个成员具有唯一的名称即可。
@LocatorApplication 用于配置和启动独立的 Apache Geode Locator 应用程序进程。
该进程只能作为 Locator,不能承担其他角色。如果您尝试启动一个带有缓存实例的 Locator,SDG 将抛出错误。
如果你想在启动嵌入式 Locator 的同时启动一个缓存实例,那么你应该改用 @EnableLocator 注解。
在开发阶段,启动一个嵌入式 Locator 非常方便。然而,为了实现高可用性,强烈建议在生产环境中运行独立的 Locator 进程。如果集群中的所有 Locator 都宕机,集群本身仍能保持正常运行,但将无法有新的成员加入集群,而这对于线性扩展以满足需求至关重要。
有关更多详细信息,请参阅配置嵌入式定位器一节。
6.5. 运行时配置使用Configurers
设计基于注解的配置模型时的另一个目标是保持注解属性中的类型安全。例如,如果某个配置属性可以表示为 int(例如端口号),那么该属性的类型就应该是 int。
不幸的是,这不利于在运行时进行动态且可解析的配置。
Spring 的一个出色特性是,在 Spring 容器中配置 bean 时,能够在配置元数据的属性或属性值中使用属性占位符和 SpEL 表达式。
然而,这将要求所有注解属性都必须是 String 类型,从而放弃类型安全性,
这是不可取的。
因此,Spring Data for Apache Geode 借鉴了 Spring 中另一个常用模式,Configurers。Spring Web MVC 提供了许多不同的 Configurer 接口,包括
org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer。
Configurers 设计模式使应用程序开发人员能够在启动时接收回调,以自定义组件或 Bean 的配置。框架会在运行时回调用户提供的代码,以调整配置。该模式最常见的用途之一是根据应用程序的运行环境提供条件化配置。
Spring Data for Apache Geode 提供了多个 Configurer 回调接口,用于在运行时、在由注解创建的 Spring 管理的 Bean 初始化之前,自定义基于注解的配置元数据的不同方面:
-
CacheServerConfigurer -
ClientCacheConfigurer -
ContinuousQueryListenerContainerConfigurer -
DiskStoreConfigurer -
IndexConfigurer -
PeerCacheConfigurer -
PoolConfigurer -
RegionConfigurer -
GatewayReceiverConfigurer -
GatewaySenderConfigurer
例如,您可以分别使用 CacheServerConfigurer 和 ClientCacheConfigurer 来自定义 Spring Boot CacheServer 和 ClientCache 应用程序所使用的端口号。
考虑以下来自服务器应用程序的示例:
CacheServer 自定义 Spring Boot CacheServerConfigurer 应用程序@SpringBootApplication
@CacheServerApplication(name = "SpringServerApplication")
class ServerApplication {
@Bean
CacheServerConfigurer cacheServerPortConfigurer(
@Value("${gemfire.cache.server.host:localhost}") String cacheServerHost
@Value("${gemfire.cache.server.port:40404}") int cacheServerPort) {
return (beanName, cacheServerFactoryBean) -> {
cacheServerFactoryBean.setBindAddress(cacheServerHost);
cacheServerFactoryBean.setHostnameForClients(cacheServerHost);
cacheServerFactoryBean.setPort(cacheServerPort);
};
}
}
接下来,考虑以下来自客户端应用程序的示例:
ClientCache 自定义 Spring Boot ClientCacheConfigurer 应用程序@SpringBootApplication
@ClientCacheApplication
class ClientApplication {
@Bean
ClientCacheConfigurer clientCachePoolPortConfigurer(
@Value("${gemfire.cache.server.host:localhost}") String cacheServerHost
@Value("${gemfire.cache.server.port:40404}") int cacheServerPort) {
return (beanName, clientCacheFactoryBean) ->
clientCacheFactoryBean.setServers(Collections.singletonList(
new ConnectionEndpoint(cacheServerHost, cacheServerPort)));
}
}
通过使用所提供的Configurers,您可以在启动期间、运行时收到回调,以进一步自定义由相关注解所启用的配置。
此外,当Configurer在Spring容器中声明为一个bean时,该bean定义可以利用Spring容器的其他特性,例如属性占位符、通过在工厂方法参数上使用@Value注解来使用SpEL表达式等。
Spring Data for Apache Geode 提供的所有 Configurers 在回调中接收两部分信息:注解在 Spring 容器中所创建的 bean 的名称,以及注解用于创建和配置 Apache Geode 组件(例如,FactoryBean 实例是通过 ClientCache 创建并配置的)所使用的 ClientCacheFactoryBean 的引用。
SDG 的 FactoryBeans 属于 SDG 公共 API 的一部分,如果你没有使用这种新的基于注解的配置模型,那么在 Spring 的基于 Java 的容器配置中就会使用它们。事实上,这些注解本身在其配置过程中也是使用了相同的 FactoryBeans。因此,本质上,这些注解是一种外观(facade),提供了一层额外的抽象以方便使用。 |
鉴于Configurer可以像其他普通POJO一样声明为常规的bean定义,您可以结合使用不同的Spring配置选项,例如将Spring Profiles与同时使用属性占位符和SpEL表达式的Conditions结合起来。这些以及其他便捷的功能使您能够创建更加复杂且灵活的配置。
然而,Configurers 并不是唯一的选择。
6.6. 使用运行时配置Properties
除了 Configurers 之外,基于注解的配置模型中的每个注解属性都对应一个相应的配置属性(以 spring.data.gemfire. 为前缀),该属性可以在 Spring Boot 的 application.properties 文件中声明。
基于前面的示例,客户端的 application.properties 文件将定义以下属性集:
application.propertiesspring.data.gemfire.cache.log-level=info
spring.data.gemfire.pool.Venus.servers=venus[48484]
spring.data.gemfire.pool.Venus.max-connections=200
spring.data.gemfire.pool.Venus.min-connections=50
spring.data.gemfire.pool.Venus.ping-interval=15000
spring.data.gemfire.pool.Venus.pr-single-hop-enabled=true
spring.data.gemfire.pool.Venus.read-timeout=20000
spring.data.gemfire.pool.Venus.subscription-enabled=true
spring.data.gemfire.pool.Saturn.locators=skullbox[20668]
spring.data.gemfire.pool.Saturn.subscription-enabled=true
spring.data.gemfire.pool.Neptune.servers=saturn[41414],neptune[42424]
spring.data.gemfire.pool.Neptune.min-connections=25
对应的服务器的 application.properties 文件将定义以下属性:
application.propertiesspring.data.gemfire.cache.log-level=info
spring.data.gemfire.cache.server.port=40404
spring.data.gemfire.cache.server.Venus.port=43434
spring.data.gemfire.cache.server.Saturn.port=41414
spring.data.gemfire.cache.server.Neptune.port=41414
然后,您可以将 @ClientCacheApplication 类简化为如下形式:
@ClientCacheApplication 类@SpringBootApplication
@ClientCacheApplication
@EnablePools(pools = {
@EnablePool(name = "Venus"),
@EnablePool(name = "Saturn"),
@EnablePool(name = "Neptune")
})
class ClientApplication { .. }
此外,@CacheServerApplication 类变为如下所示:
@CacheServerApplication 类@SpringBootApplication
@CacheServerApplication(name = "SpringServerApplication")
@EnableCacheServers(servers = {
@EnableCacheServer(name = "Venus"),
@EnableCacheServer(name = "Saturn"),
@EnableCacheServer(name = "Neptune")
})
class ServerApplication { .. }
前面的示例说明了为什么为基于注解的 Bean “命名”非常重要(除了某些情况下这是必需的之外)。这样做使得可以从 XML、属性文件和 Java 代码中引用 Spring 容器中的该 Bean。甚至可以将通过注解定义的 Bean 注入到应用程序类中,以实现任何目的,如下例所示:
@Component
class MyApplicationComponent {
@Resource(name = "Saturn")
CacheServer saturnCacheServer;
...
}
同样地,为注解定义的 Bean 命名后,你可以编写一个 Configurer 来定制特定的、具有“名称”的 Bean,
因为 beanName 是传递给回调函数的两个参数之一。
通常,一个关联的注解属性会以两种形式出现:一个“命名”属性和一个“未命名”属性。
以下示例展示了这样的配置:
spring.data.gemfire.cache.server.bind-address=10.105.20.1
spring.data.gemfire.cache.server.Venus.bind-address=10.105.20.2
spring.data.gemfire.cache.server.Saturn...
spring.data.gemfire.cache.server.Neptune...
尽管上面定义了三个具名的CacheServers,但同时还存在一个未命名的CacheServer属性,用于为该属性的任何未指定值(包括“具名”的CacheServers)提供默认值。因此,虽然“Venus”设置了并覆盖了其自身的bind-address,但“Saturn”和“Neptune”则继承自未命名的spring.data.gemfire.cache.server.bind-address属性。
请参阅注解的 Javadoc,以了解哪些注解属性支持基于属性的配置,以及它们是否支持使用“命名”属性而非默认的“未命名”属性。
6.6.1. Properties of Properties
按照 Spring 框架一贯的风格,你甚至可以将 Properties 表达为其他 Properties 的形式,无论是通过
以下示例展示了一个在 application.properties 文件中设置嵌套属性的情况:
spring.data.gemfire.cache.server.port=${gemfire.cache.server.port:40404}
以下示例展示了在 Java 中设置嵌套属性:
@Bean
CacheServerConfigurer cacheServerPortConfigurer(
@Value("${gemfire.cache.server.port:${some.other.property:40404}}")
int cacheServerPort) {
...
}
| 属性占位符的嵌套可以任意深度。 |
6.7. 配置嵌入式服务
Apache Geode 提供了启动多种不同嵌入式服务的能力,这些服务根据具体用例而定,是应用程序所需的。
6.7.1. 配置嵌入式定位器
如前所述,Apache Geode 的 Locator 用于客户端连接到集群并查找其中的服务器。 此外,加入现有集群的新成员也使用 Locator 来发现其对等节点。
在开发基于 Spring Boot 和 Spring Data for Apache Geode 的应用程序时,开发者通常会发现启动一个由两到三个 Apache Geode 服务器组成的小型集群非常方便。与其单独启动一个 Locator 进程,您可以在您的 Spring Boot @CacheServerApplication 类上添加 @EnableLocator 注解,如下所示:
CacheServer 应用程序,运行嵌入式 Locator@SpringBootApplication
@CacheServerApplication
@EnableLocator
class ServerApplication { .. }
@EnableLocator 注解会在运行于 CacheServer 的 Spring Apache Geode localhost 应用程序中启动一个内嵌的 Locator,监听默认的 Locator 端口 10334。您可以使用相应的注解属性来自定义内嵌 Locator 绑定的 host(绑定地址)和 port。
或者,你也可以通过在 @EnableLocator 中设置相应的 spring.data.gemfire.locator.host 和 spring.data.gemfire.locator.port 属性来配置 application.properties 注解的属性。
然后,你可以通过以下方式连接到此 Locator,启动其他启用了 Spring Boot @CacheServerApplication 的应用程序:
CacheServer 应用程序连接到 Locator@SpringBootApplication
@CacheServerApplication(locators = "localhost[10334]")
class ServerApplication { .. }
你甚至可以将前面展示的两个应用程序类合并为一个类,并使用你的 IDE 创建不同的运行配置文件,通过 Java 系统属性来启动同一个类的不同实例,并对配置进行细微调整,如下所示:
CacheServer 应用程序@SpringBootApplication
@CacheServerApplication(locators = "localhost[10334]")
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class);
}
@EnableLocator
@Profile("embedded-locator")
static class Configuration { }
}
然后,对于每个运行配置,您可以设置和更改以下系统属性:
spring.data.gemfire.name=SpringCacheServerOne
spring.data.gemfire.cache.server.port=41414
spring.profiles.active=embedded-locator
ServerApplication 类的运行配置中,仅应有一个配置设置 Java 系统属性 -Dspring.profiles.active=embedded-locator。然后,您可以为其他每个运行配置分别修改 ..name 和 ..cache.server.port,从而在本地系统上运行一个小型的 Apache Geode 服务器集群(分布式系统)。
@EnableLocator 注解仅用于开发阶段,应用程序开发者不应在生产环境中使用该注解。我们强烈建议将 Locator 作为独立的、单独的进程在集群中运行。 |
有关 Apache Geode 定位器(Locators)工作原理的更多详细信息,可在此找到。
6.7.2. 配置嵌入式管理器
Apache Geode 管理器(Manager)是集群中的另一个对等成员或节点,负责集群的“管理”。
管理包括创建 Regions、Indexes、DiskStores 等,以及监控集群组件的运行时操作和行为。
Manager 允许启用了 JMX 的客户端(例如 Gfsh 命令行工具)连接到 Manager 以管理集群。 只要使用 JDK 提供的工具(例如 JConsole 或 JVisualVM),也可以连接到 Manager,因为这些工具同样是启用了 JMX 的客户端。
也许你还希望将前面所示的 Spring @CacheServerApplication 同时启用为 Manager。要实现这一点,
请在你的 Spring @Configuration 或 @SpringBootApplication 类上添加 @EnableManager 注解。
默认情况下,Manager 绑定到 localhost,并在默认的 Manager 端口 1099 上监听。Manager 的多个方面可以通过注解属性或相应的配置属性进行自定义。
以下示例展示了如何在 Java 中创建一个嵌入式 Manager:
CacheServer 应用程序@SpringBootApplication
@CacheServerApplication(locators = "localhost[10334]")
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class);
}
@EnableLocator
@EnableManager
@Profile("embedded-locator-manager")
static class Configuration { }
}
通过前面的类,你甚至可以使用 Gfsh 连接到这个小型集群并对其进行管理,如下所示:
$ gfsh
_________________________ __
/ _____/ ______/ ______/ /____/ /
/ / __/ /___ /_____ / _____ /
/ /__/ / ____/ _____/ / / / /
/______/_/ /______/_/ /_/ 1.2.1
Monitor and Manage {data-store-name}
gfsh>connect
Connecting to Locator at [host=localhost, port=10334] ..
Connecting to Manager at [host=10.99.199.5, port=1099] ..
Successfully connected to: [host=10.99.199.5, port=1099]
gfsh>list members
Name | Id
---------------------- | ----------------------------------------------------
SpringCacheServerOne | 10.99.199.5(SpringCacheServerOne:14842)<ec><v0>:1024
SpringCacheServerTwo | 10.99.199.5(SpringCacheServerTwo:14844)<v1>:1025
SpringCacheServerThree | 10.99.199.5(SpringCacheServerThree:14846)<v2>:1026
由于我们还启用了嵌入式的 Locator,因此可以通过 Locator 间接连接到 Manager。 Locator 允许 JMX 客户端连接并查找集群中的 Manager。如果不存在 Manager,Locator 将承担 Manager 的角色。 然而,如果没有 Locator 存在,我们就需要使用以下方式直接连接到 Manager:
connect 命令直接连接到 Managergfsh>connect --jmx-manager=localhost[1099]
与 @EnableLocator 注解类似,@EnableManager 注解也仅用于开发阶段,
应用程序开发者不应在生产环境中使用该注解。我们强烈建议将 Manager(管理器)与 Locator(定位器)一样,
作为集群中独立、专用的进程运行。 |
有关 Apache Geode 管理和监控的更多详细信息,请参见此处。
6.7.3. 配置嵌入式 HTTP 服务器
Apache Geode 还能够运行一个嵌入式 HTTP 服务器。当前的实现基于 Eclipse Jetty。
嵌入式 HTTP 服务器用于托管 Apache Geode 的管理(Admin)REST API(非公开宣传的 API)、 开发者 REST API, 以及 Pulse 监控 Web 应用程序。
然而,要使用这些由 Apache Geode 提供的任何 Web 应用程序,您必须在系统上安装完整的 Apache Geode,并且必须将 GEODE_HOME 环境变量设置为您的安装目录。
要启用嵌入式 HTTP 服务器,请在任意使用 @EnableHttpService 或 @PeerCacheApplication 注解的类上添加 @CacheServerApplication 注解,如下所示:
CacheServer 应用程序@SpringBootApplication
@CacheServerApplication
@EnableHttpService
public class ServerApplication { .. }
默认情况下,内嵌的 HTTP 服务器会在端口 7070 上监听 HTTP 客户端请求。当然,你可以使用注解属性或相应的配置属性根据需要调整该端口。
有关 HTTP 支持及所提供的服务的更多详细信息,请参阅前面的链接。
6.7.4. 配置嵌入式 Memcached 服务器 (Gemcached)
Apache Geode 还实现了 Memcached 协议,能够为 Memcached 客户端提供服务。也就是说,Memcached 客户端可以连接到 Apache Geode 集群,并执行 Memcached 操作,就好像该集群中的 Apache Geode 服务器是真正的 Memcached 服务器一样。
要启用嵌入式 Memcached 服务,请在任意使用 @EnableMemcachedServer 或 @PeerCacheApplication 注解的类上添加 @CacheServerApplication 注解,如下所示:
CacheServer 应用程序@SpringBootApplication
@CacheServerApplication
@EnabledMemcachedServer
public class ServerApplication { .. }
有关 Apache Geode 的 Memcached 服务(称为“Gemcached”)的更多详细信息,请参见此处。
6.7.5. 配置嵌入式 Redis 服务器
Apache Geode 还实现了 Redis 服务器协议,使 Redis 客户端能够连接到 Apache Geode 服务器集群并与其通信,以执行 Redis 命令。截至本文撰写时,Apache Geode 中对 Redis 服务器协议的支持仍处于实验阶段。
要启用嵌入式 Redis 服务,请在任意使用 @EnableRedisServer 或 @PeerCacheApplication 注解的类上添加 @CacheServerApplication 注解,如下所示:
CacheServer 应用程序@SpringBootApplication
@CacheServerApplication
@EnableRedisServer
public class ServerApplication { .. }
您必须在 Spring [Boot] 应用程序的 classpath 中显式声明 org.apache.geode:geode-redis 模块。 |
有关 Apache Geode 的 Redis 适配器的更多详细信息,请点击此处。
6.8. 配置日志
通常,需要提高日志级别,以便准确了解 Apache Geode 正在执行的操作及其执行时机。
要启用日志记录,请在您的应用程序类上添加 @EnableLogging 注解,并设置相应的属性或关联的配置项,如下所示:
ClientCache 应用程序@SpringBootApplication
@ClientCacheApplication
@EnableLogging(logLevel="info", logFile="/absolute/file/system/path/to/application.log)
public class ClientApplication { .. }
虽然 logLevel 属性可以与所有基于缓存的应用程序注解一起使用(例如,@ClientCacheApplication(logLevel="info")),但使用 @EnableLogging 注解来定制日志行为更为简便。
此外,您还可以通过在 log-level 中设置 spring.data.gemfire.logging.level 属性来配置 application.properties。
查看 @EnableLogging 注解的 Javadoc
以获取更多详细信息。
6.9. 配置统计信息
为了在运行时更深入地了解 Apache Geode,您可以启用统计信息。收集统计数据有助于在发生复杂问题(这些问题通常具有分布式特性,且时间因素至关重要)时进行系统分析和故障排查。
启用统计信息后,您可以使用 Apache Geode 的 VSD(可视化统计数据显示) 工具 来分析所收集的统计数据。
要启用统计功能,请使用 @EnableStatistics 注解您的应用程序类,如下所示:
ClientCache 应用程序@SpringBootApplication
@ClientCacheApplication
@EnableStatistics
public class ClientApplication { .. }
在服务器上启用统计信息对于评估性能特别有价值。为此,请使用 @PeerCacheApplication 注解您的 @CacheServerApplication 或 @EnableStatistics 类。
您可以使用 @EnableStatistics 注解的属性或相关配置属性来自定义统计信息的收集和汇总过程。
查看 @EnableStatistics 注解的 Javadoc
以获取更多详细信息。
有关 Apache Geode 统计信息的更多详细信息,请点击此处。
6.10. 配置 PDX
Apache Geode 的一个更强大的特性是 PDX 序列化。 虽然本文档不涉及对 PDX 的完整讨论,但使用 PDX 进行序列化相比 Java 序列化是一个好得多的替代方案,具有以下优势:
-
PDX 使用一个集中式的类型注册表,以使对象的序列化字节更加紧凑。
-
PDX 是一种中立的序列化格式,允许 Java 客户端和原生客户端对同一数据集进行操作。
-
PDX 支持版本控制,允许在不影响现有应用程序的情况下添加或删除对象字段,无论这些应用程序使用的是已更改的 PDX 序列化对象的旧版本还是新版本,都不会造成数据丢失。
-
PDX 允许在 OQL 查询的投影和谓词中单独访问对象字段,而无需先对对象进行反序列化。
通常,在 Apache Geode 中,只要数据在客户端与服务器之间传输,或在集群中的对等节点之间进行正常的分发和复制操作时,以及数据溢出到磁盘或持久化到磁盘时,都需要进行序列化。
启用 PDX 序列化比修改所有应用程序领域对象类型以实现 java.io.Serializable 要简单得多,尤其是在不希望对应用程序领域模型施加此类限制,或者您无法控制所要序列化的对象时更是如此——这种情况在使用第三方库时尤为常见(例如,考虑一个包含 Coordinate 类型的地理空间 API)。
要启用 PDX,请在您的应用程序类上添加 @EnablePdx 注解,如下所示:
ClientCache 应用程序@SpringBootApplication
@ClientCacheApplication
@EnablePdx
public class ClientApplication { .. }
通常,应用程序的领域对象类型要么实现了
org.apache.geode.pdx.PdxSerializable
接口,要么您可以实现并注册一个非侵入式的
org.apache.geode.pdx.PdxSerializer
接口实现,以处理所有需要序列化的应用程序领域对象类型。
不幸的是,Apache Geode 只允许注册一个 PdxSerializer,这意味着所有应用程序领域对象类型都必须由单个 PdxSerializer 实例来处理。然而,这是一种严重的反模式,也是难以维护的做法。
尽管只能向 Apache Geode 注册一个 PdxSerializer 实例,但为每个应用程序领域对象类型创建一个单独的 PdxSerializer 实现是有意义的。
通过使用组合软件设计模式,您可以提供一个PdxSerializer接口的实现,该实现聚合了所有应用程序领域对象类型特定的PdxSerializer实例,但对外表现为单个PdxSerializer实例,并可将其注册。
你可以在 Spring 容器中将此组合的 PdxSerializer 声明为一个托管 Bean,并在 PdxSerializer 注解中通过 @EnablePdx 属性引用该组合 serializerBeanName 的 Bean 名称。Spring Data for Apache Geode 会代你将其注册到 Apache Geode 中。
以下示例展示了如何创建一个自定义的复合 PdxSerializer:
ClientCache 应用程序,使用自定义的复合 PdxSerializer@SpringBootApplication
@ClientCacheApplication
@EnablePdx(serializerBeanName = "compositePdxSerializer")
public class ClientApplication {
@Bean
PdxSerializer compositePdxSerializer() {
return new CompositePdxSerializerBuilder()...
}
}
也可以在 Spring 上下文中将 Apache Geode 的
org.apache.geode.pdx.ReflectionBasedAutoSerializer
声明为 bean 定义。
或者,您应该使用 Spring Data for Apache Geode 更健壮的
org.springframework.data.gemfire.mapping.MappingPdxSerializer,
它利用 Spring Data 映射元数据和基础设施应用于序列化过程,比单独使用反射实现更高效的处理。
PDX 的许多其他方面和功能可以通过 @EnablePdx 注解的属性或相关的配置属性进行调整。
查看 @EnablePdx 注解的 Javadoc
以获取更多详细信息。
6.11. 配置 Apache Geode 属性
尽管许多 gemfire.properties 已通过 Spring Data GemFire(SDG)基于注解的配置模型被方便地封装并抽象到注解中,但那些较少使用的 Apache Geode 属性仍然可以通过 @EnableGemFireProperties 注解进行访问。
在您的应用程序类上使用 @EnableGemFireProperties 注解非常方便,是创建 gemfire.properties 文件或在启动应用程序时通过命令行将 Apache Geode 属性设置为 Java 系统属性的一个很好的替代方案。
我们建议在将应用程序部署到生产环境时,将这些 Apache Geode 属性设置在 gemfire.properties 文件中。然而,在开发阶段,为了便于原型设计、调试和测试,可以按需单独设置这些属性。 |
一些不太常见的 Apache Geode 属性示例(通常无需担心,但不限于以下这些)包括:ack-wait-threshold、disable-tcp、socket-buffer-size 等。
要单独设置任意 Apache Geode 属性,请在您的应用程序类上添加 @EnableGemFireProperties 注解,
并通过相应的属性设置您希望更改的 Apache Geode 属性(这些属性将覆盖 Apache Geode 默认设置的值),如下所示:
ClientCache 应用程序@SpringBootApplication
@ClientCacheApplication
@EnableGemFireProperties(conflateEvents = true, socketBufferSize = 16384)
public class ClientApplication { .. }
请注意,某些 Apache Geode 属性是客户端专用的(例如 conflateEvents),
而其他一些则是服务器端专用的(例如 distributedSystemId、enableNetworkPartitionDetection、
enforceUniqueHost、memberTimeout、redundancyZone 等)。
有关 Apache Geode 属性的更多详细信息,请参见此处。
6.12. 配置区域
到目前为止,除了PDX之外,我们的讨论主要集中在配置Apache Geode的更多管理功能上:
创建缓存实例、启动嵌入式服务、启用日志记录和统计信息、配置PDX,以及使用
gemfire.properties 来影响底层配置和行为。尽管所有这些配置选项都很重要,
但它们都与您的应用程序没有直接关系。换句话说,我们仍然需要一个地方来存储应用程序数据,
并使其普遍可用和可访问。
Apache Geode 将缓存中的数据组织成区域(Regions)。 你可以将一个区域(Region)看作关系型数据库中的一张表。通常,一个区域应仅存储单一类型的对象, 这样更有利于构建高效的索引和编写查询语句。我们将在后面介绍索引相关内容。
此前,Apache Geode 的 Spring Data 用户需要显式地定义和声明其应用程序用于存储数据的 Region,
无论使用 SDG 的 FactoryBeans API 配合 Spring 的基于 Java 的容器配置,
还是使用XML,都需要编写非常冗长的 Spring 配置元数据。
以下示例演示了如何在 Java 中配置一个 Region Bean:
@Configuration
class GemFireConfiguration {
@Bean("Example")
PartitionedRegionFactoryBean exampleRegion(GemFireCache gemfireCache) {
PartitionedRegionFactoryBean<Long, Example> exampleRegion =
new PartitionedRegionFactoryBean<>();
exampleRegion.setCache(gemfireCache);
exampleRegion.setClose(false);
exampleRegion.setPersistent(true);
return exampleRegion;
}
...
}
以下示例演示了如何在 XML 中配置相同的 Region Bean:
<gfe:partitioned-region id="exampleRegion" name="Example" persistent="true">
...
</gfe:partitioned-region>
尽管 Java 配置和 XML 配置都不算特别复杂,但无论采用哪一种都可能显得繁琐,尤其是当一个应用程序需要大量 Region 时。许多基于关系数据库的应用程序可能拥有数百甚至数千张表。
手动定义和声明所有这些 Region 既繁琐又容易出错。现在,有更好的方法了。
现在,您可以直接基于应用程序的领域对象(实体)本身来定义和配置 Region。除非您需要更细粒度的控制,否则不再需要在 Spring 配置元数据中显式地定义 Region bean。
为了简化 Region 的创建,Apache Geode 的 Spring Data 将 Spring Data Repository 的使用与基于注解的配置表达能力相结合,采用了新的 @EnableEntityDefinedRegions 注解。
| 大多数 Spring Data 应用程序开发者应该已经熟悉了 Spring Data 仓库抽象 以及 Spring Data for Apache Geode 的实现/扩展, 后者经过专门定制,以优化 Apache Geode 的数据访问操作。 |
首先,应用程序开发人员从定义应用程序的领域对象(实体)开始,如下所示:
@Region("Books")
class Book {
@Id
private ISBN isbn;
private Author author;
private Category category;
private LocalDate releaseDate;
private Publisher publisher;
private String title;
}
接下来,通过扩展 Spring Data Commons 的 Books 接口,为 org.springframework.data.repository.CrudRepository 定义一个基本的仓库,如下所示:
interface BookRepository extends CrudRepository<Book, ISBN> { .. }
org.springframe.data.repository.CrudRepository 是一个数据访问对象(DAO),提供基本的数据访问操作(CRUD),并支持简单查询(例如 findById(..))。您可以通过在仓库接口上声明查询方法来定义更复杂、更高级的查询(例如,List<BooK> findByAuthor(Author author);)。
在底层,当 Spring 容器启动时,Apache Geode 的 Spring Data 会为您的应用程序仓库接口提供一个实现。只要您遵循约定,SDG 甚至还会为您定义的查询方法提供实现。
现在,当你定义 Book 类时,还通过在实体类型上声明 Apache Geode 的 Spring Data 映射注解 Book,指定了 @Region 实例所映射(存储)的 Region。当然,如果仓库接口(本例中为 Book)的类型参数所引用的实体类型(本例中为 BookRepository)未使用 @Region 注解,则 Region 名称将从该实体类型的简单类名(本例中同样为 Book)派生而来。
Spring Data for Apache Geode 使用映射上下文(其中包含应用程序中定义的所有实体的映射元数据)来确定运行时所需的所有 Region。
要启用并使用此功能,请在应用程序类上添加 @EnableEntityDefinedRegions 注解,如下所示:
@SpringBootApplication
@ClientCacheApplication
@EnableEntityDefinedRegions(basePackages = "example.app.domain")
@EnableGemfireRepositories(basePackages = "example.app.repo")
class ClientApplication { .. }
从实体类创建 Region 在应用程序中使用 Spring Data 仓库时最为有用。
如前例所示,Apache Geode 的 Spring Data 仓库支持通过 @EnableGemfireRepositories 注解启用。 |
目前,只有显式使用 @Region 注解标注的实体类才会被扫描到,并创建对应的 Region。如果某个实体类未显式使用 @Region 进行映射,则不会为其创建 Region。 |
默认情况下,@EnableEntityDefinedRegions 注解会从声明该注解的配置类所在包开始,递归地扫描实体类。
然而,通常的做法是通过设置 basePackages 属性,将其值指定为包含应用程序实体类的包名,从而在扫描过程中限制搜索范围。
或者,您可以使用类型更安全的 basePackageClasses 属性来指定要扫描的包,
方法是将该属性设置为包含实体类的包中的某个实体类型,
或者使用一个专门创建的非实体占位符类来标识要扫描的包。
以下示例展示了如何指定要扫描的实体类型:
@SpringBootApplication
@ClientCacheApplication
@EnableGemfireRepositories
@EnableEntityDefinedRegions(basePackageClasses = {
example.app.books.domain.Book.class,
example.app.customers.domain.Customer.class
})
class ClientApplication { .. }
除了指定扫描的起始位置(类似于 Spring 的 @ComponentScan 注解)之外,您还可以指定 include 和 exclude 过滤器,其语义与 org.springframework.context.annotation.ComponentScan.Filter 注解完全相同。
查看 @EnableEntityDefinedRegions 注解的 Javadoc
以获取更多详细信息。
6.12.1. 配置特定类型的区域
Apache Geode 支持多种不同的 区域类型。
每种类型对应区域的 DataPolicy,
该属性决定了区域中的数据将如何被管理(即分布式、复制等)。
其他配置设置(例如 Region 的 scope)也会影响数据的管理方式。
有关更多详细信息,请参阅 Apache Geode 用户指南中的“存储和分发选项”。 |
当你使用通用的 @Region 映射注解标注你的应用程序领域对象类型时,Spring Data for Apache Geode 会决定创建哪种类型的 Region。SDG 的默认策略在确定要创建的 Region 类型时会考虑缓存类型。
例如,如果您使用 @ClientCacheApplication 注解将应用程序声明为 ClientCache,
SDG 默认会创建一个客户端 PROXY Region。或者,如果您使用 @PeerCacheApplication 或 @CacheServerApplication 注解将应用程序声明为对等方 Cache,
SDG 默认会创建一个服务器 PARTITION Region。
当然,在必要时你始终可以覆盖默认设置。为了覆盖 Spring Data for Apache Geode 所应用的默认配置, 引入了四个新的 Region 映射注解:
-
@ClientRegion -
@LocalRegion -
@PartitionRegion -
@ReplicateRegion
@ClientRegion 映射注解专用于客户端应用程序。上述列出的所有其他 Region 映射注解只能用于具有嵌入式对等 Cache 的服务器应用程序。
有时,客户端应用程序需要创建并使用仅限本地的 Region,例如用于聚合来自其他 Region 的数据,以便在本地分析这些数据,并代表用户执行应用程序的某些功能。在这种情况下,除非其他应用程序需要访问分析结果,否则这些数据无需分发回服务器。该 Region 甚至可以是临时的,在使用后即被丢弃,这可以通过在 Region 本身上设置空闲超时(Idle-Timeout,TTI)和生存时间(Time-To-Live,TTL)过期策略来实现。(有关过期策略的更多内容,请参见“配置过期策略”。)
| 区域级的空闲超时(TTI)和生存时间(TTL)过期策略与条目级的TTI和TTL过期策略相互独立且有所不同。 |
在任何情况下,如果你想要创建一个仅限本地的客户端 Region,并且该 Region 中的数据不会被分发回服务器上同名的对应 Region,你可以声明 @ClientRegion 映射注解,并将 shortcut 属性设置为 ClientRegionShortcut.LOCAL,如下所示:
ClientCache 应用程序@ClientRegion(shortcut = ClientRegionShortcut.LOCAL)
class ClientLocalEntityType { .. }
所有 Region 类型特定的注解都提供了一些额外属性,这些属性既包括各种 Region 类型共有的,也包括仅适用于该特定 Region 类型的。例如,collocatedWith 注解中的 redundantCopies 和 PartitionRegion 属性仅适用于服务端的 PARTITION 类型 Region。
有关 Apache Geode Region 类型的更多详细信息,请点击此处。
6.12.2. 配置的集群定义区域
除了 @EnableEntityDefinedRegions 注解之外,Spring Data for Apache Geode 还提供了其反向注解
@EnableClusterDefinedRegions。通常情况下,您会根据应用程序用例(UC)和需求中定义的实体类来创建 Region(这是最常见且合乎逻辑的做法);但您也可以选择从集群中已定义的 Region 来声明您的 Region,这些 Region 将由您的 ClientCache 应用程序连接到该集群。
这使您可以使用服务器集群作为数据定义的主要来源,集中管理配置,并确保集群中的所有客户端应用程序都具有一致的配置。这在云托管环境中特别有用,例如当需要快速扩展大量相同客户端应用程序实例以应对增加的负载时。
其理念是,与其由客户端应用程序驱动数据字典,不如由用户使用 Apache Geode 的 Gfsh CLI 命令行工具来定义 Region。这样做还有一个额外的优势:当向集群中添加新的对等节点时,这些节点也会自动拥有并共享相同的配置,因为该配置会被 Apache Geode 的 集群配置服务(Cluster Configuration Service) 记住。
例如,用户可以在 Gfsh 中定义一个 Region,如下所示:
gfsh>create region --name=Books --type=PARTITION
Member | Status
--------- | --------------------------------------
ServerOne | Region "/Books" created on "ServerOne"
ServerTwo | Region "/Books" created on "ServerTwo"
gfsh>list regions
List of regions
---------------
Books
gfsh>describe region --name=/Books
..........................................................
Name : Books
Data Policy : partition
Hosting Members : ServerTwo
ServerOne
Non-Default Attributes Shared By Hosting Members
Type | Name | Value
------ | ----------- | ---------
Region | size | 0
| data-policy | PARTITION
借助 Apache Geode 的集群配置服务,为应对(后端)负载增加而添加到服务器集群中的任何额外对等成员都将拥有相同的配置,例如:
gfsh>list members
Name | Id
--------- | ----------------------------------------------
Locator | 10.0.0.121(Locator:68173:locator)<ec><v0>:1024
ServerOne | 10.0.0.121(ServerOne:68242)<v3>:1025
ServerTwo | 10.0.0.121(ServerTwo:68372)<v4>:1026
gfsh>start server --name=ServerThree --log-level=config --server-port=41414
Starting a Geode Server in /Users/you/geode/cluster/ServerThree...
...
Server in /Users/you/geode/cluster/ServerThree... on 10.0.0.121[41414] as ServerThree is currently online.
Process ID: 68467
Uptime: 3 seconds
Geode Version: 1.2.1
Java Version: 1.8.0_152
Log File: /Users/you/geode/cluster/ServerThree/ServerThree.log
JVM Arguments: -Dgemfire.default.locators=10.0.0.121[10334]
-Dgemfire.use-cluster-configuration=true
-Dgemfire.start-dev-rest-api=false
-Dgemfire.log-level=config
-XX:OnOutOfMemoryError=kill -KILL %p
-Dgemfire.launcher.registerSignalHandlers=true
-Djava.awt.headless=true
-Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /Users/you/geode/cluster/apache-geode-1.2.1/lib/geode-core-1.2.1.jar
:/Users/you/geode/cluster/apache-geode-1.2.1/lib/geode-dependencies.jar
gfsh>list members
Name | Id
----------- | ----------------------------------------------
Locator | 10.0.0.121(Locator:68173:locator)<ec><v0>:1024
ServerOne | 10.0.0.121(ServerOne:68242)<v3>:1025
ServerTwo | 10.0.0.121(ServerTwo:68372)<v4>:1026
ServerThree | 10.0.0.121(ServerThree:68467)<v5>:1027
gfsh>describe member --name=ServerThree
Name : ServerThree
Id : 10.0.0.121(ServerThree:68467)<v5>:1027
Host : 10.0.0.121
Regions : Books
PID : 68467
Groups :
Used Heap : 37M
Max Heap : 3641M
Working Dir : /Users/you/geode/cluster/ServerThree
Log file : /Users/you/geode/cluster/ServerThree/ServerThree.log
Locators : 10.0.0.121[10334]
Cache Server Information
Server Bind :
Server Port : 41414
Running : true
Client Connections : 0
如你所见,“ServerThree”现在拥有了“Books”区域。如果其中任意一台或所有服务器宕机,当它们重新启动时,将具有相同的配置以及“Books”区域。
在客户端,可能会启动多个书店客户端应用程序实例,以针对书店在线服务处理图书。“Books”区域可能是实现书店应用程序服务所需的众多不同区域之一。与其逐个创建和配置每个区域,SDG 提供了一种便捷的方式,允许客户端应用程序的区域直接从集群中定义,如下所示:
@EnableClusterDefinedRegions 从集群定义客户端区域@ClientCacheApplication
@EnableClusterDefinedRegions
class BookStoreClientApplication {
public static void main(String[] args) {
....
}
...
}
@EnableClusterDefinedRegions 只能在客户端使用。 |
您可以使用 clientRegionShortcut 注解属性来控制客户端创建的 Region 类型。
默认情况下,将创建一个客户端 PROXY Region。将 clientRegionShortcut 设置为 ClientRegionShortcut.CACHING_PROXY
以实现“近缓存”。此设置适用于从集群定义的 Region 创建的所有客户端 Region。
如果您想控制从集群上定义的 Region 创建的客户端 Region 的个别设置(如数据策略),
则可以基于 Region 名称实现自定义逻辑的
RegionConfigurer。 |
然后,在您的应用程序中使用“Books”区域就变得非常简单了。您可以直接注入“Books”区域,如下所示:
@org.springframework.stereotype.Repository
class BooksDataAccessObject {
@Resource(name = "Books")
private Region<ISBN, Book> books;
// implement CRUD and queries with the "Books" Region
}
或者,甚至可以基于应用程序领域类型(实体)Book定义一个 Spring Data Repository,该类型映射到“Books”区域,如下所示:
interface BookRepository extends CrudRepository<Book, ISBN> {
...
}
然后,您可以将自定义的 BooksDataAccessObject 或 BookRepository 注入到您的应用服务组件中,以执行所需的任何业务功能。
6.12.3. 配置驱逐
使用 Apache Geode 管理数据是一项需要持续关注的任务。通常需要进行调优,并且必须结合使用多种特性(例如,同时使用驱逐(eviction)和过期(expiration))才能在内存中有效地管理您的数据。
鉴于 Apache Geode 是一个内存数据网格(IMDG),数据在内存中进行管理,并分发到集群中的其他节点,以尽可能降低延迟、最大化吞吐量,并确保数据的高可用性。 由于应用程序的数据通常无法全部放入内存中(即使在整个节点集群中也难以容纳,更不用说单个节点了),您可以通过向集群添加新节点来提升容量。这通常被称为线性横向扩展(scale-out),而不是纵向扩展(scale-up)——后者指的是通过增加更多内存、CPU、磁盘或网络带宽(即增加各类系统资源)来应对负载。
然而,即使使用节点集群,通常也必须仅将最重要的数据保留在内存中。
耗尽内存,甚至接近内存满载,几乎从来都不是一件好事。停止世界(Stop-the-world)的垃圾回收(GC),
或者更糟的情况——OutOfMemoryErrors,都会导致您的应用程序彻底崩溃。
因此,为了帮助管理内存并保留最重要的数据,Apache Geode 支持最近最少使用(LRU)驱逐策略。也就是说,Apache Geode 会根据最近最少使用(Least Recently Used)算法,按照 Region 条目最后一次被访问的时间来驱逐这些条目。
要启用驱逐功能,请在应用程序类上添加 @EnableEviction 注解,如下所示:
@SpringBootApplication
@PeerCacheApplication
@EnableEviction(policies = {
@EvictionPolicy(regionNames = "Books", action = EvictionActionType.INVALIDATE),
@EvictionPolicy(regionNames = { "Customers", "Orders" }, maximum = 90,
action = EvictionActionType.OVERFLOW_TO_DISK,
type = EvictonPolicyType.HEAP_PERCENTAGE)
})
class ServerApplication { .. }
驱逐策略通常在服务器上的区域(Regions)中设置。
如前所示,policies 属性可以指定一个或多个嵌套的 @EvictionPolicy 注解,每个注解可单独应用于一个或多个需要应用驱逐策略的区域(Region)。
此外,您可以引用 Apache Geode 的自定义实现
org.apache.geode.cache.util.ObjectSizer 接口,
该接口可以定义为 Spring 容器中的 Bean,并通过 objectSizerName 属性按名称进行引用。
ObjectSizer 允许您定义用于评估和确定存储在 Region 中对象大小的标准。
查看 @EnableEviction 注解的 Javadoc
以获取完整的缓存淘汰配置选项列表。
有关 Apache Geode 逐出机制的更多详细信息,请参见此处。
6.12.4. 配置过期时间
除了驱逐之外,还可以使用过期机制来管理内存,即允许存储在 Region 中的条目过期。Apache Geode 支持两种条目过期策略:生存时间(TTL)和空闲超时(TTI)。
Spring Data for Apache Geode 基于注解的过期配置建立在 早期已有的条目过期注解支持 之上,该支持最初是在 Spring Data for Apache Geode 1.5 版本中引入的。
本质上,Spring Data for Apache Geode 的过期注解支持基于对 Apache Geode 的
org.apache.geode.cache.CustomExpiry 接口的自定义实现。
此 o.a.g.cache.CustomExpiry 实现会检查存储在 Region 中的用户应用程序领域对象,以查找是否存在类型级别的过期注解。
Spring Data for Apache Geode 提供了以下过期注解:
-
Expiration -
IdleTimeoutExpiration -
TimeToLiveExpiration
应用程序域对象类型可以使用一个或多个过期注解进行标注,如下所示:
@Region("Books")
@TimeToLiveExpiration(timeout = 30000, action = "INVALIDATE")
class Book { .. }
要启用过期功能,请在应用程序类上添加 @EnableExpiration 注解,如下所示:
@SpringBootApplication
@PeerCacheApplication
@EnableExpiration
class ServerApplication { .. }
除了应用程序域对象类型的过期策略外,您还可以使用 @EnableExpiration 注解,按 Region 逐个直接单独配置过期策略,如下所示:
@SpringBootApplication
@PeerCacheApplication
@EnableExpiration(policies = {
@ExpirationPolicy(regionNames = "Books", types = ExpirationType.TIME_TO_LIVE),
@ExpirationPolicy(regionNames = { "Customers", "Orders" }, timeout = 30000,
action = ExpirationActionType.LOCAL_DESTROY)
})
class ServerApplication { .. }
前面的示例为 Books、Customers 和 Orders 区域设置了过期策略。
过期策略通常在服务器上的 Region 上设置。
查看 @EnableExpiration 注解的 Javadoc
以获取完整的过期配置选项列表。
有关 Apache Geode 过期机制的更多详细信息,请点击此处。
6.12.5. 配置压缩
Apache Geode 允许您通过使用可插拔的
Compressors(即不同的压缩编解码器)来压缩内存中的 Region 值。
Apache Geode 默认使用 Google 的 Snappy 压缩库。
要启用压缩功能,请在应用程序类上添加 @EnableCompression 注解,如下所示:
@SpringBootApplication
@ClientCacheApplication
@EnableCompression(compressorBeanName = "MyCompressor", regionNames = { "Customers", "Orders" })
class ClientApplication { .. }
compressorBeanName 属性和 regionNames 属性都不是必需的。 |
compressorBeanName 的默认值为 SnappyCompressor,从而启用 Apache Geode 的
SnappyCompressor。
regionNames 属性是一个区域名称数组,用于指定启用了压缩的区域。
默认情况下,如果未显式设置 regionNames 属性,则所有区域都会对值进行压缩。
或者,你可以在 spring.data.gemfire.cache.compression.compressor-bean-name 文件中使用 spring.data.gemfire.cache.compression.region-names 和 application.properties 属性来设置和配置这些 @EnableCompression 注解属性的值。 |
要使用 Apache Geode 的 Region 压缩功能,您必须在应用程序的 pom.xml 文件(针对 Maven)或 build.gradle 文件(针对 Gradle)中包含 org.iq80.snappy:snappy 依赖项。仅当您使用 Apache Geode 默认的 Region 压缩支持时才需要这样做,该支持默认使用 SnappyCompressor。
当然,如果您使用其他压缩库,则需要在应用程序的类路径中包含该压缩库的依赖项。此外,您需要实现 Apache Geode 的 Compressor 接口以适配您选择的压缩库,将其在 Spring 压缩器中定义为 Bean,并将 compressorBeanName 设置为此自定义 Bean 定义。 |
查看 @EnableCompression 注解的 Javadoc
以获取更多详细信息。
有关 Apache Geode 压缩的更多详细信息,请点击此处。
6.12.6. 配置堆外内存
另一种有效减轻 JVM 堆内存压力并最小化垃圾回收(GC)活动的方法是使用 Apache Geode 的堆外内存支持。
与将 Region 条目存储在 JVM 堆上不同,这些条目被存储在系统的主内存中。根据 Apache Geode 用户指南中的说明,堆外内存通常在以下情况下效果最佳:所存储的对象大小较为统一,大多数小于 128KB,并且不需要频繁反序列化。
要启用堆外内存,请在应用程序类上添加 @EnableOffHeap 注解,如下所示:
@SpringBootApplication
@PeerCacheApplication
@EnableOffHeap(memorySize = 8192m regionNames = { "Customers", "Orders" })
class ServerApplication { .. }
memorySize 属性是必需的。memorySize 属性的值用于指定一个 Region 可使用的主内存量,单位可以是兆字节(m)或千兆字节(g)。
regionNames 属性是一个区域名称数组,用于指定将条目存储在主内存中的区域。
默认情况下,如果未显式设置 regionNames 属性,则所有区域都会使用主内存。
或者,你可以在 spring.data.gemfire.cache.off-heap.memory-size 文件中使用 spring.data.gemfire.cache.off-heap.region-names 和 application.properties 属性来设置和配置这些 @EnableOffHeap 注解属性的值。 |
查看 @EnableOffHeap 注解的 Javadoc
以获取更多详细信息。
6.12.7. 配置磁盘存储
或者,您可以将 Region 配置为将数据持久化到磁盘。您还可以配置 Region,在 Region 条目被逐出时将数据溢出(overflow)到磁盘。在这两种情况下,都需要一个 DiskStore 来实现数据的持久化和/或溢出。当未为具有持久化或溢出功能的 Region 显式配置 DiskStore 时,Apache Geode 会使用 DEFAULT DiskStore。
我们建议在将数据持久化到磁盘和/或溢出到磁盘时,定义特定于 Region 的 DiskStores。
Spring Data for Apache Geode 提供了注解支持,通过在应用程序类上使用 DiskStores 和 @EnableDiskStore 注解来定义和创建应用程序 Region 的 @EnableDiskStores。
@EnableDiskStores 是一个组合注解,用于聚合一个或多个 @EnableDiskStore 注解。 |
例如,Book(图书)信息可能主要由来自某些外部数据源(如亚马逊)的参考数据组成,而Order(订单)数据则很可能是事务性的,应用程序需要将其保留下来(如果事务量足够大,甚至可能需要溢出到磁盘)——当然,任何图书出版商和作者都会如此期望。
使用 @EnableDiskStore 注解,您可以按如下方式定义并创建一个 DiskStore:
DiskStore 的 Spring 应用程序@SpringBootApplication
@PeerCacheApplication
@EnableDiskStore(name = "OrdersDiskStore", autoCompact = true, compactionThreshold = 70,
maxOplogSize = 512, diskDirectories = @DiskDiretory(location = "/absolute/path/to/order/disk/files"))
class ServerApplication { .. }
同样,可以通过使用组合注解 DiskStore 来定义多个 @EnableDiskStores。
与 Apache Geode 的 Spring Data 注解配置模型中的其他注解一样,@EnableDiskStore 和 @EnableDiskStores 都具有许多属性以及相关的配置属性,用于自定义运行时创建的 DiskStores。
此外,@EnableDiskStores 注解定义了某些通用的 DiskStore 属性,这些属性适用于所有通过与 DiskStores 注解组合使用的 @EnableDiskStore 注解所创建的 @EnableDiskStores。
各个 DiskStore 的配置可以覆盖特定的全局设置,但 @EnableDiskStores 注解能够方便地定义适用于该注解所聚合的所有 DiskStores 的通用配置属性。
Spring Data for Apache Geode 还提供了 DiskStoreConfigurer 回调接口,该接口可在 Java 配置中声明,并用于在运行时自定义 DiskStore,从而替代配置属性,如下例所示:
@SpringBootApplication
@PeerCacheApplication
@EnableDiskStore(name = "OrdersDiskStore", autoCompact = true, compactionThreshold = 70,
maxOplogSize = 512, diskDirectories = @DiskDiretory(location = "/absolute/path/to/order/disk/files"))
class ServerApplication {
@Bean
DiskStoreConfigurer ordersDiskStoreDiretoryConfigurer(
@Value("${orders.disk.store.location}") String location) {
return (beanName, diskStoreFactoryBean) -> {
if ("OrdersDiskStore".equals(beanName) {
diskStoreFactoryBean.setDiskDirs(Collections.singletonList(new DiskDir(location));
}
}
}
}
查看 @EnableDiskStore 和 @EnableDiskStores 注解的 Javadoc,以获取更多关于可用属性及相关配置属性的详细信息。
有关 Apache Geode Region 持久化和溢出(使用 DiskStore)的更多详细信息, 请参见此处。
6.12.8. 配置索引
除非能够访问数据,否则将数据存储在区域(Regions)中并没有太大用处。
除了使用 Region.get(key) 操作(尤其是在键已预先知道的情况下)之外,通常还会通过对包含数据的 Region 执行查询来检索数据。在 Apache Geode 中,查询使用对象查询语言(OQL)编写,客户端希望访问的特定数据集则通过查询的谓词来表达(例如,SELECT * FROM /Books b WHERE b.author.name = 'Jon Doe')。
通常,不使用索引进行查询效率较低。在执行无索引的查询时,Apache Geode 会执行相当于全表扫描的操作。
Spring Data for Apache Geode 可以轻松地在存储和访问数据的 Region 上创建索引。与以往通过 Spring 配置显式声明 Index Bean 定义不同,我们现在可以在 Java 中创建 Index Bean 定义,如下所示:
@Bean("BooksIsbnIndex")
IndexFactoryBean bookIsbnIndex(GemFireCache gemfireCache) {
IndexFactoryBean bookIsbnIndex = new IndexFactoryBean();
bookIsbnIndex.setCache(gemfireCache);
bookIsbnIndex.setName("BookIsbnIndex");
bookIsbnIndex.setExpression("isbn");
bookIsbnIndex.setFrom("/Books"));
bookIsbnIndex.setType(IndexType.KEY);
return bookIsbnIndex;
}
或者,我们可以使用 XML 来创建一个 Index bean 定义,如下所示:
<gfe:index id="BooksIsbnIndex" expression="isbn" from="/Books" type="KEY"/>
然而,现在你可以直接在应用程序领域对象类型的字段上定义索引,这些字段你知道将被用于查询条件中,以加速这些查询。你甚至可以为从应用程序仓库接口中用户自定义的查询方法所生成的 OQL 查询应用索引。
重用前面示例中的 Book 实体类,我们可以对 Book 中已知会在 BookRepository 接口中通过查询方法定义的查询所使用的字段添加注解,如下所示:
@Region("Books")
class Book {
@Id
private ISBN isbn;
@Indexed
private Author author;
private Category category;
private LocalDate releaseDate;
private Publisher publisher;
@LuceneIndexed
private String title;
}
在我们的新 Book 类定义中,我们使用 @Indexed 注解了 author 字段,并使用 @LuceneIndexed 注解了 title 字段。此外,isbn 字段此前已使用 Spring Data 的 @Id 注解进行标注,该注解用于标识包含 Book 实例唯一标识符的字段;在 Apache Geode 的 Spring Data 实现中,带有 @Id 注解的字段或属性在存储条目时将被用作 Region 中的键。
-
带有
@Id注解的字段或属性将创建一个 Apache GeodeKEY索引。 -
带有
@Indexed注解的字段或属性将创建一个 Apache GeodeHASH索引(默认类型)。 -
@LuceneIndexed注解的字段或属性将创建一个 Apache Geode Lucene 索引,用于结合 Apache Geode 的 Lucene 集成功能进行基于文本的搜索。
当 @Indexed 注解在未设置任何属性的情况下使用时,索引 name、expression 和 fromClause 是从添加了 @Indexed 注解的类的字段或属性派生的。expression 正是该字段或属性的名称。fromClause 是从域对象类上的 @Region 注解派生的;如果未指定 @Region 注解,则使用域对象类的简单名称。
当然,您可以显式设置 @Indexed 注解的任意属性,以覆盖 Apache Geode 的 Spring Data 所提供的默认值。
@Region("Books")
class Book {
@Id
private ISBN isbn;
@Indexed(name = "BookAuthorNameIndex", expression = "author.name", type = "FUNCTIONAL")
private Author author;
private Category category;
private LocalDate releaseDate;
private Publisher publisher;
@LuceneIndexed(name = "BookTitleIndex", destory = true)
private String title;
}
索引的name(在未显式设置时会自动生成)同时也用作在 Spring 容器中注册该索引所对应的 bean 的名称。如有必要,甚至可以按此名称将该索引 bean 注入到另一个应用程序组件中。
生成的索引名称遵循以下模式:<Region Name><Field/Property Name><Index Type>Idx。
例如,author 索引的名称将是 BooksAuthorHashIdx。
要启用索引功能,请在应用程序类上添加 @EnableIndexing 注解,如下所示:
@SpringBootApplication
@PeerCacheApplication
@EnableEntityDefinedRegions
@EnableIndexing
class ServerApplication { .. }
@EnablingIndexing 注解只有在同时声明了 @EnableEntityDefinedRegions 时才会生效。
本质上,索引是根据实体类类型上的字段或属性定义的,必须对实体类进行扫描,
以检查实体的字段和属性上是否存在索引注解。如果没有进行此扫描,就无法找到索引注解。
我们还强烈建议您限制扫描的范围。 |
尽管 Lucene 查询目前在 Apache Geode 的 Spring Data 仓库中尚不支持,但 SDG 通过使用熟悉的 Spring 模板设计模式,为 Apache Geode Lucene 查询提供了全面的支持。
最后,我们在本节结尾处提供一些使用索引时需要牢记的额外提示:
-
虽然执行OQL查询不需要OQL索引,但执行基于文本的Lucene搜索则需要Lucene索引。
-
OQL 索引不会持久化到磁盘,仅保存在内存中。因此,当 Apache Geode 节点重启时,必须重新构建索引。
-
您还需要注意维护索引所带来的开销,尤其是因为索引完全存储在内存中,并且在 Region 条目被更新时尤为明显。索引的“维护”可以配置为一项异步任务。
在重启 Spring 应用程序且需要重建索引时,你可以采用的另一种优化方式是:首先预先定义所有索引,然后一次性全部创建。在 Spring Data for Apache Geode 中,这一操作会在 Spring 容器刷新时执行。
你可以预先定义索引,然后通过将 define 注解上的 @EnableIndexing 属性设置为 true 来一次性创建所有索引。
有关更多详情,请参阅 Apache Geode 用户指南中的“一次性创建多个索引”。
创建合理的索引是一项重要任务,因为设计不良的索引可能弊大于利。
查看 @Indexed 注解和 @LuceneIndexed 注解的 Javadoc,以获取完整的配置选项列表。
有关 Apache Geode OQL 查询的更多详细信息,请点击此处。
有关 Apache Geode 索引的更多详细信息,请参见此处。
有关 Apache Geode Lucene 查询的更多详细信息,请点击此处。
6.13. 配置连续查询
Apache Geode 的另一个非常重要且实用的功能是 连续查询。
在万物互联的世界中,事件和数据流来自四面八方。对于许多应用程序而言,能够处理和实时响应大量数据流已成为日益重要的需求。一个典型的例子是自动驾驶汽车。能否实时接收、过滤、转换、分析数据并作出响应,是实时应用程序的关键差异化特征和核心特性。
幸运的是,Apache Geode 在这方面走在了时代的前列。通过使用连续查询(Continuous Queries,CQ),客户端应用程序可以声明其感兴趣的数据或事件,并注册监听器以在事件发生时进行处理。客户端应用程序感兴趣的数据通过 OQL 查询来表达,其中查询谓词用于过滤或识别感兴趣的数据。当数据被修改或新增,并且符合已注册 CQ 中查询谓词所定义的条件时,客户端应用程序就会收到通知。
Spring Data for Apache Geode 可以轻松地定义和注册连续查询(CQ),并附带一个关联的监听器来处理和响应 CQ 事件,而无需处理 Apache Geode 底层繁琐的细节。SDG 新推出的基于注解的 CQ 配置建立在现有的 连续查询监听器容器 的连续查询支持之上。
例如,假设一家图书出版商希望注册对某本书(需求)订单超过当前库存(供应)时的兴趣,并接收通知。那么该出版商的印刷应用程序可能会注册以下CQ:
ClientCache 应用程序。@SpringBootApplication
@ClientCacheApplication(subcriptionEnabled = true)
@EnableContinuousQueries
class PublisherPrintApplication {
@ContinuousQuery(name = "DemandExceedsSupply", query =
"SELECT book.* FROM /Books book, /Inventory inventory
WHERE book.title = 'How to crush it in the Book business like Amazon"
AND inventory.isbn = book.isbn
AND inventory.available < (
SELECT sum(order.lineItems.quantity)
FROM /Orders order
WHERE order.status = 'pending'
AND order.lineItems.isbn = book.isbn
)
")
void handleSupplyProblem(CqEvent event) {
// start printing more books, fast!
}
}
要启用连续查询,请在您的应用程序类上添加 @EnableContinuousQueries 注解。
定义连续查询(Continuous Queries)包括使用 @Component 注解对任意带有 Spring @ContinuousQuery 注解的 POJO 类方法进行标注(方式类似于 Spring Data GemFire 中使用 Function 注解的 POJO 方法)。
通过 @ContinuousQuery 注解定义了连续查询(CQ)的 POJO 方法,会在任何匹配查询谓词的数据被添加或更改时被调用。
此外,POJO 方法签名应符合以下章节中概述的要求:
ContinuousQueryListener 和 ContinuousQueryListenerAdapter。
请参阅 @EnableContinuousQueries 和 @ContinuousQuery 注解的 Javadoc,以获取更多关于可用属性和配置设置的详细信息。
有关 Spring Data for Apache Geode 的持续查询支持的更多详细信息,请参见此处。
有关 Apache Geode 的连续查询的更多详细信息,请点击此处。
6.14. 配置 Spring 的缓存抽象
通过 Spring Data for Apache Geode,Apache Geode 可以用作 Spring 的缓存抽象中的缓存提供者。
在 Spring 的缓存抽象中,缓存注解(例如 @Cacheable)用于标识在调用可能开销较大的操作之前执行缓存查找所使用的缓存。应用程序服务方法的结果会在操作执行后被缓存。
在 Apache Geode 的 Spring Data 中,Spring 的 Cache 直接对应于 Apache Geode 的 Region。在调用任何带有缓存注解的应用服务方法之前,该 Region 必须已经存在。这对于 Spring 所有用于标识服务操作中所使用缓存的缓存注解(即 @Cacheable、@CachePut 和 @CacheEvict)都是成立的。
例如,我们的出版商的销售点(Point-of-Sale,PoS)应用程序可能具有在销售交易过程中确定或查找某本Price的Book的功能,如下例所示:
@Service
class PointOfSaleService
@Cacheable("BookPrices")
Price runPriceCheckFor(Book book) {
...
}
@Transactional
Receipt checkout(Order order) {
...
}
...
}
为了在使用 Spring Data for Apache Geode 与 Spring 的缓存抽象时简化您的工作,注解配置模型中新增了两项功能。
请考虑以下 Spring 缓存配置:
@EnableCaching
class CachingConfiguration {
@Bean
GemfireCacheManager cacheManager(GemFireCache gemfireCache) {
GemfireCacheManager cacheManager = new GemfireCacheManager();
cacheManager.setCache(gemfireCache);
return cacheManager;
}
@Bean("BookPricesCache")
ReplicatedRegionFactoryBean<Book, Price> bookPricesRegion(GemFireCache gemfireCache) {
ReplicatedRegionFactoryBean<Book, Price> bookPricesRegion =
new ReplicatedRegionFactoryBean<>();
bookPricesRegion.setCache(gemfireCache);
bookPricesRegion.setClose(false);
bookPricesRegion.setPersistent(false);
return bookPricesRegion;
}
@Bean("PointOfSaleService")
PointOfSaleService pointOfSaleService(..) {
return new PointOfSaleService(..);
}
}
使用 Spring Data for Apache Geode 的新特性,您可以将相同的缓存配置简化为如下形式:
@EnableGemfireCaching
@EnableCachingDefinedRegions
class CachingConfiguration {
@Bean("PointOfSaleService")
PointOfSaleService pointOfSaleService(..) {
return new PointOfSaleService(..);
}
}
首先,@EnableGemfireCaching 注解取代了 Spring 的 @EnableCaching 注解,并且无需在 Spring 配置中显式声明一个名为 "cacheManager" 的 CacheManager Bean 定义。
其次,@EnableCachingDefinedRegions 注解与“配置 Region”一节中描述的 #bootstrap-annotation-config-regions 注解类似,会检查整个 Spring 应用程序中带有缓存注解的服务组件,以识别应用程序在运行时所需的所有缓存,并在应用启动时为这些缓存在 Apache Geode 中创建相应的 Region。
所创建的 Region 对创建它们的应用程序进程是本地的。如果应用程序是一个对等(peer)Cache,
则这些 Region 仅存在于该应用程序节点上。如果应用程序是一个ClientCache,那么 SDG 会创建客户端PROXY Region,
并期望集群中的服务器上已存在同名的 Region。
SDG 无法确定服务方法所需的缓存,因为该方法使用 Spring 的 CacheResolver 在运行时解析操作中使用的缓存。 |
| SDG 还支持在应用程序服务组件上使用 JCache(JSR-107)缓存注解。 有关可替代 JCache 缓存注解的等效 Spring 缓存注解,请参阅核心Spring 框架参考指南。 |
有关在 Spring 缓存抽象中使用 Apache Geode 作为缓存提供程序的更多详细信息,请参阅“对 Spring 缓存抽象的支持”一节。
有关 Spring 缓存抽象的更多详细信息,请参见此处。
6.15. 配置集群配置推送
这可能是 Spring Data for Apache Geode 中最令人兴奋的新特性。
当客户端应用程序类使用 @EnableClusterConfiguration 注解时,该客户端应用程序在 Spring 容器中定义并声明为 Bean 的任何 Region 或 Index 都会被“推送”到客户端所连接的服务器集群中。不仅如此,这种“推送”操作还会通过 HTTP 方式使 Apache Geode 记住客户端推送的配置。即使集群中的所有节点全部宕机,当它们重新启动后,仍将恢复为之前的相同配置。如果向集群中添加新的服务器,它也会获得相同的配置。
从某种意义上说,此功能与使用 Gfsh 手动在集群中的所有服务器上创建 Region 和索引并没有太大区别。不同之处在于,现在借助 Spring Data for Apache Geode,您不再需要使用 Gfsh 来创建 Region 和索引。您的 Spring Boot 应用程序在启用了 Spring Data for Apache Geode 的强大功能后,已经包含了为您创建 Region 和索引所需的所有配置元数据。
当你使用 Spring Data Repository 抽象时,我们就已经知道你的应用程序所需的所有 Region(例如由带有 @Region 注解的实体类所定义的 Region)和 Index(例如由带有 @Indexed 注解的实体字段和属性所定义的 Index)。
当你使用 Spring 的缓存抽象时,我们也能获知应用程序服务组件中缓存注解所标识的所有缓存对应的全部 Region。
本质上,您只需使用 Spring 框架开发应用程序,就已经向我们传达了所需了解的一切信息——无论是通过注解元数据、Java、XML 还是其他形式来使用其所有 API 和功能,无论用于配置、映射还是任何其他目的。
关键是,你可以在使用框架的特性及其支持基础设施(例如 Spring 的缓存抽象、Spring Data 仓库、Spring 的事务管理等)的同时,专注于应用程序的业务逻辑,而 Spring Data for Apache Geode 会为你处理这些框架特性所需的所有 Apache Geode 底层配置工作。
将配置从客户端推送到集群中的服务器,并让集群记住这些配置,部分得益于 Apache Geode 的集群配置服务。Apache Geode 的集群配置服务同时也是 Gfsh 所使用的同一服务,用于记录用户通过命令行向集群发出的与模式相关的变更(例如,gfsh> create region --name=Example --type=PARTITION)。
当然,由于集群可能会“记住”客户端在之前运行时推送的配置, Apache Geode 的 Spring Data 会谨慎处理,避免覆盖服务器上已定义的 Region 和索引。 这一点尤其重要,例如,当 Region 中已经包含数据时!
| 目前,没有选项可以覆盖任何已存在的 Region(区域)或 Index(索引)定义。若要重新创建 Region 或 Index,您必须先使用 Gfsh 销毁该 Region 或 Index,然后重新启动客户端应用程序,以便将配置再次推送到服务器。或者,您也可以使用 Gfsh 手动(重新)定义 Regions 和 Indexes。 |
| 与Gfsh不同,Spring Data for Apache Geode 仅支持从客户端在服务器上创建 Region 和索引。 对于高级配置和使用场景,您应使用Gfsh来管理(服务器端)集群。 |
要使用此功能,您必须在 Spring 和 Apache Geode org.springframework:spring-web 应用程序的 classpath 中显式声明 ClientCache 依赖项。 |
考虑以下配置所体现的强大功能:
ClientCache 应用程序@SpringBootApplication
@ClientCacheApplication
@EnableCachingDefinedRegions
@EnableEntityDefinedRegions
@EnableIndexing
@EnableGemfireCaching
@EnableGemfireRepositories
@EnableClusterConfiguration
class ClientApplication { .. }
您将立即获得一个 Spring Boot 应用程序,其中包含一个 Apache Geode ClientCache 实例、Spring Data 仓库,
以及以 Apache Geode 作为缓存提供者的 Spring 缓存抽象(Region 和索引不仅在客户端创建,还会被推送到集群中的服务器)。
从那里开始,您只需执行以下操作:
-
定义应用程序的领域模型对象,并使用映射和索引注解进行标注。
-
为每种实体类型定义 Repository 接口,以支持基本的数据访问操作和简单查询。
-
定义包含业务逻辑的服务组件,用于处理实体的事务。
-
在需要缓存、事务行为等的服务方法上声明相应的注解。
在本例中,没有任何内容涉及应用程序后端服务(例如 Apache Geode)所需的基础设施和底层支持。数据库用户拥有类似的功能,现在 Spring 和 Apache Geode 开发人员也同样具备了。
当与以下 Apache Geode 的 Spring Data 注解结合使用时,该应用程序几乎无需额外努力便能真正展翅高飞:
-
@EnableContinuousQueries -
@EnableGemfireFunctionExecutions -
@EnableGemfireCacheTransactions
查看 @EnableClusterConfiguration 注解 Javadoc 以获取更多信息。
6.16. 配置 SSL
与序列化数据以便通过网络传输同等重要的是,在数据传输过程中确保其安全性。 当然,在 Java 中实现这一点的常用方法是使用安全套接字扩展(SSE) 和传输层安全(TLS)。
要启用 SSL,请使用 @EnableSsl 注解您的应用程序类,如下所示:
ClientCache 应用程序@SpringBootApplication
@ClientCacheApplication
@EnableSsl
public class ClientApplication { .. }
然后,您需要设置必要的 SSL 配置属性或参数:密钥库(keystores)、用户名/密码等。
您可以分别为不同的 Apache Geode 组件(GATEWAY、HTTP、JMX、LOCATOR 和 SERVER)配置 SSL,也可以通过使用 CLUSTER 枚举值统一为它们配置 SSL。
你可以通过使用嵌套的 @EnableSsl 注解及其 components 属性(该属性接受 Component 枚举中的枚举值)来指定应将 SSL 配置设置应用于哪些 Apache Geode 组件,如下所示:
ClientCache 应用程序(按组件启用)@SpringBootApplication
@ClientCacheApplication
@EnableSsl(components = { GATEWAY, LOCATOR, SERVER })
public class ClientApplication { .. }
此外,您还可以通过使用相应的注解属性或关联的配置属性,指定组件级别的 SSL 配置(ciphers、protocols 以及 keystore/truststore 信息)。
查看 @EnableSsl 注解的 Javadoc
以获取更多详细信息。
有关 Apache Geode SSL 支持的更多详细信息,请点击此处。
6.17. 配置安全
毫无疑问,应用程序安全极其重要,而用于 Apache Geode 的 Spring Data 提供了全面的支持,以保障 Apache Geode 客户端和服务器的安全。
最近,Apache Geode 引入了一个新的集成安全框架 (取代了其旧有的身份验证和授权安全模型),用于处理身份验证和授权。 这一新安全框架的主要特性与优势之一是它能够与 Apache Shiro 集成,从而将身份验证和授权请求委托给 Apache Shiro 来实施安全控制。
本节剩余部分将演示 Spring Data for Apache Geode 如何进一步简化 Apache Geode 的安全机制。
6.17.1. 配置服务器安全
有多种不同的方式可以配置 Apache Geode 集群中服务器的安全性。
-
实现 Apache Geode 的
org.apache.geode.security.SecurityManager接口,并将 Apache Geode 的security-manager属性设置为使用完全限定类名指向您的应用程序SecurityManager实现。 或者,用户也可以在创建 Apache Geode 对等(peer)SecurityManager时,构造并初始化其https://geode.apache.org/releases/latest/javadoc/org/apache/geode/cache/CacheFactory.html#setSecurityManager-org.apache.geode.security.SecurityManager实现的实例, 并通过 CacheFactory.setSecurityManager(:SecurityManager) 方法进行设置。 -
为应用程序定义用户、角色和权限,创建一个 Apache Shiro
shiro.ini文件,然后设置 Apache Geodesecurity-shiro-init属性以引用此shiro.ini文件,该文件必须位于CLASSPATH中。 -
仅使用 Apache Shiro,将 Spring Data for Apache Geode 的新
@EnableSecurity注解添加到您的 Spring Boot 应用程序类中,并在 Spring 容器中定义一个或多个 Apache ShiroRealms作为 Bean,以访问应用程序的安全元数据(即授权用户、角色和权限)。
第一种方法的问题在于,您必须自己实现 SecurityManager,这可能非常繁琐且容易出错。实现自定义的 SecurityManager 在从任意数据源(例如 LDAP,甚至是专有的内部数据源)访问安全元数据方面提供了一定的灵活性。然而,这个问题已经通过配置和使用 Apache Shiro 的 Realms 得到了解决,而 Shiro 的 3 更为通用,并非 Apache Geode 特有。
第二种方法是使用 Apache Shiro 的 INI 文件,这种方式略好一些,但您仍然需要事先熟悉 INI 文件格式。此外,INI 文件是静态的,在运行时不易更新。
第三种方法最为理想,因为它遵循广为人知且业界公认的概念(即 Apache Shiro 安全框架),并且易于设置,如下例所示:
@SpringBootApplication
@CacheServerApplication
@EnableSecurity
class ServerApplication {
@Bean
PropertiesRealm shiroRealm() {
PropertiesRealm propertiesRealm = new PropertiesRealm();
propertiesRealm.setResourcePath("classpath:shiro.properties");
propertiesRealm.setPermissionResolver(new GemFirePermissionResolver());
return propertiesRealm;
}
}
前面示例中配置的 Realm 可以轻松地替换为 Apache Shiro 所支持的任意 Realms: |
-
支持INI 格式的
https://shiro.apache.org/static/1.3.2/apidocs/org/apache/shiro/realm/text/IniRealm.html。
你甚至可以创建一个 Apache Shiro Realm 的自定义实现。
有关更多详细信息,请参阅 Apache Shiro 的Realm 文档。
当 Apache Shiro 位于集群中服务器的 CLASSPATH 中,并且一个或多个 Apache Shiro Realms 已在 Spring 容器中定义为 Bean 时,Spring Data for Apache Geode 会检测到此配置,并在使用 @EnableSecurity 注解时,将 Apache Shiro 作为安全提供程序来保护您的 Apache Geode 服务器。
| 你可以在spring.io 博客文章中找到更多关于 Spring Data for Apache Geode 如何支持 Apache Geode 基于 Apache Shiro 的新集成安全框架的信息。 |
查看 @EnableSecurity 注解的 Javadoc,以获取有关可用属性及相关配置属性的详细信息。
有关 Apache Geode 安全性的更多详细信息,请点击此处。
6.17.2. 配置客户端安全性
如果不讨论如何保护基于 Spring 的 Apache Geode 缓存客户端应用程序,那么安全性方面的内容就不算完整。
Apache Geode 用于保护客户端应用程序的流程,老实说,相当复杂。简而言之,你需要:
-
提供
org.apache.geode.security.AuthInitialize接口的实现。 -
将 Apache Geode 的
security-client-auth-init(系统)属性设置为指向由应用程序提供的自定义AuthInitialize接口。 -
在专有的 Apache Geode
gfsecurity.properties文件中指定用户凭据。
Spring Data for Apache Geode 通过使用与服务器端应用程序相同的 @EnableSecurity 注解,简化了所有这些步骤。换句话说,同一个 @EnableSecurity 注解可同时处理客户端和服务器端应用程序的安全性。这一特性在用户决定将其应用程序从嵌入式的对等(peer)Cache 应用程序切换为 ClientCache 应用程序时尤为便利。例如,只需将 SDG 注解从 @PeerCacheApplication 或 @CacheServerApplication 更改为 @ClientCacheApplication 即可完成切换。
实际上,您在客户端所需要做的就是以下内容:
@EnableSecurity 的 Spring 客户端应用程序@SpringBootApplication
@ClientCacheApplication
@EnableSecurity
class ClientApplication { .. }
然后,您可以定义熟悉的 Spring Boot application.properties 文件,其中包含所需的用户名和密码,如下例所示,这样就全部配置好了:
application.properties 文件spring.data.gemfire.security.username=jackBlack
spring.data.gemfire.security.password=b@cK!nB1@cK
默认情况下,当 application.properties 文件位于应用程序 CLASSPATH 的根目录下时,Spring Boot 就能自动找到它。当然,Spring 还支持通过其资源抽象(Resource abstraction)以多种方式定位资源。 |
查看 @EnableSecurity 注解的 Javadoc,以获取有关可用属性及相关配置属性的详细信息。
有关 Apache Geode 安全性的更多详细信息,请点击此处。
6.18. 配置提示
以下技巧可帮助您充分利用新的基于注解的配置模型:
6.18.1. 配置组织
正如我们在“配置集群配置推送”一节中所看到的,
当使用注解启用许多 Apache Geode 或 Spring Data for Apache Geode 功能时,我们会在 Spring 的 @Configuration 或 @SpringBootApplication 类上叠加大量注解。在这种情况下,对配置进行一定程度的模块化划分就显得很有必要了。
例如,请考虑以下声明:
ClientCache 应用程序@SpringBootApplication
@ClientCacheApplication
@EnableContinuousQueries
@EnableCachingDefinedRegions
@EnableEntityDefinedRegions
@EnableIndexing
@EnableGemfireCacheTransactions
@EnableGemfireCaching
@EnableGemfireFunctionExecutions
@EnableGemfireRepositories
@EnableClusterConfiguration
class ClientApplication { .. }
我们可以按关注点将此配置分解如下:
ClientCache 应用程序@SpringBootApplication
@Import({ GemFireConfiguration.class, CachingConfiguration.class,
FunctionsConfiguration.class, QueriesConfiguration.class,
RepositoriesConfiguration.class })
class ClientApplication { .. }
@ClientCacheApplication
@EnableClusterConfiguration
@EnableGemfireCacheTransactions
class GemFireConfiguration { .. }
@EnableGemfireCaching
@EnableCachingDefinedRegions
class CachingConfiguration { .. }
@EnableGemfireFunctionExecutions
class FunctionsConfiguration { .. }
@EnableContinuousQueries
class QueriesConfiguration {
@ContinuousQuery(..)
void processCqEvent(CqEvent event) {
...
}
}
@EnableEntityDefinedRegions
@EnableGemfireRepositories
@EnableIndexing
class RepositoriesConfiguration { .. }
虽然这对 Spring 框架本身并无影响,但为了代码的可读性,我们通常建议这样做,以便于将来维护代码的人(说不定就是未来的你自己)能够更轻松地理解和维护。
6.18.2. 基于配置的附加注解
以下 SDG 注解未在本参考文档中讨论,原因要么是该注解支持 Apache Geode 的已弃用功能,要么是因为存在更好、更优的替代方式来实现该注解所提供的功能:
-
@EnableAuth:启用 Apache Geode 的旧版身份验证和授权安全模型。(已弃用。 Apache Geode 的新集成安全框架可通过在客户端和服务器端使用 SDG 的@EnableSecurity注解来启用,如“配置安全”中所述。) -
@EnableAutoRegionLookup:不推荐使用。本质上,该注解用于查找外部配置元数据(例如cache.xml或应用于服务器时的集群配置)中定义的 Region,并自动将这些 Region 注册为 Spring 容器中的 Bean。此注解对应于 SDG XML 命名空间中的<gfe:auto-region-lookup>元素。更多详细信息请参见此处。在使用 Spring 和 Spring Data for Apache Geode 时,用户通常应优先采用 Spring 配置方式。请参考“配置 Region”和“配置集群配置推送”。 -
@EnableBeanFactoryLocator:启用 SDGGemfireBeanFactoryLocator功能,该功能仅在在使用外部配置元数据(例如cache.xml)时才有用。例如,如果您在cache.xml中定义的 Region 上定义了CacheLoader,您仍然可以将此CacheLoader自动装配到 Spring 配置中定义的某个关系型数据库DataSourceBean。此注解利用了此 SDG 功能,如果您拥有大量遗留配置元数据(如cache.xml文件),则可能会很有用。 -
@EnableGemFireAsLastResource:在全局 - JTA 事务管理中结合 Apache Geode 进行了讨论。 -
@EnableMcast:启用 Apache Geode 旧的对等节点发现机制,该机制使用基于 UDP 的多播网络。 (已弃用。请改用 Apache Geode Locator。参见“配置嵌入式 Locator”。 -
@EnableRegionDataAccessTracing:用于调试目的。此注解通过注册一个 AOP 切面来启用对 Region 上所有数据访问操作的追踪,该切面会代理 Spring 容器中声明为 bean 的所有 Region,在 Region 操作执行时进行拦截并记录事件。
6.19. 结论
正如我们在前面章节中所学到的,Apache Geode 的 Spring Data 提供了全新的基于注解的配置模型,具有强大的功能。希望它能够实现其目标,让您在将 Apache Geode 与 Spring 结合使用时能够快速且轻松地上手。
请记住,在使用新注解时,您仍然可以使用 Java 配置或 XML 配置。
您甚至可以通过在 Spring @Configuration 或 @SpringBootApplication 类上使用 Spring 的 @Import
和 @ImportResource
注解来结合这三种方法。一旦您显式提供了本应由 Spring Data for Apache Geode 通过其中一种注解提供的 Bean 定义,基于注解的配置就会退让。
|
在某些情况下,你甚至可能需要回退到 Java 配置(例如 例如,另一个需要使用 Java 或 XML 配置的情况是配置 Apache Geode WAN 组件,
因为目前这些组件尚不支持注解配置。然而,定义和注册 WAN 组件
只需在 Spring 的 |
这些注解并非旨在处理所有情况。它们的目的是帮助您在开发过程中(尤其是在开发阶段)快速、轻松地 启动并运行起来。
我们希望您能享受这些新功能!
6.20. 基于注解的配置快速入门
以下各节概述了 SDG 注解,以便您快速上手。
| 所有注解都提供了额外的配置属性,以及关联的属性(properties), 以便在运行时方便地自定义 Apache Geode 的配置和行为。然而,通常情况下, 使用某个特定的 Apache Geode 功能时,并不需要指定这些属性或关联的属性。 只需声明该注解以启用相应功能即可。更多详细信息,请参阅每个注解各自的 Javadoc。 |
6.20.1. 配置一个ClientCache应用程序
要配置并引导一个 Apache Geode ClientCache 应用程序,请使用以下方法:
@SpringBootApplication
@ClientCacheApplication
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
有关更多详情,请参阅使用 Spring 配置 Apache Geode 应用程序。
6.20.2. 配置对等节点Cache应用程序
要配置并引导一个 Apache Geode 对等(Peer)Cache 应用程序,请使用以下方法:
@SpringBootApplication
@PeerCacheApplication
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
如果你想启用一个允许 CacheServer 应用程序连接到此服务器的 ClientCache,
只需将 @PeerCacheApplication 注解替换为 @CacheServerApplication 注解即可。这将会
在“localhost”上启动一个 CacheServer,并监听默认的 CacheServer 端口 40404。 |
有关更多详情,请参阅使用 Spring 配置 Apache Geode 应用程序。
6.20.3. 配置嵌入式 Locator
通过 @PeerCacheApplication 注解您的 Spring @CacheServerApplication 或 @EnableLocator 类,即可启动一个嵌入式 Locator,该 Locator 将绑定到所有网络接口卡(NIC),并监听默认的 Locator 端口 10334,如下所示:
@SpringBootApplication
@CacheServerApplication
@EnableLocator
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
@EnableLocator 只能与 Apache Geode 服务器应用程序一起使用。 |
有关更多详情,请参阅配置嵌入式定位器。
6.20.4. 配置嵌入式管理器
在您的 Spring @PeerCacheApplication 或 @CacheServerApplication 类上添加 @EnableManager 注解,即可启动一个内嵌的 Manager,该 Manager 将绑定到所有网络接口,并在默认的 Manager 端口 1099 上监听,如下所示:
@SpringBootApplication
@CacheServerApplication
@EnableManager
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
@EnableManager 只能与 Apache Geode 服务器应用程序一起使用。 |
有关更多详情,请参阅配置嵌入式管理器。
6.20.5. 配置嵌入式 HTTP 服务器
在您的 Spring @PeerCacheApplication 或 @CacheServerApplication 类上添加 @EnableHttpService 注解,以启动嵌入式 HTTP 服务器(Jetty),监听端口 7070,如下所示:
@SpringBootApplication
@CacheServerApplication
@EnableHttpService
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
@EnableHttpService 只能与 Apache Geode 服务器应用程序一起使用。 |
有关更多详情,请参阅配置嵌入式 HTTP 服务器。
6.20.6. 配置嵌入式 Memcached 服务器
在您的 Spring @PeerCacheApplication 或 @CacheServerApplication 类上添加 @EnableMemcachedServer 注解,即可启动内嵌的 Memcached 服务器(Gemcached),并使其监听 11211 端口,如下所示:
@SpringBootApplication
@CacheServerApplication
@EnableMemcachedServer
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
@EnableMemcachedServer 只能与 Apache Geode 服务器应用程序一起使用。 |
有关更多详情,请参阅配置嵌入式 Memcached 服务器(Gemcached)。
6.20.7. 配置嵌入式 Redis 服务器
在您的 Spring @PeerCacheApplication 或 @CacheServerApplication 类上添加 @EnableRedisServer 注解,以启动内嵌的 Redis 服务器并监听端口 6379,如下所示:
@SpringBootApplication
@CacheServerApplication
@EnableRedisServer
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
@EnableRedisServer 只能与 Apache Geode 服务器应用程序一起使用。 |
您必须在 Spring [Boot] 应用程序的 classpath 中显式声明 org.apache.geode:geode-redis 模块。 |
有关更多详情,请参阅配置嵌入式 Redis 服务器。
6.20.8. 配置日志
要配置或调整 Apache Geode 的日志记录,请在您的 Spring、Apache Geode 客户端或服务器应用程序类上添加 @EnableLogging 注解,如下所示:
@SpringBootApplication
@ClientCacheApplication
@EnableLogging(logLevel="trace")
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
默认的 log-level 为“config”。此外,此注解不会调整您应用程序中的日志级别,仅适用于 Apache Geode。 |
有关更多详细信息,请参阅配置日志记录。
6.20.9. 配置统计信息
要在运行时收集 Apache Geode 统计信息,请在您的 Spring、Apache Geode 客户端或服务器应用程序类上添加 @EnableStatistics 注解,如下所示:
@SpringBootApplication
@ClientCacheApplication
@EnableStatistics
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
有关更多详情,请参阅配置统计信息。
6.20.10. 配置 PDX
要启用 Apache Geode PDX 序列化,请使用 @EnablePdx 注解您的 Spring、Apache Geode 客户端或服务器应用程序类,如下所示:
@SpringBootApplication
@ClientCacheApplication
@EnablePdx
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
Apache Geode PDX 序列化是 Java 序列化的一种替代方案,并具有许多额外优势。例如,
它能轻松地使您所有的应用程序领域模型类型具备可序列化能力,而无需实现
java.io.Serializable。 |
默认情况下,SDG 会配置 MappingPdxSerializer 来序列化您的应用程序领域模型类型。由于 MappingPdxSerializer 的逻辑基于 Spring Data 的映射基础设施,因此无需任何特殊配置即可正确识别需要序列化的应用程序领域对象并执行序列化操作。更多详细信息,请参见 MappingPdxSerializer。 |
有关更多详情,请参阅配置 PDX。
6.20.11. 配置 SSL
要启用 Apache Geode SSL,请在您的 Spring、Apache Geode 客户端或服务器应用程序类上添加 @EnableSsl 注解,如下所示:
@SpringBootApplication
@ClientCacheApplication
@EnableSsl(components = SERVER)
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
至少,Apache Geode 要求您使用相应的配置属性或参数来指定密钥库(keystore)和信任库(truststore)。密钥库和信任库的配置属性或参数可以指向同一个 KeyStore 文件。此外,如果该文件已被加密保护,您还需要指定用户名和密码以访问该 KeyStore 文件。 |
| Apache Geode SSL 允许您配置系统中需要 TLS 的特定组件,例如客户端/服务器、定位器(Locators)、网关(Gateways)等。您也可以选择通过指定“ALL”,让 Apache Geode 的所有组件都使用 SSL。 |
有关更多详情,请参阅配置 SSL。
6.20.12. 配置安全
要启用 Apache Geode 安全性,请使用 @EnableSecurity 注解您的 Spring、Apache Geode 客户端或服务器应用程序类,如下所示:
@SpringBootApplication
@ClientCacheApplication
@EnableSecurity
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
在服务器上,您必须配置对凭据的访问权限。您可以实现 Apache Geode
SecurityManager 接口,或声明
一个或多个 Apache Shiro Realms。有关更多详细信息,请参阅 配置服务器安全性。 |
| 在客户端,您必须配置用户名和密码。更多详情请参见配置客户端安全。 |
有关更多详情,请参阅配置安全性。
6.20.13. 配置 Apache Geode 属性
要配置那些未被面向功能的 Spring Data GemFire(SDG)配置注解所涵盖的其他底层 Apache Geode 属性,请在您的 Spring Apache Geode 客户端或服务器应用程序类上添加 @GemFireProperties 注解,如下所示:
@SpringBootApplication
@PeerCacheApplication
@EnableGemFireProperties(
cacheXmlFile = "/path/to/cache.xml",
conserveSockets = true,
groups = "GroupOne",
remoteLocators = "lunchbox[11235],mailbox[10101],skullbox[12480]"
)
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
| 某些 Apache Geode 属性仅适用于客户端,而其他属性则仅适用于服务器端。请查阅 Apache Geode 文档,以了解每个属性的适当用法。 |
有关更多详情,请参阅配置 Apache Geode 属性。
6.20.14. 配置缓存
要在 Spring 的缓存抽象(Cache Abstraction)中使用 Apache Geode 作为缓存提供者(caching provider),
并让 Spring Data for Apache Geode (SDG) 自动为应用程序服务组件所需的缓存创建 Apache Geode 区域(Regions),
请在您的 Spring Apache Geode 客户端或服务器应用程序类上添加 @EnableGemfireCaching 和 @EnableCachingDefinedRegions 注解,如下所示:
@SpringBootApplication
@ClientCacheApplication
@EnableCachingDefinedRegions
@EnableGemfireCaching
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
然后,只需继续定义需要缓存的应用服务,如下所示:
@Service
public class BookService {
@Cacheable("Books")
public Book findBy(ISBN isbn) {
...
}
}
@EnableCachingDefinedRegions 是可选的。也就是说,如果您愿意,可以手动定义您的 Region。 |
有关更多详情,请参阅配置 Spring 的缓存抽象。
6.20.15. 为持久化应用配置区域、索引、存储库和实体
为了快速创建 Spring 和 Apache Geode 的持久化客户端或服务器应用程序,请在您的应用程序类上添加 @EnableEntityDefinedRegions、@EnableGemfireRepositories 和 @EnableIndexing 注解,如下所示:
@SpringBootApplication
@ClientCacheApplication
@EnableEntityDefinedRegions(basePackageClasses = Book.class)
@EnableGemfireRepositories(basePackageClasses = BookRepository.class)
@EnableIndexing
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
使用 @EnableEntityDefinedRegions 注解时,必须同时使用 @EnableIndexing 注解。
更多详情请参阅配置索引。 |
接下来,定义您的实体类,并使用 @Region 映射注解来指定该实体将被存储的 Region。使用 @Indexed 注解来为应用程序查询中使用的实体字段定义索引,如下所示:
package example.app.model;
import ...;
@Region("Books")
public class Book {
@Id
private ISBN isbn;
@Indexed;
private Author author;
@Indexed
private LocalDate published;
@LuceneIndexed
private String title;
}
@Region("Books") 实体类注解由 @EnableEntityDefinedRegions 使用,
以确定应用程序所需的 Region。更多详情请参见 配置类型特定的 Region 和 POJO 映射。 |
最后,按如下方式定义您的 CRUD Repository,并使用简单查询来持久化和访问 Books:
package example.app.repo;
import ...;
public interface BookRepository extends CrudRepository {
List<Book> findByAuthorOrderByPublishedDesc(Author author);
}
| 参见Spring Data for Apache Geode 仓库以获取更多详细信息。 |
查看 @Region Javadoc。
查看 @Indexed Javadoc。
有关更多详细信息,请参阅配置区域。
参见Spring Data for Apache Geode 仓库以获取更多详细信息。
6.20.16. 从集群定义的区域配置客户端区域
或者,您可以使用 @EnableClusterDefinedRegions 从集群中已定义的 Region 来定义客户端 [*PROXY] Region,如下所示:
@SpringBootApplication
@ClientCacheApplication
@EnableClusterDefinedRegions
@EnableGemfireRepositories
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
...
}
有关更多详情,请参阅已配置的集群定义区域。
6.20.17. 配置函数
Apache Geode 函数适用于分布式计算场景,其中可能需要大量计算资源且依赖数据的操作可以并行地在集群中的各个节点上执行。在这种情况下,将计算逻辑移至数据所在(存储)的位置,比将数据请求并获取到计算节点进行处理更为高效。
使用 @EnableGemfireFunctions 注解配合 @GemfireFunction 注解,可将 POJO 类中的方法实现为 Apache Geode 函数定义,如下所示:
@PeerCacheApplication
@EnableGemfireFunctions
class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
@GemfireFunction
Integer computeLoyaltyPoints(Customer customer) {
...
}
}
使用 @EnableGemfireFunctionExecutions 注解,并配合以下函数调用注解之一:@OnMember、@OnMembers、
@OnRegion、@OnServer 和 @OnServers。
@ClientCacheApplication
@EnableGemfireFunctionExecutions(basePackageClasses = CustomerRewardsFunction.class)
class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
@OnRegion("Customers")
interface CustomerRewardsFunctions {
Integer computeLoyaltyPoints(Customer customer);
}
查看 @OnMember Javadoc,
@OnMembers Javadoc,
@OnRegion Javadoc,
@OnServer Javadoc,
以及 @OnServers Javadoc。
有关更多详情,请参阅函数执行的注解支持。
6.20.18. 配置连续查询
实时事件流处理正成为数据密集型应用中日益重要的任务, 主要是为了能够及时响应用户请求。 Apache Geode 的连续查询(Continuous Query, CQ) 将帮助您轻松实现这一相当复杂的任务。
通过在您的应用程序类上添加 @EnableContinuousQueries 注解来启用 CQ,并定义您的持续查询(CQ)及其关联的事件处理器,如下所示:
@ClientCacheApplication
@EnableContinuousQueries
class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
然后,通过使用 @ContinousQuery 注解标注相应的处理方法来定义你的 CQ,如下所示:
@Service
class CustomerService {
@ContinuousQuery(name = "CustomerQuery", query = "SELECT * FROM /Customers c WHERE ...")
public void process(CqEvent event) {
...
}
}
每当发生事件,导致Customer数据发生变化并符合您连续OQL查询(CQ)中的谓词条件时,
process方法就会被调用。
| Apache Geode CQ 仅是一项客户端功能。 |
6.20.19. 配置集群配置
在使用 Apache Geode 作为 Apache Geode ClientCache 应用程序开发 Spring Data 应用时,在开发过程中,将服务器配置为与客户端在客户端/服务器拓扑中保持一致是非常有用的。事实上,Apache Geode 要求当客户端上存在名为 "/Example" 的 PROXY Region 时,服务器上必须存在一个名称匹配的 Region(即 "Example")。
你可以使用 Gfsh 来创建应用程序所需的所有 Region 和索引,或者,也可以在运行基于 Apache Geode 的 Spring Data 应用程序时,直接推送开发过程中已定义的配置元数据。
只需在您的主应用程序类上添加 @EnableClusterConfiguration(..) 注解即可:
@EnableClusterConfiguration@ClientCacheApplication
@EnableClusterConfiguration(useHttp = true)
class ClientApplication {
...
}
| 在大多数情况下,当使用客户端/服务器拓扑结构时(尤其是在生产环境中),集群的服务器通常会使用 Gfsh 启动。在这种情况下,通常会通过 HTTP(S) 将配置元数据(例如 Region 和索引定义)发送到集群。当使用 HTTP 时,配置元数据会被发送到集群中的管理节点(Manager),并一致地分发到集群中的各个服务器节点。 |
为了使用 @EnableClusterConfiguration,您必须在 Spring 应用程序的类路径中声明 org.springframework:spring-web 依赖项。 |
有关更多详情,请参阅配置集群配置推送。
6.20.20. 配置GatewayReceivers
在不同的 Apache Geode 集群之间复制数据,正日益成为一种重要的容错和高可用性(HA)机制。Apache Geode 的广域网(WAN)复制是一种机制,它允许一个 Apache Geode 集群以可靠且具备容错能力的方式将其数据复制到另一个 Apache Geode 集群。
Apache Geode WAN 复制需要配置两个组件:
-
GatewayReceiver- 用于接收来自远程 Apache Geode 集群的GatewaySender所发送数据的 WAN 复制组件。 -
GatewaySender- 用于将数据发送到远程 Apache Geode 集群的GatewayReceiver的 WAN 复制组件。
要启用 GatewayReceiver,应用程序类需要使用 @EnableGatewayReceiver 注解,如下所示:
@CacheServerApplication
@EnableGatewayReceiver(manualStart = false, startPort = 10000, endPort = 11000, maximumTimeBetweenPings = 1000,
socketBufferSize = 16384, bindAddress = "localhost",transportFilters = {"transportBean1", "transportBean2"},
hostnameForSenders = "hostnameLocalhost"){
...
...
}
}
class MySpringApplication { .. }
Apache Geode 的 GatewayReceiver 仅为服务器端功能,只能在 CacheServer 或对等的 Cache 节点上进行配置。 |
6.20.21. 配置GatewaySenders
要启用 GatewaySender,应用程序类需要使用 @EnableGatewaySenders 和 @EnableGatewaySender 注解,如下所示:
@CacheServerApplication
@EnableGatewaySenders(gatewaySenders = {
@EnableGatewaySender(name = "GatewaySender", manualStart = true,
remoteDistributedSystemId = 2, diskSynchronous = true, batchConflationEnabled = true,
parallel = true, persistent = false,diskStoreReference = "someDiskStore",
orderPolicy = OrderPolicyType.PARTITION, alertThreshold = 1234, batchSize = 100,
eventFilters = "SomeEventFilter", batchTimeInterval = 2000, dispatcherThreads = 22,
maximumQueueMemory = 400,socketBufferSize = 16384,
socketReadTimeout = 4000, regions = { "Region1"}),
@EnableGatewaySender(name = "GatewaySender2", manualStart = true,
remoteDistributedSystemId = 2, diskSynchronous = true, batchConflationEnabled = true,
parallel = true, persistent = false, diskStoreReference = "someDiskStore",
orderPolicy = OrderPolicyType.PARTITION, alertThreshold = 1234, batchSize = 100,
eventFilters = "SomeEventFilter", batchTimeInterval = 2000, dispatcherThreads = 22,
maximumQueueMemory = 400, socketBufferSize = 16384,socketReadTimeout = 4000,
regions = { "Region2" })
}){
class MySpringApplication { .. }
}
Apache Geode 的 GatewaySender 仅为服务器端功能,只能在 CacheServer 或对等的 Cache 节点上进行配置。 |
在上述示例中,应用程序配置了两个区域(Region),Region1 和 Region2。此外,
还将配置两个 GatewaySenders 以服务于这两个区域。GatewaySender1 将被配置为复制
Region1’s data and `GatewaySender2 的数据,而 5 将被配置为复制 6 的数据。
如前所示,每个 GatewaySender 属性都可以在每个 EnableGatewaySender 注解上进行配置。
也可以采用一种更通用的、“默认值”属性方法,即将所有属性都配置在 EnableGatewaySenders 注解上。通过这种方式,可以在父注解上设置一组通用的默认值,并在需要时于子类中进行覆盖,如下所示:
@CacheServerApplication
@EnableGatewaySenders(gatewaySenders = {
@EnableGatewaySender(name = "GatewaySender", transportFilters = "transportBean1", regions = "Region2"),
@EnableGatewaySender(name = "GatewaySender2")},
manualStart = true, remoteDistributedSystemId = 2,
diskSynchronous = false, batchConflationEnabled = true, parallel = true, persistent = true,
diskStoreReference = "someDiskStore", orderPolicy = OrderPolicyType.PARTITION, alertThreshold = 1234, batchSize = 1002,
eventFilters = "SomeEventFilter", batchTimeInterval = 2000, dispatcherThreads = 22, maximumQueueMemory = 400,
socketBufferSize = 16384, socketReadTimeout = 4000, regions = { "Region1", "Region2" },
transportFilters = { "transportBean2", "transportBean1" })
class MySpringApplication { .. }
当 regions 属性留空或未填充时,GatewaySender 将自动附加到应用程序中每个已配置的 Region。 |
7. 使用 Apache Geode API
一旦配置好了 Apache Geode 缓存(Cache)和区域(Regions),就可以将它们注入到应用程序对象中并加以使用。 本章介绍了与 Spring 事务管理功能以及 DAO 异常体系结构的集成。 本章还涵盖了对 Apache Geode 所管理对象的依赖注入支持。
7.1. GemfireTemplate
与 Spring 提供的许多其他高级抽象一样,Spring Data for Apache Geode 提供了一个模板(template),用于简化 Apache Geode 的数据访问操作。该类提供了多个包含常见 Region 操作的方法,同时还支持通过使用 GemfireCallback 来执行原生 Apache Geode API 的代码,而无需处理 Apache Geode 的受检异常(checked exceptions)。
该模板类需要一个 Apache Geode Region,一旦配置完成,即是线程安全的,并可在多个应用程序类之间重复使用:
<bean id="gemfireTemplate" class="org.springframework.data.gemfire.GemfireTemplate" p:region-ref="SomeRegion"/>
一旦模板配置完成,开发人员便可将其与GemfireCallback结合使用,直接操作Apache Geode的Region,而无需处理受检异常、线程或资源管理等问题:
template.execute(new GemfireCallback<Iterable<String>>() {
public Iterable<String> doInGemfire(Region region)
throws GemFireCheckedException, GemFireException {
Region<String, String> localRegion = (Region<String, String>) region;
localRegion.put("1", "one");
localRegion.put("3", "three");
return localRegion.query("length < 5");
}
});
为了充分发挥 Apache Geode 查询语言的强大功能,开发人员可以使用 find 和 findUnique 方法。与 query 方法相比,这些方法能够跨多个 Region 执行查询、执行投影(projections)等操作。
当查询通过 find 选择多个项时,应使用 SelectResults 方法;而后者 findUnique,顾名思义,适用于仅返回一个对象的情况。
7.2. 异常转换
使用一种新的数据访问技术不仅需要适配新的 API,还需要处理该技术特有的异常。
为了处理异常情况,Spring 框架提供了一套与具体技术无关且一致的 异常层次结构, 将应用程序从专有的、通常是“受检(checked)”的异常中抽象出来,转而使用一组聚焦的运行时异常。
正如Spring Framework文档中所述,
异常转换(Exception translation)
可以通过使用@Repository注解和AOP,
并定义一个PersistenceExceptionTranslationPostProcessor bean,
透明地应用到你的数据访问对象(DAO)上。当使用 Apache Geode 时,只要声明了CacheFactoryBean(例如,通过使用<gfe:cache/>
或<gfe:client-cache>声明),
同样会启用异常转换功能。该声明将充当异常转换器,并会被 Spring 基础设施自动检测并相应地使用。
7.3. 本地与缓存事务管理
Spring 框架最受欢迎的功能之一是 事务管理。
如果您不熟悉 Spring 的事务抽象,我们强烈建议您阅读有关Spring 事务管理基础设施的内容,因为它提供了一个一致的编程模型,可透明地跨多个 API 工作,并且既可以以编程方式配置,也可以以声明方式配置(最常用的选择)。
对于 Apache Geode,Spring Data for Apache Geode 提供了一个专用的、每个缓存(per-cache)的 PlatformTransactionManager,一旦声明该管理器,即可通过 Spring 以原子方式执行 Region 操作:
<gfe:transaction-manager id="txManager" cache-ref="myCache"/>
如果 Apache Geode 缓存使用默认名称 cache-ref 定义,则上述示例可以进一步简化,省略 gemfireCache 属性。与 Apache Geode 的其他 Spring Data 命名空间元素一样,如果未配置缓存 bean 的名称,将采用上述命名约定。此外,如果未显式指定事务管理器的名称,则其默认名称为 “gemfireTransactionManager”。 |
目前,Apache Geode 支持具有读已提交(read committed)隔离级别的乐观事务。此外,为了保证这种隔离性,开发人员应避免进行原地(in-place)修改,即手动更改缓存中已存在的值。
为防止此类情况发生,事务管理器默认将缓存配置为使用读时复制(copy on read)语义,
这意味着每次执行读取操作时都会创建实际值的一个克隆副本。如果需要,可通过 copyOnRead 属性禁用此行为。
由于在启用读取时复制(copy on read)时,会为给定键的值创建一个副本,因此您必须随后调用
Region.put(key, value) 才能以事务方式更新该值。
有关底层 Geode 事务管理器的语义和行为的更多信息,请参阅 Geode 的 CacheTransactionManager Javadoc 以及文档。
7.4. 全局 JTA 事务管理
Apache Geode 也可以参与全局的、基于 JTA 的事务,例如由 Java EE 应用服务器(如 WebSphere Application Server (WAS))使用容器管理事务(CMT)与其他 JTA 资源共同管理的事务。
然而,与许多其他 JTA“兼容”的资源(例如 ActiveMQ 等 JMS 消息代理)不同,Apache Geode 并非 XA 兼容的资源。因此,在 JTA 事务中(准备阶段),Apache Geode 必须被置于“最后一个资源”的位置,因为它并未实现两阶段提交协议,或者说不支持分布式事务。
许多支持容器管理事务(CMT)的托管环境在基于 JTA 的事务中仍提供对“最后资源”(Last Resource)这类非 XA 兼容资源的支持,尽管 JTA 规范本身并未强制要求这一点。有关非 XA 兼容的“最后资源”的具体含义,可参阅 Red Hat 的文档。 事实上,Red Hat 的 JBoss 项目Narayana 就是这样一个采用 LGPL 许可的开源实现。Narayana 将此机制称为“最后资源提交优化”(Last Resource Commit Optimization,LRCO)。更多详细信息请参见此处。
然而,无论您是在独立环境中使用支持“最后资源”(Last Resource)的开源 JTA 事务管理实现,还是在托管环境(例如 Java EE 应用服务器,如 WAS)中使用 Apache Geode,Spring Data for Apache Geode 都能为您提供全面支持。
要正确地在涉及多个事务资源的 JTA 事务中将 Apache Geode 用作“最后资源(Last Resource)”,您必须完成一系列步骤。此外,在这种配置中,只能存在一个非 XA 兼容的资源(例如 Apache Geode)。
1) 首先,您必须完成 Apache Geode 文档中此处的步骤 1-4。
| #1 上述步骤独立于您的 Spring [Boot] 和/或 [Data for Apache Geode] 应用程序,必须成功完成。 |
2) 参考 Apache Geode 的 文档 中的第 5 步,
Spring Data for Apache Geode 的注解支持在使用 @EnableGemFireAsLastResource 注解时,将尝试为您设置 GemFireCache、copyOnRead 属性。
然而,如果 SDG 的自动配置在此方面未能成功,则您必须在 copy-on-read 或 <gfe:cache> XML 元素中显式设置 <gfe:client-cache> 属性,或者在 JavaConfig 中将 copyOnRead 类的 CacheFactoryBean 属性设置为 true。例如:
ClientCache XML:
<gfe:client-cache ... copy-on-read="true"/>
ClientCache JavaConfig:
@Bean
ClientCacheFactoryBean gemfireCache() {
ClientCacheFactoryBean gemfireCache = new ClientCacheFactoryBean();
gemfireCache.setCopyOnRead(true);
return gemfireCache;
}
对等 Cache XML:
<gfe:cache ... copy-on-read="true"/>
对等的 Cache JavaConfig:
@Bean
CacheFactoryBean gemfireCache() {
CacheFactoryBean gemfireCache = new CacheFactoryBean();
gemfireCache.setCopyOnRead(true);
return gemfireCache;
}
显式设置 copy-on-read 属性或 copyOnRead 属性实际上并不是必需的。启用事务管理会自动处理读取时的复制操作。 |
3) 此时,您可以跳过 Apache Geode 文档中的第 6-8 步,
让Spring Data Geode发挥其魔力。您只需在 Spring 的@Configuration类上
添加 Spring Data for Apache Geode 的新@EnableGemFireAsLastResource注解,
结合 Spring 的事务管理基础设施和 Spring Data for Apache Geode 的
@EnableGemFireAsLastResource注解配置即可实现所需功能。
配置如下所示……
@Configuration
@EnableGemFireAsLastResource
@EnableTransactionManagement(order = 1)
class GeodeConfiguration {
...
}
唯一的要求是……
3.1) @EnableGemFireAsLastResource 注解必须声明在与 Spring 的 @Configuration 注解相同的 Spring @EnableTransactionManagement 类上。
3.2) order 注解的 @EnableTransactionManagement 属性必须显式设置为一个整数值,
该值不能是 Integer.MAX_VALUE 或 Integer.MIN_VALUE(默认值为 Integer.MAX_VALUE)。
当然,希望您也清楚,在使用 JTA 事务时,还需要像下面这样配置 Spring 的 JtaTransactionManager。
@Bean
public JtaTransactionManager transactionManager(UserTransaction userTransaction) {
JtaTransactionManager transactionManager = new JtaTransactionManager();
transactionManager.setUserTransaction(userTransaction);
return transactionManager;
}
本地缓存事务管理一节中的配置在此不适用。
Apache Geode 的 Spring Data(SDG)所提供的GemfireTransactionManager仅适用于“仅限本地”的缓存事务,
不适用于“全局”JTA 事务。因此,在这种情况下,您不应配置 SDG 的GemfireTransactionManager。
您应按照上文所示配置 Spring 的JtaTransactionManager。 |
有关在 JTA 中使用Spring 的事务管理的更多详细信息,请参见此处。
实际上,Spring Data for Apache Geode 的 @EnableGemFireAsLastResource 注解会导入包含两个切面(Aspect)Bean 定义的配置,这些定义会在事务操作的适当时间点处理 Apache Geode 的 o.a.g.ra.GFConnectionFactory.getConnection() 和 o.a.g.ra.GFConnection.close() 操作。
具体而言,正确的事件顺序如下:
-
jtaTransation.begin() -
GFConnectionFactory.getConnection() -
调用应用程序的
@Transactional服务方法 -
要么调用
jtaTransaction.commit(),要么调用jtaTransaction.rollback() -
最后,
GFConnection.close()
这与您作为应用程序开发人员在必须直接使用 JTA API + Apache Geode API 时手动编写代码的方式是一致的,如 Apache Geode 示例所示。
幸运的是,Spring 会为你完成繁重的工作,你只需应用适当的配置(如上所示),然后需要做的就是:
@Service
class MyTransactionalService {
@Transactional
public <Return-Type> someTransactionalServiceMethod() {
// perform business logic interacting with and accessing multiple JTA resources atomically
}
...
}
上面的第1点和第4点在您的应用程序进入PlatformTransactionManager边界时(即调用@Transactional时),会由Spring基于JTA的MyTransactionService.someTransactionalServiceMethod()为您自动处理。
#2 和 #3 由 Spring Data for Apache Geode 处理,该功能通过 @EnableGemFireAsLastResource 注解启用新的切面(Aspects)。
#3 当然属于你的应用程序的职责。
事实上,只要配置了适当的日志记录,你就会看到正确的事件顺序……
2017-Jun-22 11:11:37 TRACE TransactionInterceptor - Getting transaction for [example.app.service.MessageService.send]
2017-Jun-22 11:11:37 TRACE GemFireAsLastResourceConnectionAcquiringAspect - Acquiring {data-store-name} Connection
from {data-store-name} JCA ResourceAdapter registered at [gfe/jca]
2017-Jun-22 11:11:37 TRACE MessageService - PRODUCER [ Message :
[{ @type = example.app.domain.Message, id= MSG0000000000, message = SENT }],
JSON : [{"id":"MSG0000000000","message":"SENT"}] ]
2017-Jun-22 11:11:37 TRACE TransactionInterceptor - Completing transaction for [example.app.service.MessageService.send]
2017-Jun-22 11:11:37 TRACE GemFireAsLastResourceConnectionClosingAspect - Closed {data-store-name} Connection @ [Reference [...]]
有关使用 Apache Geode 缓存级事务的更多详细信息,请参见此处。
有关在 JTA 事务中使用 Apache Geode 的更多详细信息,请参见此处。
有关将 Apache Geode 配置为“最后资源”的更多详细信息,请参见此处。
7.5. 使用 @TransactionalEventListener
使用事务时,可能需要注册一个监听器,以便在事务提交之前或之后,或者在回滚发生之后执行某些操作。
Spring Data for Apache Geode 通过 @TransactionalEventListener 注解,可以轻松创建在事务特定阶段被调用的监听器。使用 @TransactionalEventListener 注解的方法(如下所示)将在指定的 phase 阶段收到从事务方法发布的事件通知。
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleAfterCommit(MyEvent event) {
// do something after transaction is committed
}
要使上述方法被调用,您必须在事务内部发布一个事件,如下所示:
@Service
class MyTransactionalService {
@Autowired
private final ApplicationEventPublisher applicationEventPublisher;
@Transactional
public <Return-Type> someTransactionalServiceMethod() {
// Perform business logic interacting with and accessing multiple transactional resources atomically, then...
applicationEventPublisher.publishEvent(new MyApplicationEvent(...));
}
...
}
@TransactionalEventListener 注解允许您指定事件处理方法将被调用的事务 phase。选项包括:AFTER_COMMIT、AFTER_COMPLETION、AFTER_ROLLBACK 和 BEFORE_COMMIT。
如果未指定,phase 默认为 AFTER_COMMIT。如果您希望即使在没有事务的情况下也调用监听器,可以将 fallbackExecution 设置为 true。
7.6. 自动发布事务事件
从 Spring Data for Apache Geode Neumann/2.3 版本开始,现在可以启用自动事务事件发布功能。
使用 @EnableGemfireCacheTransactions 注解,将 enableAutoTransactionEventPublishing 属性设置为 true。默认值为 false。
@EnableGemfireCacheTransactions(enableAutoTransactionEventPublishing = true)
class GeodeConfiguration { ... }
然后,您可以创建带有 @TransactionalEventListener 注解的 POJO 方法,以在 AFTER_COMMIT 或 AFTER_ROLLBACK 事务阶段处理事务事件。
@Component
class TransactionEventListeners {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleAfterCommit(TransactionApplicationEvent event) {
...
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void handleAfterRollback(TransactionApplicationEvent event) {
...
}
}
仅支持 TransactionPhase.AFTER_COMMIT 和 TransactionPhase.AFTER_ROLLBACK。
TransactionPhase.BEFORE_COMMIT 不受支持,原因如下:1) Spring Data for Apache Geode(SDG)通过适配 Apache Geode 的 TransactionListener 和 TransactionWriter 接口来实现自动事务事件发布;2) 当调用 Apache Geode 的 TransactionWriter.beforeCommit(:TransactionEvent) 方法时,实际上已经处于 AbstractPlatformTransactionManager.triggerBeforeCommit(:TransactionStatus) 调用之后,而带有 @TranactionalEventListener 注解的 POJO 方法正是在事务生命周期中的该阶段被调用的。 |
启用自动事务事件发布后,您无需在应用程序的 applicationEventPublisher.publishEvent(..) @Transactional 方法中显式调用 @Service 方法。
然而,如果你仍然希望在提交之前接收事务事件,那么你仍必须在应用程序的 applicationEventPublisher.publishEvent(..) @Transactional 方法中调用 @Service 方法。
更多详细信息,请参见上面的注意。
7.7. 连续查询 (CQ)
Apache Geode 提供的一项强大功能是 持续查询(Continuous Query,简称 CQ)。
简而言之,CQ(连续查询)允许开发人员创建并注册一个OQL查询,当新增到Apache Geode的数据满足该查询条件时,系统会自动通知开发人员。Spring Data for Apache Geode通过org.springframework.data.gemfire.listener包及其监听器容器(listener container)为CQ提供了专门的支持;其功能和命名方式与Spring Framework中的JMS集成非常相似;事实上,熟悉Spring中JMS支持的用户会感到非常亲切。
基本上,Spring Data for Apache Geode 允许 POJO 上的方法成为 CQ(连续查询)的端点。只需定义查询,并指明在匹配发生时应调用哪个方法以接收通知。Spring Data for Apache Geode 会处理其余所有事项。这种方式与 Java EE 的消息驱动 Bean 风格非常相似,但无需继承任何基类或实现特定接口,完全基于 Apache Geode。
| 目前,连续查询(Continuous Query)仅在 Apache Geode 的客户端/服务器拓扑结构中受支持。此外,所使用的客户端连接池(Pool)必须启用订阅功能。更多信息请参阅 Apache Geode 的文档。 |
7.7.1. 连续查询监听器容器
Spring Data for Apache Geode 通过使用 SDG 的 ContinuousQueryListenerContainer 来处理持续查询(CQ)相关的基础设施,从而简化了 CQ 事件的创建、注册、生命周期管理及分发。该容器为用户承担了所有繁重的工作。熟悉 EJB 和 JMS 的用户会发现这些概念非常熟悉,因为其设计尽可能贴近 Spring Framework 中所提供的消息驱动 POJO(MDP)支持。
SDG 的 ContinuousQueryListenerContainer 充当事件(或消息)监听器容器;它用于接收已注册的连续查询(CQ)所发出的事件,并调用注入其中的 POJO。该监听器容器负责处理消息接收的所有线程操作,并将消息分发给监听器进行处理。它作为事件驱动 POJO(EDP)与事件提供者之间的中介,负责创建和注册 CQ(用于接收事件)、资源获取与释放、异常转换等任务。这使得作为应用程序开发者的您可以专注于编写与接收事件(及对其作出响应)相关的(可能较为复杂的)业务逻辑,而将 Apache Geode 相关的样板式基础设施工作委托给框架处理。
监听器容器是完全可自定义的。开发者可以选择使用 CQ 线程来执行消息分发(同步投递),也可以通过定义合适的 java.util.concurrent.Executor(或 Spring 的 TaskExecutor)来使用新线程(来自现有线程池)以实现异步处理方式。根据负载、监听器数量或运行环境的不同,开发者应调整或微调执行器(executor),以更好地满足自身需求。特别是在托管环境(如应用服务器)中,强烈建议选择一个合适的 TaskExecutor,以充分利用其运行时特性。
7.7.2.ContinuousQueryListener和ContinuousQueryListenerAdapter
ContinuousQueryListenerAdapter 类是 Spring Data for Apache Geode 中 CQ(连续查询)支持的最后一个组件。简而言之,
该类允许您以极少的约束条件,将几乎任意实现类暴露为 EDP(事件驱动处理器)。
ContinuousQueryListenerAdapter 实现了 ContinuousQueryListener 接口,这是一个简单的监听器接口,
类似于 Apache Geode 的 CqListener。
请考虑以下接口定义。注意其中各种事件处理方法及其参数:
public interface EventDelegate {
void handleEvent(CqEvent event);
void handleEvent(Operation baseOp);
void handleEvent(Object key);
void handleEvent(Object key, Object newValue);
void handleEvent(Throwable throwable);
void handleQuery(CqQuery cq);
void handleEvent(CqEvent event, Operation baseOp, byte[] deltaValue);
void handleEvent(CqEvent event, Operation baseOp, Operation queryOp, Object key, Object newValue);
}
package example;
class DefaultEventDelegate implements EventDelegate {
// implementation elided for clarity...
}
特别要注意的是,上述 EventDelegate 接口的实现完全不依赖 Apache Geode。
它确实是一个普通的 Java 对象(POJO),我们可以通过以下配置将其转变为 EDP。
| 该类不必实现接口;接口仅用于更好地展示契约与实现之间的解耦。 |
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:gfe="https://www.springframework.org/schema/geode"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/geode https://www.springframework.org/schema/geode/spring-geode.xsd
">
<gfe:client-cache/>
<gfe:pool subscription-enabled="true">
<gfe:server host="localhost" port="40404"/>
</gfe:pool>
<gfe:cq-listener-container>
<!-- default handle method -->
<gfe:listener ref="listener" query="SELECT * FROM /SomeRegion"/>
<gfe:listener ref="another-listener" query="SELECT * FROM /AnotherRegion" name="myQuery" method="handleQuery"/>
</gfe:cq-listener-container>
<bean id="listener" class="example.DefaultMessageDelegate"/>
<bean id="another-listener" class="example.DefaultMessageDelegate"/>
...
<beans>
上面的示例展示了监听器可以采用的几种不同形式;最简情况下,必须提供监听器引用和实际的查询定义。然而,也可以为生成的连续查询指定一个名称(便于监控),以及指定处理方法的名称(默认为handleEvent)。
所指定的方法可以具有多种参数类型,EventDelegate接口列出了允许的参数类型。 |
上面的示例使用了 Apache Geode 的 Spring Data 命名空间来声明事件监听器容器,并自动注册监听器。完整的 beans 定义如下所示:
<!-- this is the Event Driven POJO (MDP) -->
<bean id="eventListener" class="org.springframework.data.gemfire.listener.adapter.ContinuousQueryListenerAdapter">
<constructor-arg>
<bean class="gemfireexample.DefaultEventDelegate"/>
</constructor-arg>
</bean>
<!-- and this is the event listener container... -->
<bean id="gemfireListenerContainer" class="org.springframework.data.gemfire.listener.ContinuousQueryListenerContainer">
<property name="cache" ref="gemfireCache"/>
<property name="queryListeners">
<!-- set of CQ listeners -->
<set>
<bean class="org.springframework.data.gemfire.listener.ContinuousQueryDefinition" >
<constructor-arg value="SELECT * FROM /SomeRegion" />
<constructor-arg ref="eventListener"/>
</bean>
</set>
</property>
</bean>
每次接收到事件时,适配器都会在 Apache Geode 事件与所需的方法参数之间自动执行类型转换,且该过程对用户透明。方法调用所引发的任何异常都会被容器捕获并处理(默认情况下会被记录日志)。
7.8. 装配Declarable组件
Apache Geode 的 XML 配置(通常称为 cache.xml)允许将用户对象作为配置的一部分进行声明。通常,这些对象是 CacheLoaders 或 Apache Geode 支持的其他可插拔回调组件。使用原生的 Apache Geode 配置时,通过 XML 声明的每个用户类型都必须实现 Declarable 接口,该接口允许通过 Properties 实例向所声明的类传递任意参数。
在本节中,我们将介绍如何在使用 Spring 框架时,针对在 cache.xml 中定义的可插拔组件进行配置,同时仍将您的缓存(Cache)/区域(Region)配置保留在 cache.xml 中。这样,您的可插拔组件便可专注于应用程序逻辑,而无需关心 DataSources 或其他协作对象的位置或创建方式。
然而,如果你正在启动一个全新的项目,建议你直接在 Spring 中配置 Cache、Region 以及其他可插拔的 Apache Geode 组件。这样可以避免继承本节中介绍的 Declarable 接口或基类。
有关此方法的更多信息,请参见以下侧边栏。
作为使用 Spring 配置 Declarable 组件的一个示例,请参见以下声明
(摘自 Declarable 的Javadoc):
<cache-loader>
<class-name>com.company.app.DBLoader</class-name>
<parameter name="URL">
<string>jdbc://12.34.56.78/mydb</string>
</parameter>
</cache-loader>
为了简化参数解析、转换以及对象初始化的任务,Spring Data for Apache Geode 提供了一个基类(WiringDeclarableSupport),允许 Apache Geode 用户对象通过模板 Bean 定义进行依赖注入;如果该模板定义缺失,则通过 Spring IoC 容器执行自动装配。要利用此功能,用户对象需要继承 WiringDeclarableSupport 类,该类会在初始化过程中自动定位声明它的 BeanFactory 并完成依赖注入。
7.8.1. 使用配置模板Bean 定义
使用时,WiringDeclarableSupport 会首先尝试查找一个已存在的 bean 定义,并将其用作装配模板。除非另有指定,否则组件的类名将被用作隐式的 bean 定义名称。
让我们看看在这种情况下,我们的 DBLoader 声明会是什么样子:
class DBLoader extends WiringDeclarableSupport implements CacheLoader {
private DataSource dataSource;
public void setDataSource(DataSource dataSource){
this.dataSource = dataSource;
}
public Object load(LoaderHelper helper) { ... }
}
<cache-loader>
<class-name>com.company.app.DBLoader</class-name>
<!-- no parameter is passed (use the bean's implicit name, which is the class name) -->
</cache-loader>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
">
<bean id="dataSource" ... />
<!-- template bean definition -->
<bean id="com.company.app.DBLoader" abstract="true" p:dataSource-ref="dataSource"/>
</beans>
在上述场景中,由于未指定参数,系统使用 ID/名称为 com.company.app.DBLoader 的 Bean 作为模板,用于装配由 Apache Geode 创建的实例。对于 Bean 名称采用不同命名约定的情况,可以在 Apache Geode 配置中传入 bean-name 参数:
<cache-loader>
<class-name>com.company.app.DBLoader</class-name>
<!-- pass the bean definition template name as parameter -->
<parameter name="bean-name">
<string>template-bean</string>
</parameter>
</cache-loader>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
">
<bean id="dataSource" ... />
<!-- template bean definition -->
<bean id="template-bean" abstract="true" p:dataSource-ref="dataSource"/>
</beans>
| template bean 的定义不必在 XML 中声明。 任何格式都是允许的(Groovy、注解等)。 |
7.8.2. 使用自动装配和注解进行配置
默认情况下,如果没有找到 bean 定义,WiringDeclarableSupport 将
自动装配
声明的实例。这意味着,除非该实例提供了任何依赖注入的元数据,
容器将查找对象的 setter 方法,并尝试自动满足这些依赖关系。
然而,开发人员也可以使用 JDK 5 注解为自动装配过程提供额外的信息。
| 我们强烈建议阅读 Spring 文档中专门的章节,以了解更多关于所支持的注解及启用条件的信息。 |
例如,上面假设的 DBLoader 声明可以通过以下方式注入一个由 Spring 配置的 DataSource:
class DBLoader extends WiringDeclarableSupport implements CacheLoader {
// use annotations to 'mark' the needed dependencies
@javax.inject.Inject
private DataSource dataSource;
public Object load(LoaderHelper helper) { ... }
}
<cache-loader>
<class-name>com.company.app.DBLoader</class-name>
<!-- no need to declare any parameters since the class is auto-wired -->
</cache-loader>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
">
<!-- enable annotation processing -->
<context:annotation-config/>
</beans>
通过使用 JSR-330 注解,CacheLoader 的代码得到了简化,因为 DataSource 的定位和创建已被外部化,用户代码只需关注加载过程。
DataSource 可能是事务性的、延迟创建的、在多个对象之间共享的,或者从 JNDI 中获取的。
这些方面都可以通过 Spring 容器轻松地进行配置和修改,而无需改动 DBLoader 的代码。
7.9. Spring 缓存抽象的支持
Spring Data for Apache Geode 提供了 Spring 缓存抽象 的实现,将 Apache Geode 定位为 Spring 缓存基础设施中的缓存提供者。
要使用 Apache Geode 作为底层实现的“缓存提供者”(在 Spring 的缓存抽象中),只需将 GemfireCacheManager 添加到您的配置中即可:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:gfe="https://www.springframework.org/schema/geode"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd
https://www.springframework.org/schema/geode https://www.springframework.org/schema/geode/spring-geode.xsd
">
<!-- enable declarative caching -->
<cache:annotation-driven/>
<gfe:cache id="gemfire-cache"/>
<!-- declare GemfireCacheManager; must have a bean ID of 'cacheManager' -->
<bean id="cacheManager" class="org.springframework.data.gemfire.cache.GemfireCacheManager"
p:cache-ref="gemfire-cache">
</beans>
如果使用默认的缓存 bean 名称(即 "gemfireCache"),则 cache-ref bean 定义上的 CacheManager 属性不是必需的,例如:<gfe:cache> 未显式指定 ID 的情况。 |
当声明了 GemfireCacheManager(单例)bean 实例并启用了声明式缓存时
(在 XML 中通过 <cache:annotation-driven/>,或在 Java 配置中通过 Spring 的 @EnableCaching 注解),
Spring 缓存注解(例如 @Cacheable)将标识出使用 Apache Geode 区域(Regions)在内存中缓存数据的“缓存”。
这些缓存(即 Region)必须在使用它们的缓存注解之前存在,否则将发生错误。
举例来说,假设你有一个客户服务应用程序,其中包含一个执行缓存操作的 CustomerService 应用组件……
@Service
class CustomerService {
@Cacheable(cacheNames="Accounts", key="#customer.id")
Account createAccount(Customer customer) {
...
}
然后你需要以下配置。
XML:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:gfe="https://www.springframework.org/schema/geode"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd
https://www.springframework.org/schema/geode https://www.springframework.org/schema/geode/spring-geode.xsd
">
<!-- enable declarative caching -->
<cache:annotation-driven/>
<bean id="cacheManager" class="org.springframework.data.gemfire.cache.GemfireCacheManager">
<gfe:cache/>
<gfe:partitioned-region id="accountsRegion" name="Accounts" persistent="true" ...>
...
</gfe:partitioned-region>
</beans>
JavaConfig:
@Configuration
@EnableCaching
class ApplicationConfiguration {
@Bean
CacheFactoryBean gemfireCache() {
return new CacheFactoryBean();
}
@Bean
GemfireCacheManager cacheManager() {
GemfireCacheManager cacheManager = GemfireCacheManager();
cacheManager.setCache(gemfireCache());
return cacheManager;
}
@Bean("Accounts")
PartitionedRegionFactoryBean accountsRegion() {
PartitionedRegionFactoryBean accounts = new PartitionedRegionFactoryBean();
accounts.setCache(gemfireCache());
accounts.setClose(false);
accounts.setPersistent(true);
return accounts;
}
}
当然,您可以自由选择任何您喜欢的 Region 类型(例如 REPLICATE、PARTITION、LOCAL 等)。
有关Spring 缓存抽象的更多详细信息,请再次参阅文档。
8. 使用 Apache Geode 序列化
为了提升 Apache Geode 内存数据网格的整体性能,Apache Geode 支持一种专用的序列化协议,称为 PDX。与标准 Java 序列化相比,PDX 不仅速度更快、结果更紧凑,还能在多种语言平台(Java、C++ 和 .NET)之间透明地工作。
有关更多详细信息,请参阅PDX 序列化特性和PDX 序列化内部机制。
本章讨论了 Spring Data for Apache Geode 简化和改进 Apache Geode 在 Java 中自定义序列化的各种方式。
8.1. 装配反序列化实例
序列化对象通常包含瞬态数据。瞬态数据往往依赖于其在特定时间点所在的系统或环境。例如,DataSource 是特定于环境的。序列化此类信息毫无用处,甚至可能带来风险,因为它仅局限于某个特定的虚拟机或机器。针对这种情况,Spring Data for Apache Geode 提供了一个特殊的 Instantiator,用于在 Apache Geode 反序列化期间为每个新创建的实例执行依赖注入。
通过这种机制,您可以依赖 Spring 容器来注入和管理某些依赖项,从而轻松地将瞬态数据与持久化数据分离,并以透明的方式拥有丰富的领域对象。
Spring 用户可能会发现这种方法类似于 @Configurable)。
WiringInstantiator 的工作原理与 WiringDeclarableSupport 类似,首先尝试查找作为 wiring 模板的 bean 定义,否则将回退到自动装配。
请参阅上一节(连接 Declarable 组件)以获取有关连接功能的更多详细信息。
要使用 SDG 的 Instantiator,请将其声明为一个 Bean,如下例所示:
<bean id="instantiator" class="org.springframework.data.gemfire.serialization.WiringInstantiator">
<!-- DataSerializable type -->
<constructor-arg>org.pkg.SomeDataSerializableClass</constructor-arg>
<!-- type id -->
<constructor-arg>95</constructor-arg>
</bean>
在 Spring 容器启动过程中,一旦完成初始化,默认情况下,Instantiator 会向 Apache Geode 序列化系统注册自身,并对 Apache Geode 在反序列化期间创建的所有 SomeDataSerializableClass 实例执行依赖注入。
8.2. 自动生成自定义Instantiators
对于数据密集型应用程序,随着数据流入,每台机器上可能会创建大量实例。
Apache Geode 使用反射来创建新类型,但在某些场景下,这可能会带来较高的开销。
与往常一样,最好通过性能分析来量化判断是否确实存在此问题。针对此类情况,Spring Data for Apache Geode
允许自动生成 Instatiator 类,该类可在不使用反射的情况下(通过默认构造函数)实例化新类型。以下示例展示了如何创建一个实例化器:
<bean id="instantiatorFactory" class="org.springframework.data.gemfire.serialization.InstantiatorFactoryBean">
<property name="customTypes">
<map>
<entry key="org.pkg.CustomTypeA" value="1025"/>
<entry key="org.pkg.CustomTypeB" value="1026"/>
</map>
</property>
</bean>
上述定义会自动为两个类(Instantiators 和 CustomTypeA)生成两个 CustomTypeB,
并将它们以用户 ID 1025 和 1026 注册到 Apache Geode 中。这两个 Instantiators 避免了使用反射,
而是直接通过 Java 代码创建实例。
9. POJO 映射
这一部分涵盖了:
9.1. 对象映射基础
本节介绍 Spring Data 对象映射、对象创建、字段和属性访问、可变性与不可变性的基础知识。 请注意,本节仅适用于不使用底层数据存储对象映射(如 JPA)的 Spring Data 模块。 此外,请务必查阅特定于存储的章节,以了解特定于存储的对象映射内容,例如索引、自定义列名或字段名等。
Spring Data 对象映射的核心职责是创建领域对象的实例,并将存储原生的数据结构映射到这些对象上。 这意味着我们需要两个基本步骤:
-
通过使用公开的构造函数之一来创建实例。
-
实例填充以实例化所有公开的属性。
9.1.1. 对象创建
Spring Data 会自动尝试检测持久化实体的构造函数,以用于实例化该类型的对象。 解析算法的工作方式如下:
-
如果有无参构造函数,将会使用它。 其他构造函数将被忽略。
-
如果只有一个带参数的构造函数,则将使用该构造函数。
-
如果有多个带参数的构造函数,Spring Data 将使用带有注解
@Autowired的构造函数。
该值解析假设构造函数参数名称与实体的属性名称匹配,即解析将按照如果要填充属性来进行,包括所有映射中的自定义设置(例如不同的数据存储列名或字段名等)。
这也要求类文件中可用参数名称信息或者构造函数上存在@ConstructorProperties注解。
可以通过使用 Spring Framework 的 @Value 注解并结合特定于存储的 SpEL 表达式来自定义值解析。
有关更多详细信息,请参阅关于特定于存储的映射的相关章节。
9.1.2. 属性填充
一旦实体实例被创建,Spring Data 就会填充该类中所有其余的持久化属性。 除非标识符属性已经由实体的构造函数填充(即通过其构造函数参数列表传入),否则将首先填充标识符属性,以便解析循环对象引用。 之后,所有尚未通过构造函数填充的非瞬态(non-transient)属性都会被设置到实体实例上。 为此,我们使用以下算法:
-
如果该属性是不可变的,但提供了一个
with…方法(见下文),我们将使用该with…方法创建一个包含新属性值的新实体实例。 -
如果定义了属性访问(即通过 getter 和 setter 进行访问),我们将调用 setter 方法。
-
如果该属性是可变的,我们会直接设置字段。
-
如果该属性是不可变的,我们将使用构造函数(由持久化操作调用,参见对象创建)来创建该实例的一个副本。
-
默认情况下,我们直接设置字段值。
让我们来看一下以下实体:
class Person {
private final @Id Long id; (1)
private final String firstname, lastname; (2)
private final LocalDate birthday;
private final int age; (3)
private String comment; (4)
private @AccessType(Type.PROPERTY) String remarks; (5)
static Person of(String firstname, String lastname, LocalDate birthday) { (6)
return new Person(null, firstname, lastname, birthday,
Period.between(birthday, LocalDate.now()).getYears());
}
Person(Long id, String firstname, String lastname, LocalDate birthday, int age) { (6)
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.birthday = birthday;
this.age = age;
}
Person withId(Long id) { (1)
return new Person(id, this.firstname, this.lastname, this.birthday, this.age);
}
void setRemarks(String remarks) { (5)
this.remarks = remarks;
}
}
| 1 | 标识符属性是 final 的,但在构造函数中被设为 null。
该类提供了一个 withId(…) 方法,用于设置标识符,例如当实例被插入到数据存储中并生成了标识符时。
原始的 Person 实例保持不变,因为会创建一个新的实例。
对于其他由存储管理、但在持久化操作中可能需要更改的属性,通常也采用相同的模式。
Wither 方法是可选的,因为持久化构造函数(参见第6节)实际上是一个拷贝构造函数,设置该属性会被转换为创建一个应用了新标识符值的新实例。 |
| 2 | firstname 和 lastname 属性是普通的不可变属性,可能通过 getter 方法对外暴露。 |
| 3 | age 属性是一个不可变的派生属性,它源自 birthday 属性。
按照所示的设计,数据库中的值将优先于默认值,因为 Spring Data 会使用唯一声明的构造函数。
即使本意是优先采用计算得出的值,该构造函数也必须将 age 作为参数(即使可能忽略它),否则属性填充步骤会尝试设置 age 字段,但由于该字段不可变且不存在 with… 方法,从而导致失败。 |
| 4 | comment 属性是可变的,可以通过直接设置其字段来填充。 |
| 5 | remarks 属性是可变的,并且可以通过直接设置 comment 字段或调用其setter方法来填充。 |
| 6 | 该类暴露了一个工厂方法和一个构造函数用于对象创建。
这里的核心思想是使用工厂方法而不是额外的构造函数,以避免通过@PersistenceConstructor进行构造函数去歧义化的需求。
相反,默认属性的设置在工厂方法中处理。 |
9.1.3. 通用建议
-
尽量使用不可变对象 — 不可变对象的创建非常直接,因为实例化对象只需调用其构造函数即可。 此外,这样做可以避免在你的领域对象中充斥着大量 setter 方法,从而防止客户端代码随意修改对象的状态。 如果你确实需要这些 setter 方法,建议将其设为包级私有(package protected),以便只有有限数量的同包类才能调用它们。 仅通过构造函数进行对象实例化,比通过属性赋值的方式快多达 30%。
-
提供一个包含所有参数的构造函数 — 即使你无法或不希望将实体建模为不可变值,仍然建议提供一个接收实体所有属性(包括可变属性)作为参数的构造函数,因为这样可以让对象映射跳过属性填充步骤,从而实现最佳性能。
-
使用工厂方法替代重载构造函数,以避免
@PersistenceConstructor— 由于需要全参数构造函数以实现最佳性能,我们通常希望暴露更多针对特定应用场景的构造函数,这些构造函数会省略自动生成的标识符等内容。 采用静态工厂方法来暴露这些全参数构造函数的变体,是一种既定的设计模式。 -
请确保遵守相关约束条件,以允许使用所生成的实例化器和属性访问器类 —
-
若要生成标识符,仍应使用 final 字段结合全参数持久化构造函数(推荐)或
with…方法 — -
使用 Lombok 避免样板代码 — 由于持久化操作通常需要一个包含所有参数的构造函数,其声明往往会变成繁琐的样板代码,即重复地将参数赋值给字段。通过使用 Lombok 的
@AllArgsConstructor注解,可以很好地避免这种情况。
9.1.4. Kotlin 支持
Spring Data 适配了 Kotlin 的特性,以支持对象的创建和变更。
Kotlin 对象创建
Kotlin 类支持被实例化,所有类默认都是不可变的,并且需要显式声明属性以定义可变属性。
请考虑以下 data 类 Person:
data class Person(val id: String, val name: String)
上面的类会编译成一个带有显式构造函数的典型类。我们可以通过添加另一个构造函数并使用 @PersistenceConstructor 注解来定制此类,以指明首选的构造函数:
data class Person(var id: String, val name: String) {
@PersistenceConstructor
constructor(id: String) : this(id, "unknown")
}
Kotlin 通过允许在未提供参数时使用默认值来支持参数的可选性。
当 Spring Data 检测到一个带有参数默认值的构造函数时,如果数据存储未提供该值(或仅返回 null),它就会省略这些参数,从而让 Kotlin 能够应用参数默认值。请考虑以下对 name 参数应用默认值的类:
data class Person(var id: String, val name: String = "unknown")
每当 name 参数不在结果中,或者其值为 null 时,name 将默认为 unknown。
Kotlin 数据类的属性填充
在 Kotlin 中,所有类默认都是不可变的,需要显式声明属性才能定义可变属性。
请考虑以下 data 类 Person:
data class Person(val id: String, val name: String)
该类实际上是不可变的。
它允许创建新实例,因为 Kotlin 会生成一个 copy(…) 方法,该方法通过复制现有对象的所有属性值并应用作为方法参数提供的属性值来创建新的对象实例。
9.2. 实体映射
Spring Data for Apache Geode 提供了对存储在 Region 中的实体进行映射的支持。映射元数据通过在应用程序领域类上使用注解来定义,如下例所示:
@Region("People")
public class Person {
@Id Long id;
String firstname;
String lastname;
@PersistenceConstructor
public Person(String firstname, String lastname) {
// …
}
…
}
@Region 注解可用于自定义 Person 类的实例所存储的 Region。
@Id 注解可用于标注应作为缓存 Region 键的属性,用于标识 Region 中的条目。
@PersistenceConstructor 注解有助于在存在多个可能可用的构造函数时消除歧义,通过参数明确标记被注解的构造函数为用于构建实体的构造函数。
在没有构造函数或仅有一个构造函数的应用程序领域类中,可以省略该注解。
除了将实体存储在顶级区域(Regions)中外,实体也可以存储在子区域(Sub-Regions)中,如下例所示:
@Region("/Users/Admin")
public class Admin extends User {
…
}
@Region("/Users/Guest")
public class Guest extends User {
…
}
请务必使用 Apache Geode Region 的完整路径,该路径通过 Spring Data for Apache Geode XML 命名空间定义,并使用 id 元素的 name 或 <*-region> 属性指定。
9.2.1. 按区域类型进行实体映射
除了 @Region 注解之外,Spring Data for Apache Geode 还识别特定于 Region 类型的映射注解:@ClientRegion、@LocalRegion、@PartitionRegion 和 @ReplicateRegion。
从功能上讲,这些注解在 SDG 映射基础设施中与通用的 @Region 注解完全等效。然而,这些额外的映射注解在 Spring Data for Apache Geode 的注解配置模型中非常有用。当与 Spring 中标注了 @EnableEntityDefinedRegions 的类上的 @Configuration 配置注解结合使用时,无论应用程序是客户端还是对等节点,都可以在本地缓存中生成 Region。
这些注解可让您更明确地指定应用程序实体类应映射到哪种类型的 Region,同时也会影响该 Region 的数据管理策略(例如,分区(也称为分片)与数据复制)。
在 SDG 注解配置模型中使用这些特定类型的 Region 映射注解,可以省去您在配置中显式定义这些 Region 的麻烦。
9.3. 仓库映射
除了在实体类上使用 @Region 注解来指定实体存储的 Region 外,您还可以在实体的 @Region 接口上指定 Repository 注解。
更多详细信息,请参阅 Apache Geode 的 Spring Data Repositories。
然而,假设你希望将一个 Person 记录存储在多个 Apache Geode 区域中(例如 People 和 Customers)。那么你可以按如下方式定义相应的 Repository 接口扩展:
@Region("People")
public interface PersonRepository extends GemfireRepository<Person, String> {
…
}
@Region("Customers")
public interface CustomerRepository extends GemfireRepository<Person, String> {
...
}
然后,通过单独使用每个 Repository,您可以将实体存储到多个 Apache Geode 区域中,如下例所示:
@Service
class CustomerService {
CustomerRepository customerRepo;
PersonRepository personRepo;
Customer update(Customer customer) {
customerRepo.save(customer);
personRepo.save(customer);
return customer;
}
你甚至可以将update服务方法包装在Spring管理的事务中,既可以作为本地缓存事务,也可以作为全局事务。
9.4. MappingPdxSerializer
Spring Data for Apache Geode 提供了一个自定义的 PdxSerializer
实现,称为 MappingPdxSerializer,它使用 Spring Data 映射元数据来自定义实体序列化。
该序列化器还允许您通过使用 Spring Data 的 EntityInstantiator 抽象来定制实体的实例化。
默认情况下,序列化器使用 ReflectionEntityInstantiator,它会调用映射实体的持久化构造函数。
持久化构造函数可以是默认构造函数、唯一声明的构造函数,或者显式使用 @PersistenceConstructor 注解的构造函数。
为了为构造函数参数提供参数,序列化器会从提供的
PdxReader中读取具有命名构造函数参数的字段,
这些字段通过 Spring 的 @Value 注解显式标识,如下示例所示:
@Valuepublic class Person {
public Person(@Value("#root.thing") String firstName, @Value("bean") String lastName) {
…
}
}
以这种方式标注的实体类会从 PdxReader 中读取“thing”字段,并将其作为构造函数参数 firstname 的参数值传入。lastName 的值则是一个名为“bean”的 Spring Bean。
除了EntityInstantiators提供的自定义实例化逻辑和策略外,
MappingPdxSerializer还提供了远超 Apache Geode 自身
ReflectionBasedAutoSerializer的功能。
虽然 Apache Geode 的 ReflectionBasedAutoSerializer 可以方便地利用 Java 反射来填充实体,
并使用正则表达式来识别应由该序列化器处理(序列化和反序列化)的类型,
但它无法像 MappingPdxSerializer 那样执行以下操作:
-
为每个实体字段或属性名称及类型注册自定义的
PdxSerializer对象。 -
便捷地标识 ID 属性。
-
自动处理只读属性。
-
自动处理瞬态属性。
-
以一种更健壮的方式进行类型过滤,支持
null值并具备类型安全性(例如,不仅限于仅使用正则表达式来表示类型)。
我们现在更详细地探讨一下 MappingPdxSerializer 的每一项功能。
9.4.1. 自定义 PdxSerializer 注册
MappingPdxSerializer 允许您根据实体的字段或属性名称及类型注册自定义的 PdxSerializers。
例如,假设你已定义了一个用于建模User的实体类型,如下所示:
package example.app.security.auth.model;
public class User {
private String name;
private Password password;
...
}
虽然用户的姓名可能不需要任何特殊逻辑来序列化其值,但另一方面,序列化密码可能需要额外的逻辑来处理该字段或属性的敏感性。
也许您希望在客户端与服务器之间通过网络传输密码值时,除了仅使用 TLS 之外,进一步保护密码,并且只希望存储加盐后的哈希值。使用 MappingPdxSerializer 时,您可以注册一个自定义的 PdxSerializer 来处理用户的密码,如下所示:
PdxSerializersMap<?, PdxSerializer> customPdxSerializers = new HashMap<>();
customPdxSerializers.put(Password.class, new SaltedHashPasswordPdxSerializer());
mappingPdxSerializer.setCustomPdxSerializers(customPdxSerializers);
在将应用程序定义的 SaltedHashPasswordPdxSerializer 实例注册到 Password 应用程序领域模型类型之后,MappingPdxSerializer 将会使用该自定义的 PdxSerializer 来序列化和反序列化所有 Password 对象,无论其所属的容器对象是什么(例如 User)。
然而,假设你只想在 Passwords 对象上自定义 User 的序列化。
为此,你可以通过指定 PdxSerializer 字段或属性的全限定名,为 User 类型注册自定义的 Class’s,如下例所示:
PdxSerializersMap<?, PdxSerializer> customPdxSerializers = new HashMap<>();
customPdxSerializers.put("example.app.security.auth.model.User.password", new SaltedHashPasswordPdxSerializer());
mappingPdxSerializer.setCustomPdxSerializers(customPdxSerializers);
请注意,此处使用了完全限定的字段或属性名称(即 example.app.security.auth.model.User.password)
作为自定义 PdxSerializer 的注册键。
你可以使用更具逻辑性的代码片段来构造注册键,例如以下代码:
User.class.getName().concat(".password");。我们推荐使用这种方式,而不是前面所示的示例。
前面的示例试图尽可能明确地表达注册的语义。 |
9.4.2. 映射 ID 属性
与 Apache Geode 的 ReflectionBasedAutoSerializer 类似,SDG 的 MappingPdxSerializer 也能够确定实体的标识符。然而,MappingPdxSerializer 是通过使用 Spring Data 的映射元数据来实现的,具体而言,是通过查找由 Spring Data 的
@Id 注解指定为标识符的实体属性。
或者,任何名为“id”且未显式标注 @Id 的字段或属性,也会被指定为实体的标识符。
例如:
class Customer {
@Id
Long id;
...
}
在这种情况下,当调用 PdxSerializer.toData(..) 方法进行序列化时,通过使用 PdxWriter.markIdentifierField(:String),Customer id 字段在 PDX 类型元数据中被标记为标识符字段。
9.4.3. 映射只读属性
当你的实体定义了一个只读属性时,会发生什么?
首先,理解什么是“只读”属性非常重要。如果你按照JavaBeans规范(Spring 也是如此)来定义一个 POJO, 你可以如下所示定义一个具有只读属性的 POJO:
package example;
class ApplicationDomainType {
private AnotherType readOnly;
public AnotherType getReadOnly() [
this.readOnly;
}
...
}
readOnly 属性是只读的,因为它没有提供 setter 方法,仅包含一个 getter 方法。
在这种情况下,readOnly 属性(不要与 readOnly DomainType 字段混淆)
被视为只读。
因此,在反序列化过程中,当通过 MappingPdxSerializer 方法填充 ApplicationDomainType 实例时,PdxSerializer.fromData(:Class<ApplicationDomainType>, :PdxReader) 将不会尝试为此属性设置值,即使 PDX 序列化字节中存在该值。
这在某些场景下非常有用,例如当你返回某个实体类型的视图或投影时,仅希望设置可写入的状态。该实体的视图或投影可能是基于授权或其他条件而定的。关键是,你可以根据应用程序的具体用例和需求恰当地利用这一特性。如果你希望某个字段或属性始终可被写入,只需定义一个 setter 方法即可。
9.4.4. 映射瞬态属性
同样地,当你的实体定义了 transient 属性时会发生什么?
你可能会期望在序列化实体时,该实体的 transient 字段或属性不会被序列化为 PDX。实际情况正是如此,这与 Apache Geode 自带的 ReflectionBasedAutoSerializer 不同,后者会通过 Java 反射机制将对象中所有可访问的内容全部序列化。
MappingPdxSerializer 不会序列化任何被标记为瞬态的字段或属性,无论是通过 Java 自身的 transient 关键字(针对类实例字段),还是通过在字段或属性上使用
@Transient
Spring Data 注解。
例如,您可以按如下方式定义一个包含瞬态字段和属性的实体:
package example;
class Process {
private transient int id;
private File workingDirectory;
private String name;
private Type type;
@Transient
public String getHostname() {
...
}
...
}
Process 的 id 字段和可读的 hostname 属性都不会写入 PDX。
9.4.5. 按类类型过滤
与 Apache Geode 的 ReflectionBasedAutoSerializer 类似,SDG 的 MappingPdxSerializer 允许你过滤要序列化和反序列化的对象类型。
然而,与 Apache Geode 的 ReflectionBasedAutoSerializer(使用复杂的正则表达式来指定序列化器处理的类型)不同,SDG 的 MappingPdxSerializer 使用了更加健壮的
java.util.function.Predicate 接口和 API 来表达类型匹配标准。
如果你想使用正则表达式,可以利用 Java 的正则表达式支持来实现一个https://docs.oracle.com/javase/8/docs/api/java/util/regex/package-summary.html。 |
Java 的 Predicate 接口的一大优点是,您可以通过便捷且合适的 API 方法组合 Predicates,包括:
and(:Predicate)、
or(:Predicate)
以及 negate()。
以下示例展示了 Predicate API 的实际用法:
Predicate<Class<?>> customerTypes =
type -> Customer.class.getPackage().getName().startsWith(type.getName()); // Include all types in the same package as `Customer`
Predicate includedTypes = customerTypes
.or(type -> User.class.isAssignble(type)); // Additionally, include User sub-types (e.g. Admin, Guest, etc)
mappingPdxSerializer.setIncludeTypeFilters(includedTypes);
mappingPdxSerializer.setExcludeTypeFilters(
type -> !Reference.class.getPackage(type.getPackage()); // Exclude Reference types
传递给您的 Class 的任何 Predicate 对象都保证不为 null。 |
SDG 的 MappingPdxSerializer 同时支持包含和排除类类型的过滤器。
排除类型过滤
默认情况下,SDG 的 MappingPdxSerializer 会注册预定义的 Predicates,用于过滤或排除以下包中的类型:
-
java.* -
com.gemstone.gemfire.* -
org.apache.geode.* -
org.springframework.*
此外,MappingPdxSerializer 在调用 null 方法时会过滤 PdxSerializer.toData(:Object, :PdxWriter) 对象,
在调用 null 方法时会过滤 PdxSerializer.fromData(:Class<?>, :PdxReader) 的类类型。
通过简单地定义一个Predicate并将其添加到MappingPdxSerializer中(如前所示),即可非常轻松地为其他类类型或整个包中的类型添加排除规则。
MappingPdxSerializer.setExcludeTypeFilters(:Predicate<Class<?>>) 方法是可叠加的,这意味着它会使用 Predicates 方法,将您应用程序定义的类型过滤器与上面所示的现有预定义类型过滤器 Predicate.and(:Predicate<Class<?>>) 进行组合。
但是,如果你想要包含一个被排除类型过滤器隐式排除的类类型(例如,java.security Principal),该怎么办呢?请参阅包含类型过滤。
包含类型过滤
如果你想要显式包含某个类类型,或者覆盖某个类类型过滤器(该过滤器隐式排除了应用程序所需的类类型,例如 java.security.Principal,它在 java.* 上默认通过 MappingPdxSerializer 包的排除类型过滤器被排除),那么只需定义相应的 Predicate,并通过 MappingPdxSerializer.setIncludeTypeFilters(:Predicate<Class<?>>) 方法将其添加到序列化器中,如下所示:
Predicate<Class<?>> principalTypeFilter =
type -> java.security.Principal.class.isAssignableFrom(type);
mappingPdxSerializer.setIncludeTypeFilters(principalTypeFilters);
同样,MappingPdxSerializer.setIncludeTypeFilters(:Predicate<Class<?>>) 方法
与 setExcludeTypeFilters(:Predicate<Class<?>>) 类似,具有累加性,因此会使用
Predicate.or(:Predicate<Class<?>>) 组合所有传入的类型过滤器。
这意味着您可以根据需要多次调用 setIncludeTypeFilters(:Predicate<Class<?>>)。
当存在包含类型过滤器时,MappingPdxSerializer 会根据以下条件决定是否对某个类类型的实例进行序列化或反序列化:该类类型未被隐式排除,或者该类类型被显式包含,只要其中任一条件为 true。满足条件后,该类类型的实例将被适当地序列化或反序列化。
例如,当显式注册一个类型为 Predicate<Class<Principal>> 的类型过滤器(如前所示)时,
它会取消对 java.* 包类型隐式的排除类型过滤器。
10. Spring Data for Apache Geode Repositories
Spring Data for Apache Geode 提供了对使用 Spring Data Repository 抽象的支持,可轻松将实体持久化到 Apache Geode 中并执行查询。有关 Repository 编程模型的总体介绍,请参见此处。
10.1. Spring XML 配置
要引导 Spring Data 仓库,请使用 Apache Geode 数据的 Spring Data 命名空间中的 <repositories/> 元素,如下例所示:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:gfe-data="https://www.springframework.org/schema/data/geode"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/data/geode https://www.springframework.org/schema/data/geode/spring-data-geode.xsd
">
<gfe-data:repositories base-package="com.example.acme.repository"/>
</beans>
上述配置片段会查找指定基础包下的接口,并为这些接口创建由 SimpleGemFireRepository 支持的 Repository 实例。
| 除非您已将应用程序的领域类正确映射到已配置的 Region,否则引导过程将失败。 |
10.2. Spring 基于 Java 的配置
或者,许多开发者更喜欢使用 Spring 的基于 Java 的容器配置。
使用这种方法,您可以使用 SDG 的 @EnableGemfireRepositories 注解来引导 Spring Data 仓库,如下例所示:
@EnableGemfireRepositories 引导 Apache Geode 的 Spring Data 仓库@SpringBootApplication
@EnableGemfireRepositories(basePackages = "com.example.acme.repository")
class SpringDataApplication {
...
}
与其使用 basePackages 属性,您可能更倾向于使用类型安全的 basePackageClasses 属性。
通过 basePackageClasses,您只需指定应用程序中任意一个 Repository 接口类型,即可确定包含所有 Repository 类的包。
建议在每个包中创建一个特殊的无操作(no-op)标记类或接口,其唯一用途就是标识该属性所引用的应用程序 Repository 的位置。
除了 basePackages and basePackageClasses 属性(例如 Spring 的
@ComponentScan 注解)之外,
@EnableGemfireRepositories 注解还提供了基于 Spring
ComponentScan.Filter 类型的包含和排除过滤器。
您可以使用 filterType 属性根据各种方面进行过滤,例如应用程序 Repository 类型是否标注了特定注解或扩展了特定类类型等。请参阅
FilterType Javadoc
以获取更多详细信息。
@EnableGemfireRepositories 注解还允许你通过 Properties 属性指定命名 OQL 查询的位置,这些查询位于一个 Java namedQueriesLocation 文件中。属性名必须与 Repository 查询方法的名称相匹配,而属性值则是调用该 Repository 查询方法时要执行的 OQL 查询。
如果您的应用程序需要一个或多个自定义仓库实现,可以将Impl属性设置为其他值(默认值为https://docs.spring.io/spring-data/commons/docs/current/reference/html/#repositories.custom-implementations)。
此功能通常用于扩展Spring Data Repository基础设施,以实现数据存储本身未提供的功能(例如,SDG)。
在使用 Apache Geode 时,一个需要自定义 Repository 实现的典型场景是执行连接(joins)操作。
SDG Repository 不支持连接操作。对于 Apache Geode 的 PARTITION Region,连接操作必须在共置(collocated)的 PARTITION Region 上执行,因为 Apache Geode 不支持“分布式”连接。
此外,等值连接(Equi-Join)OQL 查询必须在 Apache Geode 函数(Function)内部执行。
有关 Apache Geode 等值连接查询(Equi-Join Queries) 的更多详细信息,请参见此处。
SDG 存储库基础设施扩展的许多其他方面也可以进行自定义。有关所有配置设置的详细信息,请参阅
@EnableGemfireRepositories
Javadoc。
10.3. 执行 OQL 查询
Spring Data for Apache Geode 仓库支持定义查询方法,以便轻松针对托管实体所映射的 Region 执行 Apache Geode OQL 查询,如下例所示:
@Region("People")
public class Person { … }
public interface PersonRepository extends CrudRepository<Person, Long> {
Person findByEmailAddress(String emailAddress);
Collection<Person> findByFirstname(String firstname);
@Query("SELECT * FROM /People p WHERE p.firstname = $1")
Collection<Person> findByFirstnameAnnotated(String firstname);
@Query("SELECT * FROM /People p WHERE p.firstname IN SET $1")
Collection<Person> findByFirstnamesAnnotated(Collection<String> firstnames);
}
前面示例中列出的第一个查询方法会生成如下 OQL 查询语句:
SELECT x FROM /People x WHERE x.emailAddress = $1。第二个查询方法的工作方式相同,不同之处在于它返回所有找到的实体,而第一个查询方法则期望只找到一个结果。
如果所支持的关键字不足以声明和表达您的 OQL 查询,或者方法名称变得过于冗长,那么您可以像第三和第四个方法所示那样,使用 @Query 注解来标注查询方法。
下表简要列出了您可以在查询方法中使用的受支持关键字示例:
| 关键字 | 示例 | 逻辑结果 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(无关键字) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10.4. 使用注解的 OQL 查询扩展
许多查询语言(例如 Apache Geode 的 OQL(对象查询语言))都包含一些扩展功能,这些功能并未被 Spring Data Commons 的 Repository 基础设施直接支持。
Spring Data Commons 的 Repository 基础设施目标之一是充当最低的通用标准,以维持对当今应用程序开发中广泛使用和可用的各种数据存储的支持与可移植性。从技术上讲,这意味着开发人员可以在其应用程序中通过复用现有的应用特定 Repository 接口,访问 Spring Data Commons 所支持的多种不同数据存储——这是一种便捷而强大的抽象。
为了支持 Apache Geode 的 OQL 查询语言扩展,并确保在不同数据存储之间保持可移植性, Apache Geode 的 Spring Data 通过使用 Java 注解来添加对 OQL 查询扩展的支持。这些注解会被其他不具备类似 查询语言特性的 Spring Data Repository 实现(例如 Spring Data JPA 或 Spring Data Redis)忽略。
例如,许多数据存储很可能并未实现 Apache Geode 的 OQL IMPORT 关键字。将 IMPORT 实现为注解(即 @Import),而不是作为查询方法签名(特别是方法名)的一部分,这样在解析查询方法名以构造适用于其他数据存储语言的查询时,就不会干扰解析基础设施。
目前,Spring Data for Apache Geode 支持的 Apache Geode OQL 查询语言扩展包括:
| 关键字 | 注解 | 描述 | 参数 |
|---|---|---|---|
|
OQL 查询索引提示 |
|
|
|
限定应用程序特定的类型。 |
|
|
|
限制返回的查询结果集。 |
|
|
|
启用 OQL 查询特定的调试功能。 |
NA |
例如,假设你有一个 Customers 应用领域类及其对应的 Apache Geode Region,
以及一个 CustomerRepository 和一个用于根据姓氏查找 Customers 的查询方法,如下所示:
package ...;
import org.springframework.data.annotation.Id;
import org.springframework.data.gemfire.mapping.annotation.Region;
...
@Region("Customers")
public class Customer ... {
@Id
private Long id;
...
}
package ...;
import org.springframework.data.gemfire.repository.GemfireRepository;
...
public interface CustomerRepository extends GemfireRepository<Customer, Long> {
@Trace
@Limit(10)
@Hint("LastNameIdx")
@Import("org.example.app.domain.Customer")
List<Customer> findByLastName(String lastName);
...
}
前面的示例将生成以下 OQL 查询:
<TRACE> <HINT 'LastNameIdx'> IMPORT org.example.app.domain.Customer; SELECT * FROM /Customers x WHERE x.lastName = $1 LIMIT 10
当 OQL 注解扩展与 @Query 注解结合使用时,Apache Geode 的 Spring Data Repository 扩展会谨慎避免产生冲突的声明。
再举一个例子,假设你在 @Query 中定义了一个带有 CustomerRepository 注解的原始查询方法,如下所示:
public interface CustomerRepository extends GemfireRepository<Customer, Long> {
@Trace
@Limit(10)
@Hint("CustomerIdx")
@Import("org.example.app.domain.Customer")
@Query("<TRACE> <HINT 'ReputationIdx'> SELECT DISTINCT * FROM /Customers c WHERE c.reputation > $1 ORDER BY c.reputation DESC LIMIT 5")
List<Customer> findDistinctCustomersByReputationGreaterThanOrderByReputationDesc(Integer reputation);
}
上述查询方法将生成以下 OQL 查询:
IMPORT org.example.app.domain.Customer; <TRACE> <HINT 'ReputationIdx'> SELECT DISTINCT * FROM /Customers x
WHERE x.reputation > $1 ORDER BY c.reputation DESC LIMIT 5
@Limit(10) 注解不会覆盖原始查询中显式定义的 LIMIT。
此外,@Hint("CustomerIdx") 注解也不会覆盖原始查询中显式定义的 HINT。
最后,@Trace 注解是多余的,不会产生任何额外效果。
|
|
10.5. 查询后处理
得益于使用了 Spring Data Repository 抽象,定义数据存储特定查询(例如 OQL)的查询方法约定变得简单而便捷。然而,有时我们仍然希望检查甚至可能修改由 Repository 查询方法生成的查询。
从 2.0.x 版本起,Spring Data for Apache Geode 引入了 o.s.d.gemfire.repository.query.QueryPostProcessor 函数式接口。
该接口的定义大致如下:
package org.springframework.data.gemfire.repository.query;
import org.springframework.core.Ordered;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.QueryMethod;
import ...;
@FunctionalInterface
interface QueryPostProcessor<T extends Repository, QUERY> extends Ordered {
QUERY postProcess(QueryMethod queryMethod, QUERY query, Object... arguments);
}
还提供了额外的默认方法,允许你以类似于 java.util.function.Function.andThen(:Function) 和 java.util.function.Function.compose(:Function) 的方式组合 https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html#compose-java.util.function.Function- 实例。
此外,QueryPostProcessor 接口实现了
org.springframework.core.Ordered 接口,
当在 Spring 容器中声明和注册多个 QueryPostProcessors 并用于为生成的一组查询方法构建处理管道时,此功能非常有用。
最后,QueryPostProcessor 接受对应于类型参数 T 和 QUERY 的类型参数。类型 T 扩展了 Spring Data Commons 标记接口
org.springframework.data.repository.Repository。
我们将在本节稍后进一步讨论这一点。在 Apache Geode 的 Spring Data 案例中,所有 QUERY 类型参数均为 java.lang.String 类型。
将查询定义为 QUERY 类型非常有用,因为此 QueryPostProcessor 接口可能会被移植到 Spring Data Commons 中,因此必须能够处理不同数据存储(例如 JPA、MongoDB 或 Redis)的各种形式的查询。 |
你可以实现此接口,以便在调用应用程序的 Repository 接口方法时,接收一个回调,其中包含该方法所生成的查询。
例如,你可能希望记录来自所有应用程序 Repository 接口定义的所有查询。你可以通过使用以下 QueryPostProcessor 实现来做到这一点:
package example;
import ...;
class LoggingQueryPostProcessor implements QueryPostProcessor<Repository, String> {
private Logger logger = Logger.getLogger("someLoggerName");
@Override
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
String message = String.format("Executing query [%s] with arguments [%s]", query, Arrays.toString(arguments));
this.logger.info(message);
}
}
LoggingQueryPostProcessor 被限定为 Spring Data 的 org.springframework.data.repository.Repository 标记接口,因此会记录所有应用程序 Repository 接口查询方法生成的查询语句。
您可以将此日志记录的范围限制为仅来自特定类型的应用程序 Repository 接口的查询,
例如,比如 CustomerRepository,如下例所示:
interface CustomerRepository extends CrudRepository<Customer, Long> {
Customer findByAccountNumber(String accountNumber);
List<Customer> findByLastNameLike(String lastName);
}
然后,您可以将 LoggingQueryPostProcessor 明确地指定给 CustomerRepository,如下所示:
class LoggingQueryPostProcessor implements QueryPostProcessor<CustomerRepository, String> { .. }
因此,只有在 CustomerRepository 接口中定义的查询(例如 findByAccountNumber)会被记录日志。
你可能希望为 Repository 查询方法定义的特定查询创建一个 QueryPostProcessor。例如,
假设你想限制由 CustomerRepository.findByLastNameLike(:String) 查询方法生成的 OQL 查询,
使其仅返回五个结果,并按 Customers 字段以升序对 firstName 进行排序。为此,
你可以定义一个自定义的 QueryPostProcessor,如下例所示:
class OrderedLimitedCustomerByLastNameQueryPostProcessor implements QueryPostProcessor<CustomerRepository, String> {
private final int limit;
public OrderedLimitedCustomerByLastNameQueryPostProcessor(int limit) {
this.limit = limit;
}
@Override
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
return "findByLastNameLike".equals(queryMethod.getName())
? query.trim()
.replace("SELECT", "SELECT DISTINCT")
.concat(" ORDER BY firstName ASC")
.concat(String.format(" LIMIT %d", this.limit))
: query;
}
}
尽管前面的示例可以正常工作,但你可以通过使用 Spring Data for Apache Geode 提供的 Spring Data Repository 约定来实现相同的效果。例如,相同的查询可以定义如下:
interface CustomerRepository extends CrudRepository<Customer, Long> {
@Limit(5)
List<Customer> findDistinctByLastNameLikeOrderByFirstNameDesc(String lastName);
}
然而,如果你无法控制应用程序的 CustomerRepository 接口定义,那么使用 QueryPostProcessor(即 OrderedLimitedCustomerByLastNameQueryPostProcessor)会更加方便。
如果你希望确保 LoggingQueryPostProcessor 始终排在其他由应用程序定义的、可能已在 Spring QueryPostProcessors 中声明并注册的 ApplicationContext 之后,可以通过重写 order 方法来设置 o.s.core.Ordered.getOrder() 属性,如下例所示:
order 属性class LoggingQueryPostProcessor implements QueryPostProcessor<Repository, String> {
@Override
int getOrder() {
return 1;
}
}
class CustomerQueryPostProcessor implements QueryPostProcessor<CustomerRepository, String> {
@Override
int getOrder() {
return 0;
}
}
这确保了在QueryPostProcessors记录查询之前,你始终能看到其他LoggingQueryPostProcessor所应用的后处理效果。
你可以在 Spring QueryPostProcessors 中定义任意多个 ApplicationContext,并以任意顺序将它们应用到所有或特定的应用程序 Repository 接口上。通过使用 postProcess(..) 方法回调所提供的参数,你可以实现任意细粒度的控制。
11. 函数执行的注解支持
Spring Data for Apache Geode 提供了注解支持,以简化使用 Apache Geode 的函数执行。
在底层,Apache Geode API 提供了用于实现和注册 Apache Geode 函数 的类,这些函数会被部署到 Apache Geode 服务器上,随后可由其他对等成员应用程序或远程缓存客户端调用。
函数可以在集群中的多个 Apache Geode 服务器之间并行执行,使用 MapReduce 模式对结果进行聚合,并将结果返回给调用方。函数也可以指定在单个服务器或 Region 上运行。Apache Geode API 支持通过多种预定义的作用域来远程执行函数,例如:在 Region 上、在成员(分组内)上、在服务器上等。与任何 RPC 协议一样,远程函数的实现和执行需要编写一些样板代码。
Spring Data for Apache Geode 秉承 Spring 的核心价值主张,旨在隐藏远程函数执行的底层机制,让您专注于核心的 POJO 编程和业务逻辑。为此,Spring Data for Apache Geode 引入了注解,用于以声明式方式将 POJO 类的公共方法注册为 Apache Geode 函数,并支持通过带注解的接口调用已注册的函数(包括远程调用)。
11.1. 实现与执行
有两个独立的问题需要解决:实现和执行。
首先是函数实现(服务端),它必须与
FunctionContext
交互以访问调用参数,
ResultsSender用于发送结果,
以及其他执行上下文信息。函数实现通常访问缓存和区域,并在唯一 ID 下注册到
FunctionService。
调用 Function 的缓存客户端应用程序不依赖于具体实现。要调用 Function,
应用程序需实例化一个
Execution
并提供 Function ID、调用参数以及定义其作用域(Region、server、servers、member 或 members)的 Function 目标。
如果 Function 产生结果,调用者将使用
ResultCollector
来聚合并获取执行结果。在某些情况下,需要自定义 ResultCollector 实现,
并可通过 Execution 进行注册。
此处的“客户端”(Client)和“服务器”(Server)是在函数执行(Function execution)的上下文中使用的,其含义可能与 Apache Geode 客户端-服务器拓扑结构中的客户端和服务器有所不同。虽然通常使用 ClientCache 实例的应用程序会在集群中的一个或多个 Apache Geode 服务器上调用函数,但也可以在对等(peer-to-peer,P2P)配置中执行函数,此时应用程序本身就是集群的一个成员,并托管一个对等的 Cache 实例。请注意,作为对等成员的缓存应用程序需遵守作为集群对等成员的所有约束条件。 |
11.2. 实现一个函数
通过使用 Apache Geode API,FunctionContext 提供了一个运行时调用上下文,其中包含客户端的调用参数以及一个 ResultSender 实现,用于将结果发送回客户端。此外,如果该函数在某个 Region 上执行,则 FunctionContext 实际上是 RegionFunctionContext 的一个实例,它提供了额外的信息,例如函数被调用的目标 Region、与 Execution 关联的任何过滤器(一组特定的键)等。如果该 Region 是一个 PARTITION Region,则函数应使用 PartitionRegionHelper 来提取本地数据集。
通过使用 Spring,您可以编写一个简单的 POJO,并利用 Spring 容器将该 POJO 的一个或多个公共方法绑定到一个 Function 上。若要将 POJO 方法用作 Function,其方法签名通常必须符合客户端的执行参数要求。然而,在 Region 执行的情况下,Region 数据也可以被提供(假设如果该 Region 是 PARTITION 类型,则数据保存在本地分区中)。
此外,该 Function 可能还需要获取所应用的过滤器(如果有的话)。这表明客户端和服务器之间对调用参数有一个约定,但方法签名中可以包含额外的参数,用于传递由 FunctionContext 提供的值。一种可能的做法是让客户端和服务器共享一个公共接口,但这并非强制要求。唯一的约束是,在解析完额外参数之后,方法签名中必须包含与调用 Function 时所使用的相同顺序的调用参数。
例如,假设客户端提供了一个 String 和一个 int 作为调用参数。这些参数会以数组的形式包含在 FunctionContext 中,如下例所示:
Object[] args = new Object[] { "test", 123 };
Spring 容器应能够绑定到类似于以下形式的任何方法签名(暂时忽略返回类型):
public Object method1(String s1, int i2) { ... }
public Object method2(Map<?, ?> data, String s1, int i2) { ... }
public Object method3(String s1, Map<?, ?> data, int i2) { ... }
public Object method4(String s1, Map<?, ?> data, Set<?> filter, int i2) { ... }
public void method4(String s1, Set<?> filter, int i2, Region<?,?> data) { ... }
public void method5(String s1, ResultSender rs, int i2) { ... }
public void method6(FunctionContest context) { ... }
一般规则是:一旦解析了所有额外参数(即 Region 数据和过滤器),剩余的参数必须在顺序和类型上与函数方法所期望的参数完全对应。
该方法的返回类型必须为 void,或者是一个可序列化的类型(如 java.io.Serializable、DataSerializable 或 PdxSerializable)。
后者(即可序列化性)也是调用参数的一项要求。
Region 数据通常应定义为 Map 类型,以便于单元测试,但在必要时也可以是 Region 类型。如上例所示,如果需要控制结果如何返回给客户端,也可以直接传递 FunctionContext 本身或 ResultSender。
11.2.1. 函数实现注解
以下示例展示了如何使用 SDG 的 Function 注解将 POJO 方法暴露为 Apache Geode 函数:
@Component
public class ApplicationFunctions {
@GemfireFunction
public String function1(String value, @RegionData Map<?, ?> data, int i2) { ... }
@GemfireFunction(id = "myFunction", batchSize=100, HA=true, optimizedForWrite=true)
public List<String> function2(String value, @RegionData Map<?, ?> data, int i2, @Filter Set<?> keys) { ... }
@GemfireFunction(hasResult=true)
public void functionWithContext(FunctionContext functionContext) { ... }
}
注意,该类本身必须注册为 Spring Bean,并且每个 Apache Geode Function 都必须使用
@GemfireFunction 注解。在前面的示例中,使用了 Spring 的 @Component 注解,但您可以使用 Spring 支持的任何方法(例如 XML 配置或使用 Spring Boot 时的 Java 配置类)来注册该 Bean。
这使得 Spring 容器能够创建该类的实例,并将其封装在
PojoFunctionWrapper 中。
Spring 会为每个使用 @GemfireFunction 注解的方法创建一个包装器实例。每个包装器实例共享同一个目标对象实例,以调用相应的方法。
POJO 函数类作为 Spring Bean 可能会带来其他好处。由于它与 Apache Geode 组件(例如缓存和 Region)共享 ApplicationContext,因此在必要时可以将这些组件注入到该类中。 |
Spring 会创建包装类,并将函数注册到 Apache Geode 的 FunctionService 中。用于注册每个函数的函数 ID 必须是唯一的。按照约定,该 ID 默认为方法的简单(非限定)名称。也可以通过使用 id 注解的 @GemfireFunction 属性显式指定该名称。
@GemfireFunction 注解还提供其他配置属性:HA 和 optimizedForWrite,
它们对应于 Apache Geode 的
Function 接口中定义的属性。
如果 POJO 函数方法的返回类型为 void,则 hasResult 属性将自动设置为 false。
否则,如果方法返回一个值,则 hasResult 属性将设置为 true。即使对于 void 方法返回类型,也可以将 GemfireFunction 注解的 hasResult 属性设置为 true 以覆盖此约定,
如前文所示的 functionWithContext 方法所示。可以推测,其意图是让您直接使用 ResultSender 将结果发送给调用者。
最后,GemfireFunction 注解支持 requiredPermissions 属性,该属性用于指定执行该函数所需的权限。默认情况下,所有函数都需要 DATA:WRITE 权限。<RESOURCE>:<OPERATION>:[Target]:[Key] 属性接受一个字符串数组,允许您根据应用程序和/或函数的使用场景(UC)按需修改权限。
每个资源权限应采用以下格式:4。
RESOURCE 可以是 {data-store-javadoc]/org/apache/geode/security/ResourcePermission.Resource.html[ResourcePermission.Resource] 枚举值之一。OPERATION 可以是 {data-store-javadoc}/org/apache/geode/security/ResourcePermission.Operation.html[ResourcePermission.Operation] 枚举值之一。可选地,Target 可以是某个 Region 的名称,或者是 {data-store-javadoc}/org/apache/geode/security/ResourcePermission.Target.html[ResourcePermission.Target] 枚举值之一。最后,如果指定了 Key,还可以选择性地指定 Target,它必须是该 8 Region 中的一个有效键。
PojoFunctionWrapper 实现了 Apache Geode 的 Function 接口,在其 execute() 方法中绑定方法参数并调用目标方法。它还通过使用 ResultSender 将方法的返回值发送回调用方。
11.2.2. 分组结果
如果返回类型是数组或Collection,则需要考虑如何返回结果。
默认情况下,PojoFunctionWrapper会一次性返回整个数组或Collection。如果数组或Collection中的元素数量非常大,可能会带来性能损耗。为了将有效载荷划分为更小、更易管理的块,您可以设置batchSize属性,如前面所示的function2示例中所演示的那样。
如果你需要对 ResultSender 进行更精细的控制,特别是当方法本身在创建 Collection 时会占用过多内存的情况下,你可以将 ResultSender 作为参数传入,或者通过 FunctionContext 获取它,并在方法内部直接使用它将结果发送回调用方。 |
11.2.3. 启用注解处理
根据 Spring 的标准,您必须显式启用对 @GemfireFunction 注解的注解处理。以下示例展示了如何通过 XML 启用注解处理:
<gfe:annotation-driven/>
以下示例通过为 Java 配置类添加注解来启用注解处理:
@Configuration
@EnableGemfireFunctions
class ApplicationConfiguration { ... }
11.3. 执行函数
调用远程函数(Function)的过程需要提供该函数的 ID、调用参数、执行目标(onRegion、onServers、onServer、onMember 或 onMembers),以及(可选的)一个过滤器集合。通过使用 Spring Data for Apache Geode,您只需定义一个带有注解支持的接口即可。Spring 会为该接口创建一个动态代理,该代理利用 FunctionService 来创建 Execution、调用 Execution,并在必要时将结果强制转换为所定义的返回类型。这种技术与 Spring Data for Apache Geode 的 Repository 扩展的工作方式类似,因此其中一些配置和概念应该会让您感到熟悉。
通常,一个接口定义会映射到多个函数执行,每个接口中定义的方法对应一次函数执行。
11.3.1. 函数执行的注解
为了支持客户端函数执行,提供了以下 SDG 函数注解:@OnRegion、
@OnServer、@OnServers、@OnMember和@OnMembers。这些注解对应于 Apache Geode 提供的
FunctionService类的Execution实现。
每个注解都暴露了相应的属性。这些注解还提供了一个可选的 resultCollector 属性,其值是实现 ResultCollector 接口的 Spring Bean 的名称,用于执行。
| 代理接口将所有声明的方法绑定到相同的执行配置。尽管通常预期使用单方法接口,但该接口中的所有方法均由同一个代理实例提供支持,因此它们共享相同的配置。 |
以下列表展示了一些示例:
@OnRegion(region="SomeRegion", resultCollector="myCollector")
public interface FunctionExecution {
@FunctionId("function1")
String doIt(String s1, int i2);
String getString(Object arg1, @Filter Set<Object> keys);
}
默认情况下,函数 ID 为方法的简单名称(不带限定符)。可以使用 @FunctionId 注解将此调用绑定到不同的函数 ID。
11.3.2. 启用注解处理
客户端使用 Spring 的类路径组件扫描功能来发现带注解的接口。要在 XML 中启用函数执行注解处理,请在您的 XML 配置中插入以下元素:
<gfe-data:function-executions base-package="org.example.myapp.gemfire.functions"/>
function-executions 元素位于 gfe-data XML 命名空间中。base-package 属性是必需的,
以避免扫描整个类路径。可以按照 Spring
参考文档 中所述提供额外的过滤器。
您可以选择性地按如下方式为您的 Java 配置类添加注解:
@EnableGemfireFunctionExecutions(basePackages = "org.example.myapp.gemfire.functions")
11.4. 程序化函数执行
使用上一节中定义的带有函数执行注解的接口,只需将该接口自动装配到一个应用程序 Bean 中,即可调用该函数:
@Component
public class MyApplication {
@Autowired
FunctionExecution functionExecution;
public void doSomething() {
functionExecution.doIt("hello", 123);
}
}
或者,您可以直接使用一个函数执行模板。在下面的示例中,GemfireOnRegionFunctionTemplate 创建了一个 onRegion 函数的 Execution:
GemfireOnRegionFunctionTemplateSet<?, ?> myFilter = getFilter();
Region<?, ?> myRegion = getRegion();
GemfireOnRegionOperations template = new GemfireOnRegionFunctionTemplate(myRegion);
String result = template.executeAndExtract("someFunction", myFilter, "hello", "world", 1234);
在内部,Function Executions 始终返回一个 List。executeAndExtract 假设该 List 仅包含一个结果元素,并尝试将该值强制转换为所请求的类型。此外,还有一个 execute 方法,它会原样返回该 List。List 方法的第一个参数是函数 ID,filter 参数是可选的,其余参数是一个可变参数 7。
11.5. 使用 PDX 执行函数
在将 Spring Data for Apache Geode 的 Function 注解支持与 Apache Geode 的 PDX 序列化结合使用时,有一些注意事项需要牢记。
如本节前面所述,并举例说明,您通常应使用带有 Spring Data for Apache Geode 函数注解的 POJO 类来定义 Apache Geode 函数,如下所示:
public class OrderFunctions {
@GemfireFunction(...)
Order process(@RegionData data, Order order, OrderSource orderSourceEnum, Integer count) { ... }
}
Integer 参数的 count 类型是任意的,Order 类与 OrderSource 枚举的分离也是如此,这两者在逻辑上可能是可以合并的。然而,参数之所以这样设置,是为了演示在 PDX 上下文中使用 Function 执行时所遇到的问题。 |
您的 Order 类和 OrderSource 枚举可能定义如下:
public class Order ... {
private Long orderNumber;
private LocalDateTime orderDateTime;
private Customer customer;
private List<Item> items
...
}
public enum OrderSource {
ONLINE,
PHONE,
POINT_OF_SALE
...
}
当然,你可以定义一个 Function Execution 接口来调用 Apache Geode 服务器上的 'process' 函数,如下所示:
@OnServer
public interface OrderProcessingFunctions {
Order process(Order order, OrderSource orderSourceEnum, Integer count);
}
显然,这个 process(..) Order 函数是从客户端通过一个 ClientCache 实例(即 <gfe:client-cache/>)调用的。这意味着函数参数也必须是可序列化的。在集群中对等节点之间调用对等成员函数(例如 @OnMember(s))时也是如此。任何形式的distribution都要求在客户端与服务器(或对等节点)之间传输的数据必须经过序列化。
现在,如果你已将 Apache Geode 配置为使用 PDX 进行序列化(而不是例如 Java 序列化),
你还可以在 Apache Geode 服务器的配置中将 pdx-read-serialized 属性设置为 true,
如下所示:
<gfe:cache ... pdx-read-serialized="true"/>
或者,您可以将 Apache Geode 缓存客户端应用程序的 pdx-read-serialized 属性设置为 true,如下所示:
<gfe:client-cache ... pdx-read-serialized="true"/>
这样做会导致从缓存(即 Region)中读取的所有值以及在客户端与服务器(或对等节点)之间传递的信息都保持序列化形式,包括但不限于函数参数。
Apache Geode 仅序列化您已通过 Apache Geode 的
ReflectionBasedAutoSerializer
进行配置(注册)的应用程序域对象类型,
或者(推荐方式)通过自定义的 Apache Geode
PdxSerializer
进行配置。
如果您使用 Spring Data for Apache Geode 的 Repository 扩展,甚至可以考虑使用 Spring Data for Apache Geode 的
MappingPdxSerializer,
该功能利用实体的映射元数据来确定从应用程序域对象序列化为 PDX 实例的数据。
然而不太明显的是,Apache Geode 会自动处理 Java Enum 类型,无论它们是否被显式配置(即,通过 ReflectionBasedAutoSerializer 使用正则表达式模式和 classes 参数进行注册,或由“自定义”的 Apache Geode PdxSerializer 处理),尽管 Java 枚举实现了 java.io.Serializable 接口。
因此,当你在注册了 Apache Geode 函数(包括使用 Spring Data for Apache Geode 的 @Function 注解的 POJO 类)的 Apache Geode 服务器上将 pdx-read-serialized 设置为 true 时,在调用函数 Execution 时可能会遇到意外行为。
调用该函数时,您可能会传递以下参数:
orderProcessingFunctions.process(new Order(123, customer, LocalDateTime.now(), items), OrderSource.ONLINE, 400);
然而,服务器端的 Apache Geode 函数会收到以下内容:
process(regionData, order:PdxInstance, :PdxInstanceEnum, 400);
Order 和 OrderSource 已作为 PDX 实例 传递给函数。
再次强调,这一切之所以发生,是因为 pdx-read-serialized 被设置为 true,这在 Apache Geode 服务器需要与多种不同客户端交互的场景中可能是必要的(例如,同时使用 Java 客户端和原生客户端,如 C/C++、C# 等)。
这与 Apache Geode 的 Spring Data 所倡导的强类型、带有 @Function 注解的 POJO 类方法签名背道而驰,因为在这些方法签名中,你理应期望看到的是应用程序领域对象类型,而不是 PDX 序列化的实例。
因此,Spring Data for Apache Geode 提供了增强的函数(Function)支持,可自动将 PDX 类型的方法参数转换为函数方法签名(参数类型)所定义的目标应用程序领域对象类型。
然而,这也要求您在 Apache Geode 服务器上显式注册一个 Apache Geode PdxSerializer,
因为 Spring Data for Apache Geode 中带有 Function 注解的 POJO 会在这些服务器上注册并使用,如下例所示:
<bean id="customPdxSerializer" class="x.y.z.gemfire.serialization.pdx.MyCustomPdxSerializer"/>
<gfe:cache ... pdx-serializer-ref="customPdxSerializeer" pdx-read-serialized="true"/>
或者,您可以使用 Apache Geode 的
ReflectionBasedAutoSerializer
以方便操作。当然,我们建议您在可能的情况下使用自定义 PdxSerializer,以便对序列化策略进行更细粒度的控制。
最后,如果您以泛型方式处理函数参数,或者将其视为 Apache Geode 的 PDX 类型之一,那么 Apache Geode 的 Spring Data 将谨慎地不对您的函数参数进行转换,如下所示:
@GemfireFunction
public Object genericFunction(String value, Object domainObject, PdxInstanceEnum enum) {
...
}
Spring Data for Apache Geode 仅在相应的应用程序领域类型位于类路径上,并且使用 @Function 注解的 POJO 方法期望该类型时,才会将 PDX 类型的数据转换为对应的应用程序领域类型。
对于自定义、组合的特定于应用程序的 Apache Geode PdxSerializers 以及基于方法签名的适当 POJO 函数参数类型处理的良好示例,请参阅 Spring Data for Apache Geode 的
ClientCacheFunctionExecutionWithPdxIntegrationTest 类。
12. Apache Lucene 集成
Apache Geode 与 Apache Lucene 集成,使您能够使用 Lucene 查询对存储在 Apache Geode 中的数据进行索引和搜索。基于搜索的查询还支持对查询结果进行分页。
此外,Spring Data for Apache Geode 基于 Spring Data Commons 的投影基础设施,增加了对查询投影的支持。 此功能允许将查询结果按应用程序所需,投影到一流的应用程序领域类型中。
在执行任何基于 Lucene 搜索的查询之前,必须先创建一个 Lucene Index。LuceneIndex
可以在 Spring(Apache Geode 的 Spring Data)XML 配置中按如下方式创建:
<gfe:lucene-index id="IndexOne" fields="fieldOne, fieldTwo" region-path="/Example"/>
此外,Apache Lucene 允许为每个字段指定 分析器 ,如下例所示进行配置:
<gfe:lucene-index id="IndexTwo" lucene-service-ref="luceneService" region-path="/AnotherExample">
<gfe:field-analyzers>
<map>
<entry key="fieldOne">
<bean class="example.AnalyzerOne"/>
</entry>
<entry key="fieldTwo">
<bean class="example.AnalyzerTwo"/>
</entry>
</map>
</gfe:field-analyzers>
</gfe:lucene-index>
该 Map 可以指定为顶层的 bean 定义,并通过嵌套的 ref 元素中的 <gfe:field-analyzers> 属性进行引用,如下所示:
<gfe-field-analyzers ref="refToTopLevelMapBeanDefinition"/>。
Spring Data for Apache Geode 的 LuceneIndexFactoryBean API 以及 SDG 的 XML 命名空间还允许在创建 LuceneIndex 时指定 org.apache.geode.cache.lucene.LuceneSerializer。LuceneSerializer 可让您配置对象在被索引时如何转换为 Lucene 文档以用于索引。
以下示例展示了如何向 LuceneSerializer 添加一个 LuceneIndex:
<bean id="MyLuceneSerializer" class="example.CustomLuceneSerializer"/>
<gfe:lucene-index id="IndexThree" lucene-service-ref="luceneService" region-path="/YetAnotherExample">
<gfe:lucene-serializer ref="MyLuceneSerializer">
</gfe:lucene-index>
你也可以将 LuceneSerializer 指定为一个匿名的嵌套 Bean 定义,如下所示:
<gfe:lucene-index id="IndexThree" lucene-service-ref="luceneService" region-path="/YetAnotherExample">
<gfe:lucene-serializer>
<bean class="example.CustomLuceneSerializer"/>
</gfe:lucene-serializer>
</gfe:lucene-index>
或者,你也可以在 Spring Java 配置中,在一个 LuceneIndex 类内部声明或定义一个 @Configuration,如下例所示:
@Bean(name = "Books")
@DependsOn("bookTitleIndex")
PartitionedRegionFactoryBean<Long, Book> booksRegion(GemFireCache gemfireCache) {
PartitionedRegionFactoryBean<Long, Book> peopleRegion =
new PartitionedRegionFactoryBean<>();
peopleRegion.setCache(gemfireCache);
peopleRegion.setClose(false);
peopleRegion.setPersistent(false);
return peopleRegion;
}
@Bean
LuceneIndexFactoryBean bookTitleIndex(GemFireCache gemFireCache,
LuceneSerializer luceneSerializer) {
LuceneIndexFactoryBean luceneIndex = new LuceneIndexFactoryBean();
luceneIndex.setCache(gemFireCache);
luceneIndex.setFields("title");
luceneIndex.setLuceneSerializer(luceneSerializer);
luceneIndex.setRegionPath("/Books");
return luceneIndex;
}
@Bean
CustomLuceneSerializer myLuceneSerialier() {
return new CustomeLuceneSerializer();
}
Apache Geode 与 Apache Lucene 的集成和支持存在一些限制。
首先,LuceneIndex 只能创建在 Apache Geode 的 PARTITION 区域上。
其次,所有 LuceneIndexes 必须在其所应用的 Region 创建之前创建。
为了确保在 Spring 容器中声明的所有 LuceneIndexes 在其适用的 Region 之前被创建,SDG 包含了 org.springframework.data.gemfire.config.support.LuceneIndexRegionBeanFactoryPostProcessor。
您可以通过使用 <bean class="org.springframework.data.gemfire.config.support.LuceneIndexRegionBeanFactoryPostProcessor"/> 在 XML 配置中注册此 Spring BeanFactoryPostProcessor。
o.s.d.g.config.support.LuceneIndexRegionBeanFactoryPostProcessor 仅在使用 SDG XML 配置时可用。
有关 Spring 的 BeanFactoryPostProcessors 的更多详细信息,请点击此处。 |
这些 Apache Geode 限制在未来版本中可能不再适用,因此 SDG 的 LuceneIndexFactoryBean API 除了接受 Region 路径外,也直接接受对 Region 的引用。
当你希望在应用程序生命周期的后期、根据需求变化,为一个已包含数据的现有 Region 定义 LuceneIndex 时,这种方式更为理想。只要可能,Spring Data for GemFire(SDG)都力求遵循强类型对象的原则。然而,目前你必须使用 regionPath 属性来指定要应用 LuceneIndex 的 Region。
此外,在前面的示例中,请注意在 @DependsOn Region 的 Bean 定义上使用了 Spring 的 Books 注解。这会在 Books Region Bean 和 bookTitleIndex LuceneIndex Bean 定义之间建立依赖关系,确保在应用该索引的 Region 创建之前,先创建好 LuceneIndex。 |
现在,一旦我们有了一个LuceneIndex,就可以执行基于 Lucene 的数据访问操作,例如查询。
12.1. Lucene 模板数据访问器
Spring Data for Apache Geode 提供了两个主要的模板用于 Lucene 数据访问操作,具体取决于您的应用程序准备处理的抽象层级有多低。
LuceneOperations 接口通过使用 Apache Geode 的
Lucene 类型
来定义查询操作,这些类型在以下接口定义中给出:
public interface LuceneOperations {
<K, V> List<LuceneResultStruct<K, V>> query(String query, String defaultField [, int resultLimit]
, String... projectionFields);
<K, V> PageableLuceneQueryResults<K, V> query(String query, String defaultField,
int resultLimit, int pageSize, String... projectionFields);
<K, V> List<LuceneResultStruct<K, V>> query(LuceneQueryProvider queryProvider [, int resultLimit]
, String... projectionFields);
<K, V> PageableLuceneQueryResults<K, V> query(LuceneQueryProvider queryProvider,
int resultLimit, int pageSize, String... projectionFields);
<K> Collection<K> queryForKeys(String query, String defaultField [, int resultLimit]);
<K> Collection<K> queryForKeys(LuceneQueryProvider queryProvider [, int resultLimit]);
<V> Collection<V> queryForValues(String query, String defaultField [, int resultLimit]);
<V> Collection<V> queryForValues(LuceneQueryProvider queryProvider [, int resultLimit]);
}
[, int resultLimit] 表示 resultLimit 参数是可选的。 |
LuceneOperations 接口中的操作与 Apache Geode 的
LuceneQuery 接口所提供的操作相对应。
然而,Spring Data Geode(SDG)还额外提供了将 Apache Geode 或 Apache Lucene 特有的 Exceptions
转换为 Spring 高度一致且表达力强的 DAO
异常体系结构 的功能,
尤其是在许多现代数据访问操作涉及多个存储或仓库的情况下。
此外,SDG 的 LuceneOperations 接口可以在底层 Apache Geode 或 Apache Lucene API 发生破坏性接口变更时,保护您的应用程序免受其影响。
然而,如果提供的 Lucene 数据访问对象(DAO)仅使用 Apache Geode 和 Apache Lucene 的数据类型(例如 Apache Geode 的 LuceneResultStruct),那就太遗憾了。因此,Spring Data for Apache Geode(SDG)提供了 ProjectingLuceneOperations 接口,以解决这些重要的应用关注点。以下代码清单展示了 ProjectingLuceneOperations 接口的定义:
public interface ProjectingLuceneOperations {
<T> List<T> query(String query, String defaultField [, int resultLimit], Class<T> projectionType);
<T> Page<T> query(String query, String defaultField, int resultLimit, int pageSize, Class<T> projectionType);
<T> List<T> query(LuceneQueryProvider queryProvider [, int resultLimit], Class<T> projectionType);
<T> Page<T> query(LuceneQueryProvider queryProvider, int resultLimit, int pageSize, Class<T> projectionType);
}
ProjectingLuceneOperations 接口主要使用应用程序领域对象类型,使您可以直接操作应用程序数据。query 方法的各个变体接受一个投影类型,模板会利用 Spring Data Commons 的投影基础设施,将查询结果映射到给定投影类型的实例上。
此外,该模板将分页的 Lucene 查询结果包装在一个 Spring Data Commons 的 Page 抽象实例中。相同的投影逻辑仍可应用于页面中的结果,并且在访问集合中的每个页面时会进行懒加载投影。
举例来说,假设你有一个表示 Person 的类,如下所示:
class Person {
Gender gender;
LocalDate birthDate;
String firstName;
String lastName;
...
String getName() {
return String.format("%1$s %2$s", getFirstName(), getLastName());
}
}
此外,根据您的应用程序视图,您可能还需要一个单一的接口来将人员表示为Customers,如下所示:
interface Customer {
String getName()
}
如果我定义了以下 LuceneIndex……
@Bean
LuceneIndexFactoryBean personLastNameIndex(GemFireCache gemfireCache) {
LuceneIndexFactoryBean personLastNameIndex =
new LuceneIndexFactoryBean();
personLastNameIndex.setCache(gemfireCache);
personLastNameIndex.setFields("lastName");
personLastNameIndex.setRegionPath("/People");
return personLastNameIndex;
}
然后你可以将人员查询为 Person 对象,如下所示:
List<Person> people = luceneTemplate.query("lastName: D*", "lastName", Person.class);
或者,您也可以查询一个类型为 Page 的 Customer,如下所示:
Page<Customer> customers = luceneTemplate.query("lastName: D*", "lastName", 100, 20, Customer.class);
然后可以使用 Page 来获取结果的各个页面,如下所示:
List<Customer> firstPage = customers.getContent();
方便的是,Spring Data Commons 的 Page 接口也实现了 java.lang.Iterable<T>,使得遍历其内容变得非常简单。
Spring Data Commons 投影基础设施的唯一限制是,投影类型必须是一个接口。然而,可以扩展提供的 SDC 投影基础设施,并提供一个自定义的
ProjectionFactory
,该自定义实现使用 CGLIB 来生成作为投影实体的代理类。
你可以使用 setProjectionFactory(:ProjectionFactory) 在 Lucene 模板上设置一个自定义的 ProjectionFactory。
12.2. 注解配置支持
最后,Spring Data for Apache Geode 为 LuceneIndexes 提供了注解配置支持。
最终,SDG 的 Lucene 支持将被集成到 Apache Geode 的 Repository 基础设施扩展中,使得 Lucene 查询可以像当前的OQL 支持一样,通过应用程序的 #gemfire-repositories.queries.executing 接口中的方法来表达。
然而,在此期间,如果你想方便地表达 LuceneIndexes,你可以直接在你的应用程序领域对象上进行,如下例所示:
@PartitionRegion("People")
class Person {
Gender gender;
@Index
LocalDate birthDate;
String firstName;
@LuceneIndex;
String lastName;
...
}
要启用此功能,您必须使用 SDG 的注解配置支持,并特别配合 @EnableEntityDefineRegions 和 @EnableIndexing 注解,如下所示:
@PeerCacheApplication
@EnableEntityDefinedRegions
@EnableIndexing
class ApplicationConfiguration {
...
}
LuceneIndexes 只能在 Apache Geode 服务器上创建,因为 LuceneIndexes 仅适用于 PARTITION 区域。 |
根据我们之前对 Person 类的定义,SDG 注解配置支持会找到 Person 实体类的定义,并确定人员数据存储在一个名为 “People” 的 PARTITION Region 中,同时 Person 类在 Index 字段上有一个 OQL birthDate,并在 LuceneIndex 字段上有一个 lastName。
13. 在 Apache Geode 中引导 Spring ApplicationContext
通常,基于 Spring 的应用程序会利用 Spring Data for Apache Geode 的功能来引导启动 Apache Geode。
通过指定一个使用 Spring Data for Apache Geode XML 命名空间的 <gfe:cache/> 元素,
会在与应用程序相同的 JVM 进程中创建并初始化一个嵌入式的 Apache Geode 对等节点 Cache 实例,并采用默认配置。
然而,有时(可能是由于贵公司IT部门的要求)需要由 Apache Geode 提供的工具套件对 Apache Geode 进行完全管理和操作,例如使用 Gfsh。通过使用 Gfsh,Apache Geode 会引导启动你的 Spring ApplicationContext,而不是反过来由 Spring 来引导 Geode。此时,不再是应用服务器或使用 Spring Boot 的 Java 主类来启动应用,而是由 Apache Geode 负责引导并托管你的应用程序。
| Apache Geode 不是一个应用服务器。此外,在 Apache Geode 缓存配置方面,使用这种方法也存在一些限制。 |
13.1. 使用 Apache Geode 启动由 Gfsh 启动的 Spring 上下文
为了在使用 Gfsh 启动 Apache Geode 服务器时,在 Apache Geode 中引导 Spring ApplicationContext,
您必须使用 Apache Geode 的
initializer 功能。
初始化器块可以声明一个应用程序回调,该回调将在 Apache Geode 初始化缓存后启动。
通过使用 Apache Geode 原生 https://geode.apache.org/docs/guide/19/reference/topics/cache_xml.html#initializer 中的一小段配置,可以在 initializer 元素内声明一个初始化器。为了引导 Spring ApplicationContext,需要一个 cache.xml 文件,这与使用一小段 Spring XML 配置来引导启用了组件扫描的 Spring ApplicationContext 的方式非常相似(例如 <context:component-scan base-packages="…"/>)。
幸运的是,框架已经 conveniently 提供了这样一个初始化器:SpringContextBootstrappingInitializer。
以下示例展示了在 Apache Geode 的 cache.xml 文件中,此类的典型但最简配置:
<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns="http://geode.apache.org/schema/cache"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://geode.apache.org/schema/cache https://geode.apache.org/schema/cache/cache-1.0.xsd"
version="1.0">
<initializer>
<class-name>org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer</class-name>
<parameter name="contextConfigLocations">
<string>classpath:application-context.xml</string>
</parameter>
</initializer>
</cache>
SpringContextBootstrappingInitializer 类遵循与 Spring 的 ContextLoaderListener 类相似的约定,后者用于在 Web 应用程序中引导启动一个 Spring ApplicationContext,其中 ApplicationContext 的配置文件通过 contextConfigLocations Servlet 上下文参数指定。
此外,SpringContextBootstrappingInitializer 类也可以配合 basePackages 参数使用,
以指定一个逗号分隔的基包列表,这些基包中包含带有适当注解的应用程序组件。
Spring 容器会在类路径中搜索这些组件,以查找并创建 Spring Bean 及其他应用程序组件,
如下例所示:
<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns="http://geode.apache.org/schema/cache"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://geode.apache.org/schema/cache https://geode.apache.org/schema/cache/cache-1.0.xsd"
version="1.0">
<initializer>
<class-name>org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer</class-name>
<parameter name="basePackages">
<string>org.mycompany.myapp.services,org.mycompany.myapp.dao,...</string>
</parameter>
</initializer>
</cache>
然后,在启动 Apache Geode 服务器时,通过 Gfsh 命令行指定一个正确配置并构建好的 CLASSPATH 和 cache.xml 文件(如前所示),其命令行如下:
gfsh>start server --name=ExampleServer --log-level=config ...
--classpath="/path/to/application/classes.jar:/path/to/spring-data-geode-<major>.<minor>.<maint>.RELEASE.jar"
--cache-xml-file="/path/to/geode/cache.xml"
application-context.xml 可以是任意有效的 Spring 配置元数据,包括所有 SDG(Spring Data for Apache Geode)XML 命名空间元素。这种方法的唯一限制是无法使用 SDG XML 命名空间来配置 Apache Geode 缓存。换句话说,<gfe:cache/> 元素的任何属性(例如 cache-xml-location、properties-ref、critical-heap-percentage、pdx-serializer-ref、lock-lease 等)都不能被指定。如果指定了这些属性,它们将被忽略。
其原因在于,Apache Geode 本身在初始化器被调用之前就已经创建并初始化了缓存。因此,缓存已经存在,并且由于它是一个“单例”,无法被重新初始化,也无法对其配置进行任何增强。
13.2. 延迟装配 Apache Geode 组件
Spring Data for Apache Geode 已经通过使用 SDG 的 CacheListeners 类,为在 CacheLoaders 中由 Apache Geode 声明和创建的 Apache Geode 组件(例如 CacheWriters、cache.xml、WiringDeclarableSupport 等)提供了自动装配(auto-wiring)支持,如使用自动装配和注解进行配置一节所述。然而,这种方式仅在由 Spring 负责引导启动(即 Spring Boot启动 Apache Geode)时才有效。
当你的 Spring ApplicationContext 由 Apache Geode 启动时,这些 Apache Geode 应用组件不会被注意到,因为此时 Spring ApplicationContext 尚未存在。Spring ApplicationContext 只有在 Apache Geode 调用初始化代码块后才会被创建,而该初始化代码块的调用发生在所有其他 Apache Geode 组件(缓存、Region 等)已经创建并初始化完成之后。
为了解决这个问题,引入了一个新的 LazyWiringDeclarableSupport 类。这个新类能够感知 Spring 的 ApplicationContext。该抽象基类的设计意图是:任何实现该类的子类都会在 Apache Geode 调用初始化器并最终创建 Spring 容器后,自动向该 Spring 容器注册以接受配置。本质上,这使得您的 Apache Geode 应用组件有机会被 Spring 容器中定义的 Spring Bean 进行配置和自动装配。
为了让您的 Apache Geode 应用组件能够被 Spring 容器自动装配,您应当创建一个继承自 LazyWiringDeclarableSupport 的应用程序类,并对需要作为 Spring Bean 依赖注入的任何类成员添加注解,如下例所示:
public class UserDataSourceCacheLoader extends LazyWiringDeclarableSupport
implements CacheLoader<String, User> {
@Autowired
private DataSource userDataSource;
...
}
正如上面的 CacheLoader 示例所暗示的那样,你可能(尽管很少见)有必要在 Apache Geode 的 CacheListener 中同时定义一个 Region 和一个 cache.xml 组件。CacheLoader 在启动时可能需要访问应用程序的 Repository(或者可能是 Spring DataSource 中定义的 JDBC ApplicationContext),以便将 Users 加载到 Apache Geode 的 REPLICATE Region 中。
注意
以这种方式将 Apache Geode 和 Spring 容器的不同生命周期混合在一起时,请务必小心。
并非所有用例和场景都受支持。Apache Geode 的 cache.xml 配置类似于以下内容(该示例来自 SDG 的测试套件):
<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns="http://geode.apache.org/schema/cache"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://geode.apache.org/schema/cache https://geode.apache.org/schema/cache/cache-1.0.xsd"
version="1.0">
<region name="Users" refid="REPLICATE">
<region-attributes initial-capacity="101" load-factor="0.85">
<key-constraint>java.lang.String</key-constraint>
<value-constraint>org.springframework.data.gemfire.repository.sample.User</value-constraint>
<cache-loader>
<class-name>
org.springframework.data.gemfire.support.SpringContextBootstrappingInitializerIntegrationTest$UserDataStoreCacheLoader
</class-name>
</cache-loader>
</region-attributes>
</region>
<initializer>
<class-name>org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer</class-name>
<parameter name="basePackages">
<string>org.springframework.data.gemfire.support.sample</string>
</parameter>
</initializer>
</cache>
14. 示例应用程序
| 示例应用程序现在维护在 Spring Apache Geode 示例 代码仓库中。 |
Apache Geode 的 Spring Data 项目还包含一个示例应用程序。该示例应用程序名为“Hello World”,演示了如何在 Spring 应用程序中配置和使用 Apache Geode。运行时,该示例提供了一个 shell,允许您对数据网格执行各种命令。对于不熟悉核心组件或 Spring 与 Apache Geode 概念的开发人员来说,这是一个极佳的入门起点。
该示例随发行版一起提供,并基于 Maven。您可以将其导入任何支持 Maven 的 IDE(例如 Spring Tool Suite),也可以从命令行运行。
14.1. 你好世界
“Hello World”示例应用程序展示了 Spring Data for Apache Geode 项目的核心功能。 它会启动 Apache Geode,对其进行配置,在缓存上执行任意命令,并在应用程序退出时将其关闭。 可以同时启动该应用程序的多个实例,它们能够协同工作,无需任何用户干预即可共享数据。
在 Linux 下运行
如果在启动 Apache Geode 或相关示例时遇到网络问题,请尝试在命令行中添加以下系统属性 java.net.preferIPv4Stack=true(例如,-Djava.net.preferIPv4Stack=true)。
作为一种替代方案(全局修复方法,尤其适用于 Ubuntu 系统),请参见 SGF-28。 |
14.1.1. 启动和停止示例
“Hello World”示例应用程序被设计为一个独立的 Java 应用程序。它包含一个 main 类,该类既可以从您的 IDE(在 Eclipse 或 STS 中,通过 Run As/Java Application)启动,也可以通过 Maven 在命令行中使用 mvn exec:java 启动。如果类路径设置正确,您还可以直接对生成的构件使用 java 命令运行。
要停止该示例,请在命令行中输入 exit,或按 Ctrl+C 以停止 JVM 并关闭 Spring 容器。
14.1.2. 使用示例
启动后,该示例会创建一个共享数据网格,并允许您对其执行命令。 输出应类似于以下内容:
INFO: Created {data-store-name} Cache [Spring {data-store-name} World] v. X.Y.Z
INFO: Created new cache region [myWorld]
INFO: Member xxxxxx:50694/51611 connecting to region [myWorld]
Hello World!
Want to interact with the world ? ...
Supported commands are:
get <key> - retrieves an entry (by key) from the grid
put <key> <value> - puts a new entry into the grid
remove <key> - removes an entry (by key) from the grid
...
例如,要向网格中添加新项,您可以使用以下命令:
-> Bold Section qName:emphasis level:5, chunks:[put 1 unu] attrs:[role:bold]
INFO: Added [1=unu] to the cache
null
-> Bold Section qName:emphasis level:5, chunks:[put 1 one] attrs:[role:bold]
INFO: Updated [1] from [unu] to [one]
unu
-> Bold Section qName:emphasis level:5, chunks:[size] attrs:[role:bold]
1
-> Bold Section qName:emphasis level:5, chunks:[put 2 two] attrs:[role:bold]
INFO: Added [2=two] to the cache
null
-> Bold Section qName:emphasis level:5, chunks:[size] attrs:[role:bold]
2
可以同时运行多个实例。一旦启动,新的虚拟机将自动看到现有的区域及其信息,如下例所示:
INFO: Connected to Distributed System ['Spring {data-store-name} World'=xxxx:56218/49320@yyyyy]
Hello World!
...
-> Bold Section qName:emphasis level:5, chunks:[size] attrs:[role:bold]
2
-> Bold Section qName:emphasis level:5, chunks:[map] attrs:[role:bold]
[2=two] [1=one]
-> Bold Section qName:emphasis level:5, chunks:[query length = 3] attrs:[role:bold]
[one, two]
我们鼓励您尝试该示例,随意启动(和停止)任意多个实例,并在一个实例中运行各种命令,观察其他实例如何响应。为了保留数据,必须始终至少保持一个实例处于运行状态。如果所有实例都被关闭,网格数据将被完全销毁。
14.1.3. Hello World 示例说明
“Hello World” 示例同时使用了 Spring XML 和注解进行配置。初始的引导配置文件是 app-context.xml,它包含了在 cache-context.xml 文件中定义的缓存配置,并对类路径执行
组件扫描,以发现 Spring
组件。
缓存配置定义了 Apache Geode 缓存、一个区域(region),以及出于示例目的的一个充当记录器的 CacheListener。
主要的 Bean 是 HelloWorld 和 CommandProcessor,它们依赖于 GemfireTemplate 来与分布式结构进行交互。这两个类都使用注解来定义其依赖关系和生命周期回调。