当前位置: 首页 > 工具软件 > Chronicle > 使用案例 >

Chronicle Map(超快速的 & 非堆内存 & 可持久化 & 非阻塞 键值Map框架) 教程

澹台建华
2023-12-01

关于

Chronicle Map 是一种超快速的 & 非堆内存 & 可持久化 & 非阻塞 键值Map框架,专为低延迟和/或多进程应用程序(例如交易和金融市场应用程序)而设计。

创建一个 ChronicleMap 实例

创建 ChronicleMap 的实例比调用构造函数要复杂一些。 要创建实例,您必须使用ChronicleMapBuilder

内存中的 Chronicle Map

以下代码片段创建了一个内存中的 Chronicle Map 存储,用于存储大约 50,000 个 city name → postal code 映射。

它可以在创建它的单个JVM进程中访问。在进程处于活动状态时,可以访问数据。当进程终止时,数据将被清除。

import net.openhft.chronicle.map.*
.....

interface PostalCodeRange {
    int minCode();
    void minCode(int minCode);

    int maxCode();
    void maxCode(int maxCode);
}

ChronicleMapBuilder<CharSequence, PostalCodeRange> cityPostalCodesMapBuilder =
    ChronicleMapBuilder.of(CharSequence.class, PostalCodeRange.class)
        .name("city-postal-codes-map")
        .averageKey("Amsterdam")
        .entries(50_000);
ChronicleMap<CharSequence, PostalCodeRange> cityPostalCodes =
    cityPostalCodesMapBuilder.create();

// Or shorter form, without builder variable extraction:

ChronicleMap<CharSequence, PostalCodeRange> cityPostalCodes = ChronicleMapBuilder
    .of(CharSequence.class, PostalCodeRange.class)
    .name("city-postal-codes-map")
    .averageKey("Amsterdam")
    .entries(50_000)
    .create();

持久化 Chronicle Map

您可以修改此代码以创建持久的 Chronicle Map,方法是将 .create() 调用替换为 .createPersistedTo(cityPostalCodesFile)。 如果您愿意,可以使用持久的 Chronicle Map,或者:

  • 比创建它的过程更长寿; 例如,支持热应用重新部署。
  • 可以从同一服务器上的多个并发进程访问。
  • 将数据持久化到磁盘。

对于希望访问此 Chronicle Map 实例的所有 Java 进程,cityPostalCodesFile 必须代表服务器上的相同位置。 例如,OS.getTarget() + "/cityPostalCodes.dat"

文件的名称和位置完全取决于您。

注意: 当您使用 .createPersistedTo(file) 创建一个 ChronicleMap 实例,并且指定的文件已经存在于系统中时,您从这个 JVM 进程打开一个现有 Chronicle Map 数据存储的视图,而不是创建一个新的 Chronicle Map 数据存储。 这意味着数据存储可能已经包含一些条目。 在此类操作期间,不会对数据执行任何特殊操作。 如果要清理损坏的条目,并确保数据存储处于正确状态,请参阅Recovery部分。

ChronicleMap 实例与 Chronicle Map 数据存储

在本教程中,术语 ChronicleMap 实例(或简称为 ChronicleMap)用于指代提供对 Chronicle Map 数据存储(或 Chronicle Map 键值存储,或 Chronicle Map 存储,或 简单的 Chronicle Map,两个单词之间有空格,与 ChronicleMap 不同),它可以纯粹在内存中,也可以持久化到磁盘。

目前,Java 实现不允许为单个 内存中的 Chronicle Map 数据存储 创建多个访问器 ChronicleMap 对象; 总是存在一对一的关系。

然而,一个 持久化的 Chronicle Map 数据存储 确实允许创建多个访问器 ChronicleMap 实例; 在单个 JVM 进程中(尽管不推荐),或者来自并发 JVM 进程。

当没有进程访问该文件时,它可以被移动到系统中的另一个位置,甚至是另一个服务器。 它甚至可以在不同的操作系统上运行。 从另一个位置打开时,您将看到相同的数据。

如果您不需要 Chronicle Map 实例在服务器重新启动后仍然存在(也就是说,您不需要持久性到磁盘;只需要多进程访问),请将文件挂载到tmpfs. 例如,在 Linux 上,只需将文件放在 /dev/shm 目录中即可。

配置条目

您必须配置 .entries(entries) -  以支持 Chronicle Map 中的最大条目数。 尝试配置条目,以便创建的 Chronicle Map 将服务于大约 99% 的请求。

您不应在实际目标条目数上设置额外的保证。 这种不好的做法被 new HashMap(capacity)new HashSet(capacity) 构造函数推广,它们接受容量,应该乘以负载因子以获得容器中实际的最大预期条目数。 ChronicleMapChronicleSet 没有负载因子的概念。

有关详细信息,请参阅 Javadocs 中的 ChronicleMapBuilder#entries()

一旦创建了 ChronicleMap 实例,它的配置就会被固定,并且不能使用 ChronicleMapBuilder 实例进行更改。

每个 JVM 单个 ChronicleMap 实例

如果要在 Java 进程中同时访问 Chronicle Map 数据存储,则不应为每个线程创建单独的 ChronicleMap 实例。 在 JVM 环境中,ChronicleMap 实例是ConcurrentMap的,并且可以以与例如ConcurrentHashMap 相同的方式同时访问。

恢复

如果访问持久化的 Chronicle Map 的进程异常终止,例如:

  • 崩溃了
  • SIGKILL 信号杀死
  • 由于主机操作系统崩溃而终止
  • 由于主机断电而终止

那么 Chronicle Map 可能会保持无法访问和/或损坏的状态。

下一次进程打开 Chronicle Map 时,应该使用 ChronicleMapBuilder 中的 .recoverPersistedTo() 方法来完成。

createPersistedTo() 不同,此方法会扫描 Chronicle Map 存储的所有内存中是否存在不一致,如果发现任何不一致,它会清除它们。

.recoverPersistedTo() 方法需要独占访问 Chronicle Map。 如果一个并发进程正在访问 Chronicle Map,而另一个进程正在尝试执行恢复,则访问进程侧的操作结果和恢复结果是未指定的; 数据可能会进一步损坏。 您必须确保在调用 .recoverPersistedTo() 时没有其他进程正在访问 Chronicle Map 存储。

例如:

ChronicleMap<CharSequence, PostalCodeRange> cityPostalCodes = ChronicleMap
    .of(CharSequence.class, PostalCodeRange.class)
    .name("city-postal-codes-map")
    .averageKey("Amsterdam")
    .entries(50_000)
    .recoverPersistedTo(cityPostalCodesFile, false);

recoverPersistedTo() 方法中的第二个参数称为 sameBuilderConfigAndLibraryVersion。 它有两个可能的值:

  • true - 如果ChronicleMapBuilder的配置方式与创建Chronicle Map(持久化到给定文件)时完全相同,并且使用相同版本的Chronicle Map library
  • false - 如果初始配置未知,或者Chronicle Map库的当前版本可能与最初用于创建此Chronicle Map的版本不同。

如果 sameBuilderConfigAndLibraryVersiontrue,则 recoverPersistedTo() “知道”所有正确的配置,以及应该将哪些内容写入标头。 它检查恢复的 Chronicle Map 的标头内存(包含序列化配置)是否损坏。 如果标头已损坏,则将其覆盖,并继续恢复过程。

如果 sameBuilderConfigAndLibraryVersionfalse,则 recoverPersistedTo() 依赖于写入 Chronicle Map 标头的配置,假设它没有损坏。 如果它已损坏,则会抛出 ChronicleHashRecoveryFailedException

注意: 使用 Chronicle Map 进行普通操作时,主题标头内存永远不会更新,因此如果访问进程崩溃,操作系统崩溃,甚至机器断电,它都不会被破坏。 只有硬件、内存或磁盘损坏,或文件系统中的错误,可能导致 Chronicle Map 标头内存损坏。

.recoverPersistedTo() 如果访问 Chronicle Map 的前一个进程正常终止,则无害; 然而,这是一个计算成本很高的过程,通常应该避免。

Chronicle Map 创建和恢复可以使用单个调用方便地合并,在 ChronicleMapBuilder.createOrRecoverPersistedTo(persistenceFile, sameLibraryVersion)。 如果持久性文件尚不存在,则此操作类似于 createPersistedTo(persistenceFile),如果文件已存在,则类似于recoverPersistedTo(persistenceFile, sameLibraryVersion)。 例如:

ChronicleMap<CharSequence, PostalCodeRange> cityPostalCodes = ChronicleMap
    .of(CharSequence.class, PostalCodeRange.class)
    .averageKey("Amsterdam")
    .entries(50_000)
    .createOrRecoverPersistedTo(cityPostalCodesFile, false);

如果 Chronicle Map 配置为存储条目校验和以及条目,则恢复过程检查每个条目的校验和是否正确。

否则,它假定条目已损坏并将其从 Chronicle Map 中删除。 如果要存储校验和,则恢复过程不能保证条目数据的正确性。 有关详细信息,请参阅 条目校验和 部分。

复制Map的恢复(仅限企业版)

通过在 software.chronicle.enterprise.map.config.ReplicatedMapCfg 中设置 recoverOnCreate 标志,可以将复制的Map配置为在启动时(复制之前)运行恢复。

键和值的类型

ChronicleMap<K, V> 的键或值类型可以是:

  • 具有最佳开箱即用支持的类型:

    • 任何值接口
    • 任何类实现了 Byteable interface from Chronicle Bytes.
    • 任何类实现了 BytesMarshallable. 来自 Chronicle Bytes 的接口。 实现类应该有一个公共的无参数构造函数。
    • byte[]ByteBuffer
    • CharSequence, StringStringBuilder. 请注意,这些字符序列类型默认使用 UTF-8 编码进行序列化。 如果您需要不同的编码,请参考 自定义CharSequence 编码中的示例.
    • Integer, LongDouble
  • 支持开箱即用的类型,但效率不是特别高。 您可能希望为它们实施更高效的自定义序列化器

    • 任何实现了 java.io.Externalizable 的类。 实现类应该有一个公共的“无参数”构造函数。

    • 任何实现了 java.io.Serializable 的类型,包括装箱的基本类型(上面列出的除外)和数组类型。

    • 实现了net.openhft.chronicle.wire.Marshallable的类型可以用作MarshallableReaderWriter的键或值类型。 它的效率比 BytesMarshallable 低得多,但由于其自描述格式,它可能会带来一些好处。 处理架构更改(添加或删除字段)是开箱即用的。

      注意: Chronicle Map 不考虑使用 BytesMarshallable#usesSelfDescribingMessage 方法。

  • 任何其他类型,如果提供了 自定义序列化程序

值接口 是首选,因为它们不会产生垃圾,并且序列化/反序列化成本接近于零。 它们甚至比包装的基本类型更可取。 例如,尝试使用 net.openhft.chronicle.core.values.IntValue 而不是 Integer

通常,您必须为 ChronicleMapBuilder 提供有关键和值的平均大小的提示,它们将被插入到 ChronicleMap 中。 这是分配适当数量的共享内存所必需的。 分别使用 averageKey()(首选)或 averageKeySize()averageValue()averageValueSize() 执行此操作。

在上面的示例中,调用了 averageKey("Amsterdam"),因为假定“Amsterdam”(UTF-8 编码中的 9 个字节)是城市名称的平均长度。 有些名称较短(Tokyo,5 个字节),有些名称较长(San Francisco,13 个字节)。

另一个例子是,如果你的ChronicleMap中的值是某个社交图的邻接列表,其中节点表示为long标识符,而邻接列表是long[]数组。 例如,如果平均好友数为 150,您可以配置 ChronicleMap 如下:

Map<Long, long[]> socialGraph = ChronicleMap
    .of(Long.class, long[].class)
    .name("social-graph-map")
    .entries(1_000_000_000L)
    .averageValue(new long[150])
    .create();

如果它们的类型是包装的Java即被类型或值接口,则可以省略指定键或值的平均大小。 它们的大小不断变化,Chronicle Map 知道这一点。

如果键或值类型的大小不变,或者只有特定大小的键或值出现在您的 Chronicle Map 域中,那么您最好配置 constantKeySizeBySample()constantValueSizeBySample(),而不是 averageKey()averageValue()。 例如:

ChronicleSet<UUID> uuids =
    ChronicleSet.of(UUID.class)
        .name("uuids")
        // All UUIDs take 16 bytes.
        .constantKeySizeBySample(UUID.randomUUID())
        .entries(1_000_000)
        .create();

自定义序列化器

Chronicle Map允许您为不支持开箱即用的键或值类型配置自定义编组器。你也可以以某种自定义的方式(编码方式不是UTF-8)序列化支持的类型,比如 String,或者序列化支持的类型比默认情况下更有效。

有三对序列化接口。在单个实现中只应该选择其中的一个,并为键或值类型提供给 ChronicleMapBuilder。这些是:

自定义序列化检查表

  1. 选择最合适的一对序列化接口; BytesWriter 和 BytesReaderSizedWriter 和 SizedReader, 或 DataAccess 和 SizedReader。 链接部分给出了关于选择哪对的建议,描述了每一对。
  2. 如果写入器或读取器部分的实现不需要配置,则给它一个 private 构造函数,并定义一个 INSTANCE 常量。JVM中此编组器类的唯一实例。实现ReadResolvable并从readResolve() 方法返回INSTANCE。不要让实现成为Java的enum类型。
  3. 如果 writer 和 reader 都是无配置的,请将它们合并到单个 -Marshaller 实现类中。
  4. 尽最大努力在读取端重用using对象(BytesReadeSizedReader); 包括嵌套对象。
  5. 在处理某些对象时,尽量在编写器端缓存中间序列化结果。例如,尽量不要在sizewriter实现的size()write()方法中进行昂贵的计算。相反,应该计算它们并在序列化器实例字段中缓存。
  6. 尽量重用用于读写的中间对象。将它们存储在序列化器实现的实例字段中。
  7. 如果序列化器实现是有状态的,或者有缓存字段,则要实现 StatefulCopyable
    参见理解StatefulCopyable了解更多信息。
  8. 通过逐个写入和读取序列化器实例的配置字段(而不是状态或缓存字段)来实现writeMarshallable()readMarshallable()。使用给定的 WireOut/WireIn对象。
    参见定制 CharSequence 编码器部分了解实现这些方法的一些重要示例。参见Wire教程
  9. 不要忘记在 readMarshallable()实现的末尾初始化实例的 瞬态/缓存/状态 字段。这是必需的,因为在调用readMarshallable()之前,Wire框架通过Unsafe.allocateInstance() 创建一个序列化器实例,而不是调用任何构造函数。
  10. 如果实现 DataAccess,考虑也实现 Data,并从 getData() 方法返回 this
  11. 不要忘记在从 DataAccess.getData() 方法返回的 Data 实现中实现 equals()hashCode()toString(),不管这是否实际上是同一个 DataAccess 对象,或单独的对象。
  12. 除了 DataAccess 也是一个 Data之外,序列化程序不应该覆盖 Object 的 equals()hashCode()toString()(这些方法永远不会在 Chronicle Map 库中的序列化程序上调用); 他们不应该实现SerializableExternalizable(但必须实现net.openhft.chronicle.wire.Marshallable); 不应实现 Cloneable(但必须实现 StatefulCopyable,如果它们是有状态的或具有缓存字段)。
  13. 实现自定义序列化器后,不要忘记通过 keyMarshallers()keyReaderAndDataAccess()valueMarshallers()valueReaderAndDataAccess() 方法将它们实际应用到 ChronicleMapBuilder

ChronicleMap 使用模式

单键查询

ChronicleMap 支持以下所有操作:

  • Map 接口; get()put() 等,包括 Java 8 中添加的方法,如 compute()merge(),以及
  • ConcurrentMap 接口; putIfAbsent()replace()

所有操作,包括那些包含“两步”的操作,例如 compute(),都在 ConcurrentMap 接口正确同步。 这意味着您可以像使用 HashMapConcurrentHashMap 一样使用 ChronicleMap 实例。

PostalCodeRange amsterdamCodes = Values.newHeapInstance(PostalCodeRange.class);
amsterdamCodes.minCode(1011);
amsterdamCodes.maxCode(1183);
cityPostalCodes.put("Amsterdam", amsterdamCodes);

...

PostalCodeRange amsterdamCodes = cityPostalCodes.get("Amsterdam");

但是,这种方法经常会产生垃圾,因为在分配新值对象时,应该将值从堆外内存反序列化到堆上内存。 有几种有效地重用对象的可能性:

值接口而不是装箱的基本类型

如果你想创建一个 ChronicleMap,其中键是 long 类型,请使用 LongValue 而不是 Long

ChronicleMap<LongValue, Order> orders = ChronicleMap
    .of(LongValue.class, Order.class)
    .name("orders-map")
    .entries(1_000_000)
    .create();

LongValue key = Values.newHeapInstance(LongValue.class);
key.setValue(id);
orders.put(key, order);

...

long[] orderIds = ...
// Allocate a single heap instance for inserting all keys from the array.
// This could be a cached or ThreadLocal value as well, eliminating
// allocations altogether.
LongValue key = Values.newHeapInstance(LongValue.class);
for (long id : orderIds) {
    // Reuse the heap instance for each key
    key.setValue(id);
    Order order = orders.get(key);
    // process the order...
}

chronicleMap.getUsing()

使用 ChronicleMap#getUsing(K key, V using) 来重用值对象。 如果值类型是 CharSequence,它会起作用。 传递 StringBuilder 作为 using 参数。 例如:

 ChronicleMap<LongValue, CharSequence> names = ...
 StringBuilder name = new StringBuilder();
 for (long id : ids) {
    key.setValue(id);
    names.getUsing(key, name);
    // process the name...
 }

在这种情况下,调用 names.getUsing(key, name) 相当于:

 name.setLength(0);
 name.append(names.get(key));

不同之处在于它不会产生垃圾。值类型是值接口。传递堆实例来读取数据,而不需要新的对象分配:

 ThreadLocal<PostalCodeRange> cachedPostalCodeRange =
    ThreadLocal.withInitial(() -> Values.newHeapInstance(PostalCodeRange.class));

 ...

 PostalCodeRange range = cachedPostalCodeRange.get();
 cityPostalCodes.getUsing(city, range);
 // process the range...
  • 如果值类型实现了BytesMarshallableExternalizable,则ChronicleMap会尝试通过将值反序列化为给定对象来重用给定的using对象。
  • 如果通过 .valueMarshaller()ChronicleMapBuilder 中配置了自定义编组器,则 ChronicleMap 会尝试通过从编组器接口调用 readUsing() 方法重用给定对象。

If ChronicleMap 未能重用 getUsing() 中的对象,这没有害处。 它回退到对象创建,如 get() 方法。 特别是,即使是 null 也可以作为 using 对象传递。 它允许“懒惰”方式使用对象初始化模式:

// a field
PostalCodeRange cachedRange = null;

...

// in a method
cachedRange = cityPostalCodes.getUsing(city, cachedRange);
// process the range...

在这个例子中,cachedRange 最初是 null。 在第一次 getUsing() 调用中,堆值被分配并保存在 cachedRange 字段中以供以后重用。

注意: 如果值类型是值接口,不要使用flyweight实现作为 getUsing() 参数。 这是危险的,因为在重用flyweight时直接指向 ChronicleMap 内存,但访问不同步。 充其量你可以读取不一致的值状态; 在最坏的情况下,您可能会损坏ChronicleMap内存。

要直接访问 ChronicleMap 值内存,请使用以下技术。

在上下文中使用条目

try (ExternalMapQueryContext<CharSequence, PostalCodeRange, ?> c =
        cityPostalCodes.queryContext("Amsterdam")) {
    MapEntry<CharSequence, PostalCodeRange> entry = c.entry();
    if (entry != null) {
        PostalCodeRange range = entry.value().get();
        // Access the off-heap memory directly, by calling range
        // object getters.
        // This is very rewarding, when the value has a lot of fields
        // and expensive to copy to heap all of them, when you need to access
        // just a few fields.
    } else {
        // city not found..
    }
}

多键查询

在本例中,一致的图边添加和删除是使用多键查询实现的:

public static boolean addEdge(
        ChronicleMap<Integer, Set<Integer>> graph, int source, int target) {
    if (source == target)
        throw new IllegalArgumentException("loops are forbidden");
    ExternalMapQueryContext<Integer, Set<Integer>, ?> sourceC = graph.queryContext(source);
    ExternalMapQueryContext<Integer, Set<Integer>, ?> targetC = graph.queryContext(target);
    // order for consistent lock acquisition => avoid dead lock
    if (sourceC.segmentIndex() <= targetC.segmentIndex()) {
        return innerAddEdge(source, sourceC, target, targetC);
    } else {
        return innerAddEdge(target, targetC, source, sourceC);
    }
}

private static boolean innerAddEdge(
        int source, ExternalMapQueryContext<Integer, Set<Integer>, ?> sourceContext,
        int target, ExternalMapQueryContext<Integer, Set<Integer>, ?> targetContext) {
    try (ExternalMapQueryContext<Integer, Set<Integer>, ?> sc = sourceContext) {
        try (ExternalMapQueryContext<Integer, Set<Integer>, ?> tc = targetContext) {
            sc.updateLock().lock();
            tc.updateLock().lock();
            MapEntry<Integer, Set<Integer>> sEntry = sc.entry();
            if (sEntry != null) {
                MapEntry<Integer, Set<Integer>> tEntry = tc.entry();
                if (tEntry != null) {
                    return addEdgeBothPresent(sc, sEntry, source, tc, tEntry, target);
                } else {
                    addEdgePresentAbsent(sc, sEntry, source, tc, target);
                    return true;
                }
            } else {
                MapEntry<Integer, Set<Integer>> tEntry = tc.entry();
                if (tEntry != null) {
                    addEdgePresentAbsent(tc, tEntry, target, sc, source);
                } else {
                    addEdgeBothAbsent(sc, source, tc, target);
                }
                return true;
            }
        }
    }
}

private static boolean addEdgeBothPresent(
        MapQueryContext<Integer, Set<Integer>, ?> sc,
        @NotNull MapEntry<Integer, Set<Integer>> sEntry, int source,
        MapQueryContext<Integer, Set<Integer>, ?> tc,
        @NotNull MapEntry<Integer, Set<Integer>> tEntry, int target) {
    Set<Integer> sNeighbours = sEntry.value().get();
    if (sNeighbours.add(target)) {
        Set<Integer> tNeighbours = tEntry.value().get();
        boolean added = tNeighbours.add(source);
        assert added;
        sEntry.doReplaceValue(sc.wrapValueAsData(sNeighbours));
        tEntry.doReplaceValue(tc.wrapValueAsData(tNeighbours));
        return true;
    } else {
        return false;
    }
}

private static void addEdgePresentAbsent(
        MapQueryContext<Integer, Set<Integer>, ?> sc,
        @NotNull MapEntry<Integer, Set<Integer>> sEntry, int source,
        MapQueryContext<Integer, Set<Integer>, ?> tc, int target) {
    Set<Integer> sNeighbours = sEntry.value().get();
    boolean added = sNeighbours.add(target);
    assert added;
    sEntry.doReplaceValue(sc.wrapValueAsData(sNeighbours));

    addEdgeOneSide(tc, source);
}

private static void addEdgeBothAbsent(MapQueryContext<Integer, Set<Integer>, ?> sc, int source,
        MapQueryContext<Integer, Set<Integer>, ?> tc, int target) {
    addEdgeOneSide(sc, target);
    addEdgeOneSide(tc, source);
}

private static void addEdgeOneSide(MapQueryContext<Integer, Set<Integer>, ?> tc, int source) {
    Set<Integer> tNeighbours = new HashSet<>();
    tNeighbours.add(source);
    MapAbsentEntry<Integer, Set<Integer>> tAbsentEntry = tc.absentEntry();
    assert tAbsentEntry != null;
    tAbsentEntry.doInsert(tc.wrapValueAsData(tNeighbours));
}

public static boolean removeEdge(
        ChronicleMap<Integer, Set<Integer>> graph, int source, int target) {
    ExternalMapQueryContext<Integer, Set<Integer>, ?> sourceC = graph.queryContext(source);
    ExternalMapQueryContext<Integer, Set<Integer>, ?> targetC = graph.queryContext(target);
    // order for consistent lock acquisition => avoid dead lock
    if (sourceC.segmentIndex() <= targetC.segmentIndex()) {
        return innerRemoveEdge(source, sourceC, target, targetC);
    } else {
        return innerRemoveEdge(target, targetC, source, sourceC);
    }
}

private static boolean innerRemoveEdge(
        int source, ExternalMapQueryContext<Integer, Set<Integer>, ?> sourceContext,
        int target, ExternalMapQueryContext<Integer, Set<Integer>, ?> targetContext) {
    try (ExternalMapQueryContext<Integer, Set<Integer>, ?> sc = sourceContext) {
        try (ExternalMapQueryContext<Integer, Set<Integer>, ?> tc = targetContext) {
            sc.updateLock().lock();
            MapEntry<Integer, Set<Integer>> sEntry = sc.entry();
            if (sEntry == null)
                return false;
            Set<Integer> sNeighbours = sEntry.value().get();
            if (!sNeighbours.remove(target))
                return false;

            tc.updateLock().lock();
            MapEntry<Integer, Set<Integer>> tEntry = tc.entry();
            if (tEntry == null)
                throw new IllegalStateException("target node should be present in the graph");
            Set<Integer> tNeighbours = tEntry.value().get();
            if (!tNeighbours.remove(source))
                throw new IllegalStateException("the target node have an edge to the source");
            sEntry.doReplaceValue(sc.wrapValueAsData(sNeighbours));
            tEntry.doReplaceValue(tc.wrapValueAsData(tNeighbours));
            return true;
        }
    }
}

用法:

HashSet<Integer> averageValue = new HashSet<>();
for (int i = 0; i < AVERAGE_CONNECTIVITY; i++) {
    averageValue.add(i);
}
ChronicleMap<Integer, Set<Integer>> graph = ChronicleMapBuilder
        .of(Integer.class, (Class<Set<Integer>>) (Class) Set.class)
        .name("graph")
        .entries(100)
        .averageValue(averageValue)
        .create();

addEdge(graph, 1, 2);
removeEdge(graph, 1, 2);

关闭 ChronicleMap

ConcurrentHashMap 不同,ChronicleMap 将其数据存储在堆外; 通常在内存映射文件中。 建议您在使用完ChronicleMap后调用close()方法。

map.close()

这在使用 Chronicle Map 复制时尤其重要,因为调用 close 失败可能会阻止您在同一端口上重新启动复制的Map。

如果您的应用程序崩溃,可能无法调用 close()。 您的操作系统通常会自动关闭悬空端口。 因此,虽然建议您在完成Map后使用 close(),但这不是您必须做的事情; 这只是我们建议您应该做的事情。

警告: 如果在完成Map处理之前过早调用 close(),可能会导致 JVM 崩溃。 关闭必须是您对Map所做的最后一件事。

自定义

您可以自定义 Chronicle Map 的行为。

有关详细信息,请参阅 CM_Tutorial_Behaviour

条目校验和

Chronicle Map 能够将条目校验和与条目一起存储。 使用条目校验和可以识别部分写入的条目(在操作系统或电源故障的情况下)和损坏的条目(在硬件、内存或磁盘损坏的情况下)并在恢复过程中清理它们。

条目校验和是“32 位”数字,由具有良好雪崩效应的哈希函数计算得出。 从理论上讲,在条目损坏后,它仍然有十亿分之一的机会通过总和检查。

By default, entry checksums are:

  • ON 如果 Chronicle Map 被持久化到磁盘(即通过 createPersistedTo() 方法创建)
  • OFF 如果 Chronicle Map 纯粹在内存中。

为纯内存中的 Chronicle Map 存储校验和几乎没有任何实际意义,但您可能希望通过在用于创建Map的 ChronicleMapBuilder 上调用 .checksumEntries(false) 来禁用持久化 Chronicle Map 的存储校验和。 如果您不需要校验和提供的额外安全性,这是有道理的。

当一个条目被插入到 Chronicle Map 中时,条目校验和会自动计算,并在更新整个值的操作中自动重新计算。 例如,map.put()map.replace()map.compute()mapEntry.doReplaceValue()。 请参阅 Javadocs 中的 MapEntry 接口。 如果您直接更新值,绕过 Chronicle Map 逻辑,保持条目校验和最新也是您的责任。

强烈建议仅在上下文中直接更新值的堆外内存,并持有更新或写入锁。 在上下文中,您将获得一个“MapEntry”类型的条目对象。 要手动重新计算条目校验和,将该对象转换为ChecksumEntry类型并调用.updateChecksum()方法:

try (ChronicleMap<Integer, LongValue> map = ChronicleMap
        .of(Integer.class, LongValue.class)
        .entries(1)
        // Entry checksums make sense only for persisted Chronicle Maps, and are ON by
        // default for such maps
        .createPersistedTo(file)) {

    LongValue value = Values.newHeapInstance(LongValue.class);
    value.setValue(42);
    map.put(1, value);

    try (ExternalMapQueryContext<Integer, LongValue, ?> c = map.queryContext(1)) {
        // Update lock required for calling ChecksumEntry.checkSum()
        c.updateLock().lock();
        MapEntry<Integer, LongValue> entry = c.entry();
        Assert.assertNotNull(entry);
        ChecksumEntry checksumEntry = (ChecksumEntry) entry;
        Assert.assertTrue(checksumEntry.checkSum());

        // to access off-heap bytes, should call value().getUsing() with Native value
        // provided. Simple get() return Heap value by default
        LongValue nativeValue =
                entry.value().getUsing(Values.newNativeReference(LongValue.class));
        // This value bytes update bypass Chronicle Map internals, so checksum is not
        // updated automatically
        nativeValue.setValue(43);
        Assert.assertFalse(checksumEntry.checkSum());

        // Restore correct checksum
        checksumEntry.updateChecksum();
        Assert.assertTrue(checksumEntry.checkSum());
    }
}

<<<<<<<<<<<< [完] >>>>>>>>>>>>

 类似资料: