2.2. 分布式缓存


Data Grid 会尝试在缓存中保留任何条目的固定数量(配置为 numOwners )。这允许分布式缓存线性扩展,在向集群添加节点时存储更多数据。

当节点加入并离开集群时,当键有超过个或小于 numOwners 副本时,会出现一些时间。特别是,如果 numOwners 节点存在快速连续,某些条目将会丢失,因此我们表示分布式缓存容许 numOwners - 1 节点失败。

副本数代表在性能和数据的持久性之间权衡。您维护的更多副本,性能越低,但会降低由于服务器或网络故障而丢失数据的风险。

Data Grid 将密钥的所有者分成一个 主要所有者,它将协调对密钥的写入,以及零个或多个 备份所有者

下图显示了客户端发送到备份所有者的写入操作。在这种情况下,备份节点将写入转发到主所有者,然后将写入复制到备份。

图 2.2. 集群复制

图 2.3. 分布式缓存

读取操作

读取操作从主所有者请求值。如果主所有者没有以合理的时间响应,Data Grid 也从备份所有者请求值。

如果本地缓存中存在密钥,或者所有所有者都较慢,则读取操作可能需要 0 个信息,或者最多 2 个 * numOwners 信息。

写操作

写入操作最多会导致 2 个 * numOwners 信息。来自 originator 到主所有者和 numOwners - 1 消息从主到备份节点的消息以及相应的确认消息。

注意

缓存拓扑更改可能会导致读取和写入操作重试和额外信息。

同步或异步复制

不建议异步复制,因为它可能会丢失更新。除了丢失更新外,当线程写入到键时,异步分布式缓存也可以看到 stale 值,然后立即读取同一密钥。

Transactions

事务分布式缓存将 lock/prepare/commit/unlock 消息发送到受影响的节点,这意味着至少有一个密钥受事务影响。作为优化,如果事务写入单个密钥,并且 originator 是密钥的主所有者,则不会复制锁定信息。

2.2.1. 读取一致性

即使是同步复制,分布式缓存也不可线性。对于事务缓存,它们不支持序列化/快照隔离。

例如,线程正在执行单个放置请求:

cache.get(k) -> v1
cache.put(k, v2)
cache.get(k) -> v2
Copy to Clipboard Toggle word wrap

但是另一个线程可能会以不同顺序看到值:

cache.get(k) -> v2
cache.get(k) -> v1
Copy to Clipboard Toggle word wrap

该原因是从任何所有者返回值,具体取决于主所有者回复的速度。写入不是所有所有者的原子性。实际上,只有在从备份收到确认后,才会提交更新。在主等待备份的确认消息时,从备份中读取会显示新值,但从主读取将看到旧值。

2.2.2. 密钥所有权

分布式缓存将条目分成固定数量的片段,并将每个片段分配给所有者节点列表。复制缓存执行相同的操作,但每个节点都是一个所有者。

owners 列表中的第一个节点 是主所有者。列表中的其他节点是 备份所有者。当缓存拓扑更改时,因为节点加入或离开集群时,片段所有权表会广播到每个节点。这允许节点定位密钥,而无需为每个密钥发出多播请求或维护元数据。

numSegments 属性配置可用的片段数量。但是,除非集群重启,片段数量不会改变。

同样,键到网段映射无法更改。无论集群拓扑更改是什么,键必须始终映射到同一片段。务必要确保 key-to-segment 映射平均分配分配给每个节点的片段数量,同时尽量减少集群拓扑更改时必须移动的片段数量。

Expand
一致的哈希工厂实现描述

SyncConsistentHashFactory

使用基于 一致哈希的算法。当禁用服务器提示时,默认选择。

这种实现始终将密钥分配给每个缓存中的同一节点,只要集群是对称的。换句话说,所有缓存在所有节点上运行。这种实施确实有一些负点,负载分布稍微不均匀。它还比加入或离开更严格要求更多片段。

TopologyAwareSyncConsistentHashFactory

等同于 SyncConsistentHashFactory,但与服务器提示一起使用,以在拓扑之间分发数据,以便备份数据副本存储在拓扑中与主所有者不同的节点上。这是带有服务器提示的默认哈希实现。

DefaultConsistentHashFactory

实现比 SyncConsistentHashFactory 更加均匀分发,但有一个缺点。节点加入集群的顺序决定了哪个节点拥有哪个片段。因此,密钥可能会分配给不同缓存中的不同节点。

TopologyAwareConsistentHashFactory

等同于 DefaultConsistentHashFactory,但与服务器提示用于在拓扑间分发数据,以便备份数据副本存储在拓扑中的与主所有者不同的节点上。

ReplicatedConsistentHashFactory

用于内部实施复制缓存。您不应该在分布式缓存中显式选择此算法。

哈希配置

您可以配置 ConsistentHashFactory 实现,包括自定义的带有嵌入式缓存。

XML

<distributed-cache name="distributedCache"
                   owners="2"
                   segments="100"
                   capacity-factor="2" />
Copy to Clipboard Toggle word wrap

ConfigurationBuilder

Configuration c = new ConfigurationBuilder()
   .clustering()
      .cacheMode(CacheMode.DIST_SYNC)
      .hash()
         .numOwners(2)
         .numSegments(100)
         .capacityFactor(2)
   .build();
Copy to Clipboard Toggle word wrap

2.2.3. 容量因素

容量因素根据集群中每个节点的可用资源分配片段数量。

节点的容量因素适用于该节点是主所有者和备份所有者的片段。换句话说,容量因素指定节点与集群中的其他节点相比的总容量。

默认值为 1,这意味着集群中的所有节点都有相等的容量,Data Grid 为集群中的所有节点分配相同的片段数量。

但是,如果节点有不同数量的可用内存,您可以配置容量因素,以便 Data Grid 哈希算法为每个节点分配大量按其容量权重的片段。

容量因素配置的值必须是正数,可以是 1.5 所示的比例。您还可以配置容量因数 0, 但建议临时加入集群的节点,应该改为使用零容量配置。

2.2.3.1. 零容量节点

您可以为每个缓存、用户定义的缓存和内部缓存配置容量因子为 0 的节点。在定义零容量节点时,该节点不会保存任何数据。

零容量节点配置

XML

<infinispan>
  <cache-container zero-capacity-node="true" />
</infinispan>
Copy to Clipboard Toggle word wrap

JSON

{
  "infinispan" : {
    "cache-container" : {
      "zero-capacity-node" : "true"
    }
  }
}
Copy to Clipboard Toggle word wrap

YAML

infinispan:
  cacheContainer:
    zeroCapacityNode: "true"
Copy to Clipboard Toggle word wrap

ConfigurationBuilder

new GlobalConfigurationBuilder().zeroCapacityNode(true);
Copy to Clipboard Toggle word wrap

2.2.4. 一级(L1)缓存

Data Grid 节点会在从集群中的另一节点检索条目时创建本地副本。L1 缓存避免在主所有者节点上重复查找条目并提高性能。

下图演示了 L1 缓存的工作方式:

图 2.4. L1 缓存

在 "L1 缓存"图中:

  1. 客户端调用 cache.get () 来读取集群中另一个节点是主所有者的条目。
  2. 原始器节点将读取操作转发到主所有者。
  3. 主所有者返回键/值条目。
  4. 原始器节点会创建一个本地副本。
  5. 后续的 cache.get () 调用会返回本地条目,而不是转发到主所有者。
L1 缓存性能

启用 L1 提高了读取操作的性能,但需要主所有者节点在修改条目时广播失效消息。这样可确保 Data Grid 在集群中删除任何过时的副本。但是,这也降低了写操作的性能并增加内存用量,从而减少缓存的整体容量。

注意

与任何其他缓存条目一样,Data Grid 驱除和过期本地副本或 L1 条目。

L1 缓存配置

XML

<distributed-cache l1-lifespan="5000"
                   l1-cleanup-interval="60000">
</distributed-cache>
Copy to Clipboard Toggle word wrap

JSON

{
  "distributed-cache": {
    "l1-lifespan": "5000",
    "l1-cleanup-interval": "60000"
  }
}
Copy to Clipboard Toggle word wrap

YAML

distributedCache:
  l1Lifespan: "5000"
  l1-cleanup-interval: "60000"
Copy to Clipboard Toggle word wrap

ConfigurationBuilder

ConfigurationBuilder builder = new ConfigurationBuilder();
builder.clustering().cacheMode(CacheMode.DIST_SYNC)
         .l1()
         .lifespan(5000, TimeUnit.MILLISECONDS)
         .cleanupTaskFrequency(60000, TimeUnit.MILLISECONDS);
Copy to Clipboard Toggle word wrap

2.2.5. 服务器提示

服务器提示通过尽可能在多个服务器、机架和数据中心中复制条目来提高分布式缓存中数据的可用性。

注意

服务器提示仅适用于分布式缓存。

当 Data Grid 分发您的数据的副本时,它遵循优先级顺序:site、rack、machine 和 node。所有配置属性都是可选的。例如,当您只指定机架 ID 时,Data Grid 会将副本分布到不同的机架和节点上。

如果缓存的片段数量过低,服务器提示可能会影响集群重新平衡操作。

提示

多个数据中心中的集群的替代方法是跨站点复制。

服务器提示配置

XML

<cache-container>
  <transport cluster="MyCluster"
            machine="LinuxServer01"
            rack="Rack01"
            site="US-WestCoast"/>
</cache-container>
Copy to Clipboard Toggle word wrap

JSON

{
  "infinispan" : {
    "cache-container" : {
      "transport" : {
        "cluster" : "MyCluster",
        "machine" : "LinuxServer01",
        "rack" : "Rack01",
        "site" : "US-WestCoast"
      }
    }
  }
}
Copy to Clipboard Toggle word wrap

YAML

cacheContainer:
  transport:
    cluster: "MyCluster"
    machine: "LinuxServer01"
    rack: "Rack01"
    site: "US-WestCoast"
Copy to Clipboard Toggle word wrap

GlobalConfigurationBuilder

GlobalConfigurationBuilder global = GlobalConfigurationBuilder.defaultClusteredBuilder()
  .transport()
  .clusterName("MyCluster")
  .machineId("LinuxServer01")
  .rackId("Rack01")
  .siteId("US-WestCoast");
Copy to Clipboard Toggle word wrap

2.2.6. 关键关联性服务

在分布式缓存中,使用不透明算法将密钥分配给节点列表。无法反转计算并生成映射到特定节点的密钥。但是,Data Grid 可以生成一系列(pseudo-) random 键,查看其主所有者,并在需要关键映射到特定节点时将其移入应用程序。

以下代码片段演示了如何获取和使用对此服务的引用。

// 1. Obtain a reference to a cache
Cache cache = ...
Address address = cache.getCacheManager().getAddress();

// 2. Create the affinity service
KeyAffinityService keyAffinityService = KeyAffinityServiceFactory.newLocalKeyAffinityService(
      cache,
      new RndKeyGenerator(),
      Executors.newSingleThreadExecutor(),
      100);

// 3. Obtain a key for which the local node is the primary owner
Object localKey = keyAffinityService.getKeyForAddress(address);

// 4. Insert the key in the cache
cache.put(localKey, "yourValue");
Copy to Clipboard Toggle word wrap

服务在第 2 步启动:此时,它使用提供的 Executor 生成和队列密钥。在第 3 步,我们从服务获得密钥,在第 4 步中使用该密钥。

生命周期

KeyAffinityService 扩展 生命周期,它允许停止和(重新)启动它:

public interface Lifecycle {
   void start();
   void stop();
}
Copy to Clipboard Toggle word wrap

服务通过 KeyAffinityServiceFactory 实例化。所有工厂方法都有一个 Executor 参数,用于异步密钥生成(因此它不会在调用者的线程中发生)。用户负责处理此可执行文件的关闭

启动后,KeyAffinityService 需要被显式停止。这会停止后台密钥生成,并释放其他保存的资源。

KeyAffinityService 停止它的唯一情况下,当它被注册的缓存管理器被关闭时。

拓扑更改

当缓存拓扑更改时,KeyAffinityService 生成的密钥的所有权可能会改变。key affinity 服务跟踪这些拓扑更改,且不会返回当前映射到不同节点的键,但它不会对之前生成的密钥进行任何操作。

因此,应用程序应该只将 KeyAffinityService 视为优化,它们不应该依赖生成的密钥的位置来获得正确的。

特别是,应用程序不应依赖 KeyAffinityService 生成的密钥,以便始终位于同一地址。密钥共存仅由 Grouping API 提供。

2.2.7. Grouping API

与 Key affinity 服务补充,分组 API 允许您在同一节点上并置一组条目,但不能选择实际节点。

默认情况下,使用密钥的 hashCode () 计算密钥的片段。如果您使用 Grouping API,Data Grid 将计算组的网段,并将其用作密钥的网段。

使用 Grouping API 时,务必要确保每个节点仍然可以计算每个密钥的所有者,而无需联系其他节点。因此,无法手动指定组。组可以刻录到条目(由密钥类生成)或 extrinsic (由外部函数生成)。

要使用 Grouping API,您必须启用组。

Configuration c = new ConfigurationBuilder()
   .clustering().hash().groups().enabled()
   .build();
Copy to Clipboard Toggle word wrap
<distributed-cache>
   <groups enabled="true"/>
</distributed-cache>
Copy to Clipboard Toggle word wrap

如果您有关键类的控制(您可以更改类定义,它不是一个不可修改的库的一部分),我们建议使用一个内部组。内部组通过向方法添加 @Group 注释来指定,例如:

class User {
   ...
   String office;
   ...

   public int hashCode() {
      // Defines the hash for the key, normally used to determine location
      ...
   }

   // Override the location by specifying a group
   // All keys in the same group end up with the same owners
   @Group
   public String getOffice() {
      return office;
   }
   }
}
Copy to Clipboard Toggle word wrap
注意

group 方法必须返回 String

如果您没有对密钥类的控制权,或者组的确定是关键类的正面问题,我们建议使用 extrinsic 组。通过实施 Grouper 接口来指定 extrinsic 组。

public interface Grouper<T> {
    String computeGroup(T key, String group);

    Class<T> getKeyType();
}
Copy to Clipboard Toggle word wrap

如果为同一密钥类型配置了多个 Grouper 类,则会调用它们的所有类,接收上一个密钥计算的值。如果密钥类也具有 @Group 注释,则第一个 将接收由注释方法计算的组。这允许您在使用 内部组时对组进行更大的控制。

Grouper 实现示例

public class KXGrouper implements Grouper<String> {

   // The pattern requires a String key, of length 2, where the first character is
   // "k" and the second character is a digit. We take that digit, and perform
   // modular arithmetic on it to assign it to group "0" or group "1".
   private static Pattern kPattern = Pattern.compile("(^k)(<a>\\d</a>)$");

   public String computeGroup(String key, String group) {
      Matcher matcher = kPattern.matcher(key);
      if (matcher.matches()) {
         String g = Integer.parseInt(matcher.group(2)) % 2 + "";
         return g;
      } else {
         return null;
      }
   }

   public Class<String> getKeyType() {
      return String.class;
   }
}
Copy to Clipboard Toggle word wrap

组群 实现必须在缓存配置中显式注册。如果您要以编程方式配置 Data Grid:

Configuration c = new ConfigurationBuilder()
   .clustering().hash().groups().enabled().addGrouper(new KXGrouper())
   .build();
Copy to Clipboard Toggle word wrap

或者,如果您使用 XML:

<distributed-cache>
   <groups enabled="true">
      <grouper class="com.example.KXGrouper" />
   </groups>
</distributed-cache>
Copy to Clipboard Toggle word wrap
高级 API

AdvancedCache 有两个特定于组的方法:

两种方法会迭代整个数据容器和存储(如果存在),因此当缓存包含大量小组时,它们可能会较慢。

返回顶部
Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。 了解我们当前的更新.

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

Theme

© 2025 Red Hat