Chronicle Map 是一种超快、内存中、非阻塞、键值存储,专为低延迟和多进程应用程序而设计。
简而言之,它是一个堆外键值存储。该map不需要大量 RAM 即可正常运行。它可以根据可用磁盘容量增长。此外,它还支持在多主服务器设置中复制数据。
使用场景
- 实时交易系统: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
暂无评论