2年前 (2023-03-06)  Java系列 |   抢沙发  628 
文章评分 0 次,平均分 0.0

Chronicle Map 是一种超快、内存中、非阻塞、键值存储,专为低延迟和多进程应用程序而设计。

简而言之,它是一个堆外键值存储。该map不需要大量 RAM 即可正常运行。它可以根据可用磁盘容量增长。此外,它还支持在多主服务器设置中复制数据。

Chronicle Map:一款基于堆外内存的键值存储

使用场景

  • 实时交易系统:Chronicle Map 提供内存中访问速度,并支持超低垃圾收集。Chronicle Map 可以支持最苛刻的应用程序。
  • 高并发系统:Chronicle Map 支持分布在多台机器上的多个读取器和写入器。

为什么要使用Chronicle Map?

  • 快:每秒数百万次操作,具有低且稳定的微秒级读写延迟。写入查询可以很好地扩展到服务器中的硬件执行线程数。读查询永远不会互相阻塞。
  • 可靠:可在节点和网络故障时验证 Chronicle Map 多主复制。可以选择性地保存到磁盘。

复制示例

下图显示了三个服务器(或站点)上的 Chronicle Map 复制示例

Chronicle Map:一款基于堆外内存的键值存储

使用示例

in-memory模式

ChronicleMap<LongValue, CharSequence> inMemoryCountryMap = ChronicleMap
  .of(LongValue.class, CharSequence.class)
  .name("country-map")
  .entries(50)
  .averageValue("America")
  .create();

为了简单起见,我们正在创建一个存储 50 个国家 ID 及其名称的地图。正如我们在代码片段中看到的,除了averageValue()配置之外,创建非常简单。这告诉映射配置映射条目值占用的平均字节数。

换句话说,在创建map时,Chronicle Map确定序列化形式的值占用的平均字节数。它通过使用配置的值编组器序列化给定的平均值来做到这一点。然后它将为每个映射条目的值分配确定的字节数。

关于内存映射,我们必须注意的一件事是只有当 JVM 进程处于活动状态时才能访问数据。当进程终止时,库将清除数据。

持久映射

与内存中的map不同,该实现会将持久化的map保存到磁盘。现在让我们看看如何创建持久化map

ChronicleMap<LongValue, CharSequence> persistedCountryMap = ChronicleMap
  .of(LongValue.class, CharSequence.class)
  .name("country-map")
  .entries(50)
  .averageValue("America")
  .createPersistedTo(new File(System.getProperty("user.home") + "/country-details.dat"));

这将在指定的文件夹中创建一个名为country-details.dat的文件。如果此文件已在指定路径中可用,那么构建器实现将从该 JVM 进程打开一个指向现有数据存储的链接。

我们可以在需要的情况下使用持久化map:

  • 在创建者进程之外生存;例如,支持热应用重新部署
  • 使其在服务器中全局化;例如,支持多个并发进程访问
  • 充当我们将保存到磁盘的数据存储

单键查询

单键查询是处理单个键的操作。Chronicle Map支持 Java Map接口和ConcurrentMap接口的所有操作:

LongValue qatarKey = Values.newHeapInstance(LongValue.class);
qatarKey.setValue(1);
inMemoryCountryMap.put(qatarKey, "Qatar");

//...

CharSequence country = inMemoryCountryMap.get(key);

除了正常的 get 和 put 操作外,ChronicleMap还添加了一个特殊的操作getUsing(),它可以减少检索和处理条目时的内存占用。让我们看看实际效果:

LongValue key = Values.newHeapInstance(LongValue.class);
StringBuilder country = new StringBuilder();
key.setValue(1);
persistedCountryMap.getUsing(key, country);
assertThat(country.toString(), is(equalTo("Romania")));

key.setValue(2);
persistedCountryMap.getUsing(key, country);
assertThat(country.toString(), is(equalTo("India")));

在这里,我们使用相同的StringBuilder对象通过将其传递给getUsing()方法来检索不同键的值。它基本上重用相同的对象来检索不同的条目。在我们的例子中,getUsing()方法等效于:

country.setLength(0);
country.append(persistedCountryMap.get(key));

多键查询

可能存在我们需要同时处理多个键的用例。为此,我们可以使用queryContext()功能。queryContext ()方法将创建用于处理map条目的上下文。

让我们首先创建一个多map并向其添加一些值:

Set<Integer> averageValue = IntStream.of(1, 2).boxed().collect(Collectors.toSet());
ChronicleMap<Integer, Set<Integer>> multiMap = ChronicleMap
  .of(Integer.class, (Class<Set<Integer>>) (Class) Set.class)
  .name("multi-map")
  .entries(50)
  .averageValue(averageValue)
  .create();

Set<Integer> set1 = new HashSet<>();
set1.add(1);
set1.add(2);
multiMap.put(1, set1);

Set<Integer> set2 = new HashSet<>();
set2.add(3);
multiMap.put(2, set2);

要处理多个条目,我们必须锁定这些条目以防止由于并发更新而可能发生的不一致:

try (ExternalMapQueryContext<Integer, Set<Integer>, ?> fistContext = multiMap.queryContext(1)) {
    try (ExternalMapQueryContext<Integer, Set<Integer>, ?> secondContext = multiMap.queryContext(2)) {
        fistContext.updateLock().lock();
        secondContext.updateLock().lock();

        MapEntry<Integer, Set<Integer>> firstEntry = fistContext.entry();
        Set<Integer> firstSet = firstEntry.value().get();
        firstSet.remove(2);

        MapEntry<Integer, Set<Integer>> secondEntry = secondContext.entry();
        Set<Integer> secondSet = secondEntry.value().get();
        secondSet.add(4);

        firstEntry.doReplaceValue(fistContext.wrapValueAsData(firstSet));
        secondEntry.doReplaceValue(secondContext.wrapValueAsData(secondSet));
    }
} finally {
    assertThat(multiMap.get(1).size(), is(equalTo(1)));
    assertThat(multiMap.get(2).size(), is(equalTo(2)));
}

关闭Chronicle Map

现在我们已经完成了对地图的处理,让我们在地图对象上调用 close ()方法来释放堆外内存和与之关联的资源:

persistedCountryMap.close();
inMemoryCountryMap.close();
multiMap.close();

这里要记住的一件事是所有map操作必须在关闭map之前完成。否则,JVM 可能会意外崩溃。

github地址:https://github.com/OpenHFT/Chronicle-Map

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2780.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册