第 7 章 设置持久性存储
Data Grid 可以向外部存储保留内存数据,为您提供管理数据的额外功能,例如:
- 持久性
- 通过添加缓存存储,您可以将数据持久保留到非易失性存储中,以便在重启后保留数据。
- 直写缓存
- 将 Data Grid 配置为持久性存储前面的缓存层简化了应用程序的数据访问,因为 Data Grid 处理与外部存储的所有交互。
- 数据溢出
- 使用驱除和传递技术可确保 Data Grid 只保留在内存中使用的数据,并将旧的条目写入持久性存储。
7.1. Data Grid Cache Stores
缓存存储将 Data Grid 连接到持久性数据源,并实现 NonBlockingStore
接口。
7.1.1. 配置缓存存储
以声明性方式或以编程方式在链中添加缓存存储到 Data Grid 缓存。缓存读取操作会按照配置的顺序检查每个缓存存储,直到它们找到有效的非null 元素。写入操作会影响所有缓存存储,但您配置为只读的缓存存储除外。
流程
-
使用
persistence
参数配置缓存的持久性层。 配置缓存存储是否对节点本地,还是在集群中共享。
以声明性方式使用共享属性,或者以编程方式使用
shared
(false)根据需要配置其他缓存存储属性。自定义缓存存储也可以包含
属性
参数。注意将缓存存储配置为共享或不共享(仅限本地)决定您应该设置哪些参数。在某些情况下,在缓存存储配置中使用错误的参数组合可能会导致数据丢失或性能问题。
例如,如果缓存存储对节点而言是本地的,则最好获取状态并在启动时清除。但是,如果缓存存储被共享,则您不应该在启动时获取状态或清除。
本地(非共享)文件存储
<persistence passivation="false"> <!-- note that class is missing and is induced by the fileStore element name --> <file-store shared="false" preload="true" fetch-state="true" read-only="false" purge="true" path="${java.io.tmpdir}"> <write-behind modification-queue-size="123" /> </file-store> </persistence>
共享自定义缓存存储
<local-cache name="myCustomStore"> <persistence passivation="false"> <store class="org.acme.CustomStore" fetch-state="false" preload="true" shared="false" purge="true" read-only="false" segmented="true"> <write-behind modification-queue-size="123" /> <property name="myProp">${system.property}</property> </store> </persistence> </local-cache>
单个文件存储
ConfigurationBuilder builder = new ConfigurationBuilder(); builder.persistence() .passivation(false) .addSingleFileStore() .preload(true) .shared(false) .fetchPersistentState(true) .ignoreModifications(false) .purgeOnStartup(true) .location(System.getProperty("java.io.tmpdir")) .async() .enabled(true)
7.1.2. 为基于文件的缓存存储设置全局持久位置
Data Grid 使用全局文件系统位置将数据保存到持久性存储。
全局持久位置对于每个 Data Grid 实例都必须是唯一的。要在多个实例间共享数据,请使用共享持久位置。
Data Grid 服务器使用 $RHDG_HOME/server/data
目录作为全局持久位置。
如果您使用 Data Grid 作为嵌入在自定义应用程序中的库,并且启用了 global-state,则全局持久位置默认为 user.dir
系统属性。此系统属性通常使用应用程序启动的目录。您应该将全局持久位置配置为使用合适的位置。
声明性配置
<cache-container default-cache="myCache"> <global-state> <persistent-location path="example" relative-to="my.data"/> </global-state> ... </cache-container>
new GlobalConfigurationBuilder().globalState().enable().persistentLocation("example", "my.data");
基于文件的缓存存储和全局持久位置
使用基于文件的缓存存储时,您可以选择为存储指定文件系统目录。除非声明绝对路径,否则目录始终相对于全局持久位置。
例如,您要配置全局持久位置,如下所示:
<global-state> <persistent-location path="/tmp/example" relative-to="my.data"/> </global-state>
然后,配置使用名为 myDataStore
的单个文件缓存存储,如下所示:
<file-store path="myDataStore"/>
在这种情况下,配置会在 /tmp/example/myDataStore/myCache.dat
中生成单一文件缓存存储
如果您试图设置位于全局持久位置外的绝对路径,并且启用了 global-state,Data Grid 会抛出以下异常:
ISPN000558: "The store location 'foo' is not a child of the global persistent location 'bar'"
7.1.3. passivation
传递配置 Data Grid,在从内存中驱除这些条目时写入条目以缓存存储。这样,传递可确保只维护一个条目的副本,可以是内存中或缓存存储中,这可以防止不必要的且可能昂贵的写入持久性存储。
当尝试访问传递的条目时,激活是从缓存存储中恢复内存的过程。因此,当启用 passivation 时,您必须配置实现 CacheWriter
和 CacheLoader
接口的缓存存储,以便可以从持久性存储写入和加载条目。
当 Data Grid 从缓存中驱除条目时,它会通知缓存监听程序被传递该条目,然后将条目存储在缓存存储中。当 Data Grid 获取被驱除条目的访问请求时,它会从缓存存储加载条目到内存中,然后通知缓存监听程序已激活该条目。
- 传递使用数据网格配置中的第一个缓存加载程序,并忽略所有其他缓存。
不支持传递:
- 事务存储.传递写入并从实际数据网格提交边界范围之外的存储中删除条目。
- 共享存储。共享缓存存储需要条目始终存在于存储中用于其他所有者。因此,不支持 passivation,因为无法删除条目。
如果您启用了使用事务存储或共享存储的传递,则数据网格会抛出异常。
7.1.3.1. 传递和缓存存储
禁用 passivation
写入内存中的数据会导致写入持久性存储。
如果 Data Grid 从内存驱除数据,则持久性存储中的数据会包括从内存驱除的条目。这样,持久性存储是内存缓存的超集。
如果没有配置驱除,则持久性存储中的数据会在内存中提供数据副本。
启用 passivation
只有在数据从内存中驱除数据时,Data Grid 才会将数据添加到持久性存储中。
当 Data Grid 激活条目时,它会在内存中恢复数据并从持久性存储中删除数据。这样,持久性存储中的数据和数据的数据形成了整个数据集的独立子集,两者之间没有交集。
使用共享缓存存储时,持久性存储中的条目可能会过时。这是因为 Data Grid 在激活时不会从共享缓存存储中删除传递的条目。
值会在内存中更新,但之前传递的条目会保留在持久性存储中,且超出日期值。
下表显示了在一系列操作后内存和持久性存储中的数据:
操作 | 禁用 passivation | 启用 passivation | 使用共享缓存存储启用传递 |
---|---|---|---|
插入 k1。 |
memory: k1 |
memory: k1 |
memory: k1 |
插入 k2. |
memory: k1, k2 |
memory: k1, k2 |
memory: k1, k2 |
驱除线程运行和驱除 k1。 |
memory: k2 |
memory: k2 |
memory: k2 |
读 k1。 |
memory: k1, k2 |
memory: k1, k2 |
memory: k1, k2 |
驱除线程运行和驱除 k2。 |
memory: k1 |
memory: k1 |
memory: k1 |
删除 k2。 |
memory: k1 |
memory: k1 |
memory: k1 |
7.1.4. cache Loaders 和 Transactional Caches
只有基于 JDBC 字符串的缓存存储支持事务操作。如果将缓存配置为事务性,您应该设置 transactional=true
,以便使数据存储在持久性存储中与内存中的数据同步。
对于所有其他缓存存储,Data Grid 不对事务操作中的缓存加载程序进行编码。如果事务在在内存中修改数据时成功修改数据,但不会完全应用缓存存储中的数据更改,这可能会导致数据不一致。在这种情况下,手动恢复无法使用缓存存储。
7.1.5. 分段缓存存储
缓存存储可以将数据组织到哪些键映射到的散列空间片段中。
分段存储提高了批量操作的读取性能;例如,流传输数据(Cache.size
、Cache.entrySet.stream
)、预加载缓存和执行状态传输操作。
但是,分段存储也可以丢失写操作的性能。此性能丢失适用于可以使用事务或写成存储进行批量写入操作。因此,您应该在启用分段存储前评估写操作的开销。如果写入操作的性能丢失了大量性能,则批量读取操作的性能可能无法接受。
为缓存存储配置的片段数量必须与您使用 cluster. hash.numSegments
参数在 Data Grid 配置中定义的片段数量匹配。
如果在添加分段缓存存储后更改配置中的 numSegments
参数,则 Data Grid 无法从该缓存存储中读取数据。
参考
7.1.6. 基于文件系统的缓存存储
在大多数情况下,基于文件系统的缓存存储适用于本地缓存存储,用于内存溢出的数据,因为它超过大小和/或时间限制。
您不应该在 NFS、Microsoft Windows 或 Samba 共享等共享文件系统上使用基于文件系统的缓存存储。共享文件系统不提供文件锁定功能,这可能导致数据崩溃。
同样,共享文件系统不是事务性的。如果您试图将事务缓存与共享文件系统搭配使用,则在提交阶段写入文件时可能会出现无法恢复的故障。
7.1.7. 直写
直写是一个缓存写入模式,写入内存和写入缓存存储是同步的。当客户端应用程序更新缓存条目时,在大多数情况下,Data Grid 不会返回调用,直到它更新缓存存储为止。这个缓存写入模式会导致对缓存存储更新会在客户端线程的界限内进行。
Write-Through 模式的主要优点是,缓存和缓存存储同时更新,这样可确保缓存存储始终与缓存一致。
但是,Write-Through 模式可能会降低性能,因为需要访问和更新缓存存储会直接为缓存操作添加延迟。
Data Grid 默认为 Write-Through 模式,除非您在缓存存储上明确配置 Write-Behind 模式。
直写配置
<persistence passivation="false"> <file-store fetch-state="true" read-only="false" purge="false" path="${java.io.tmpdir}"/> </persistence>
参考
7.1.8. write-Behind
write-Behind 是一个缓存写入模式,其中写入内存是同步的,写入缓存存储是异步的。
当客户端发送写入请求时,Data Grid 会将这些操作添加到修改队列中。在数据网格加入队列时处理操作,以便调用线程不会被阻止,操作会立即完成。
如果修改队列中的写入操作数量超过队列的大小,则数据网格会将这些额外的操作添加到队列中。但是,这些操作不会完成,直到已在队列中的 Data Grid 进程操作为止。
例如,调用 Cache.putAsync
立即返回,如果修改队列未满,则 Stage 也会立即完成。如果修改队列已满,或者 Data Grid 当前正在处理批处理写操作,则 Cache.putAsync
会立即返回,并且 Stage 稍后完成。
write-Behind 模式提供与 Write-Through 模式相比的性能优势,因为缓存操作不需要等待更新底层缓存存储完成。但是,在处理修改队列前,缓存存储中的数据与缓存中的数据不一致。因此,Write-Behind 模式适合具有低延迟的缓存存储,如不共享和本地基于文件系统的缓存存储,其中写入缓存和写入缓存存储之间的时间尽可能小。
write-behind 配置
<persistence passivation="false"> <file-store fetch-state="true" read-only="false" purge="false" path="${java.io.tmpdir}"> <write-behind modification-queue-size="123" fail-silently="true"/> </file-store> </persistence>
前面的配置示例使用 fail-silently
参数来控制缓存存储不可用时或修改队列已满时发生的情况。
-
如果
fail-silently="true"
,则 Data Grid 会记录 WARN 消息并拒绝写操作。 如果
fail-silently="false"
,如果数据网格在写入操作过程中检测到缓存存储不可用,则数据网格会抛出异常。同样,如果修改队列已满,Data Grid 会抛出异常。在某些情况下,如果修改队列中存在 Data Grid 重启和写操作,则数据丢失可能会发生。例如,缓存存储离线,但在检测缓存存储不可用时,写入操作会添加到修改队列中,因为它未满。如果 Data Grid 在缓存存储恢复在线前重启或者其他不可用,则修改队列中的写入操作将会丢失,因为它们没有保留。
参考