【问题标题】:Understanding of Neo4j object cacheNeo4j对象缓存的理解
【发布时间】:2014-10-26 01:53:43
【问题描述】:

我试图通过对 Neo4j 对象缓存的一些调查来了解它。我对对象缓存的第一印象来自此链接中的幻灯片: http://www.slideshare.net/thobe/an-overview-of-neo4j-internals

具体来说,缓存中的节点/关系对象应类似于幻灯片 9 或 15/42。为了验证这一点,我使用现有的图形数据库内容编写了一个简单的服务器脚本。我这样做的方法是尝试使用 sun.misc.Unsafe 查看节点/关系对象的起始虚拟地址。获取虚拟地址的程序来自以下链接: How can I get the memory location of a object in java?

public static long addressOf(Object o) throws Exception {
    Object[] array = new Object[] { o };

    long baseOffset = unsafe.arrayBaseOffset(Object[].class);
    int addressSize = unsafe.addressSize();
    long objectAddress;
    switch (addressSize) {
    case 4:
        objectAddress = unsafe.getInt(array, baseOffset);
        break;
    case 8:
        objectAddress = unsafe.getLong(array, baseOffset);
        break;
    default:
        throw new Error("unsupported address size: " + addressSize);
    }
    return (objectAddress);
}

在 neo4j 服务器脚本(我的 main() 类)中,我通过 id 获取节点地址并按以下方式打印出地址:

void checkAddr(){
    nodeAddr(0);
    nodeAddr(1);
    nodeAddr(2);
}

void nodeAddr(int n){
    Node oneNode = graphDb.getNodeById(n);
    Node[] array1 = {oneNode};

    try {
        long address = UnsafeUtil.addressOf(array1);
        System.out.println("Addess: " + address);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

首先,我尝试使用默认情况下的软缓存提供程序。打印出节点对象 0、1 和 2 的地址是:

地址:4168500044 地址:4168502383 地址:4168502753

因此,使用第二个地址 - 第一个地址和第三个地址 - 第二个地址,我可以准确地知道一个节点占用了多少空间。在这种情况下,第一个节点对象占用 2339B,第二个占用 370B。

然后,为了查看禁用对象缓存的影响,我使用 NoCacheProvider 进行设置:

setConfig(GraphDatabaseSettings.cache_type,NoCacheProvider.NAME)

打印出来的地址是:

地址:4168488391 地址:4168490708 地址:4168491056

偏移量,计算方式与第一种情况类似:第一个节点对象占用 2317B,第二个占用 348B。

我的问题来了:

  1. 既然我使用同一张图并进行只读查询,为什么同一个节点对象的大小会发生变化?

  2. 当我禁用对象缓存时,为什么地址偏移量看起来与存在对象缓存一样?例如,在节点存储文件中,单个节点占用 9 个字节,在我的实验中不是这样。如果我获取节点对象的方式有问题,我怎样才能以正确的方式获取虚拟地址?有什么方法可以让我具体知道 mmap 节点文件在内存中的位置?

  3. 我怎样才能确切地知道节点对象中存储的内容。当我在这个链接上查看 Node.class 时: https://github.com/neo4j/neo4j/blob/1.9.8/community/kernel/src/main/java/org/neo4j/graphdb/Node.java 似乎节点对象的外观与演示幻灯片中的外观不同。而只是节点对象使用的一组函数。此外,在无对象缓存和有对象缓存的情况下,是否将节点对象作为一个整体同时带入内存?

【问题讨论】:

    标签: java object neo4j graph-databases


    【解决方案1】:

    Node 对象不是 Neo4j 存储在“对象缓存”中的内容,因此您不会通过查看这些实例来深入了解 Neo4j 的缓存。 Neo4j 为您提供的Node 的实现是一个名为NodeProxy 的类的实例,并且尽可能小(两个字段:内部ID 和对数据库的引用)。这些只是用作节点的句柄,用于在数据库中围绕该节点执行操作。存储在“对象缓存”中的对象是名为NodeImpl 的类的实例(尽管名称如此,但它们并未实现Node 接口)。 NodeImpl 对象具有该演示文稿中第 15 张幻灯片(幻灯片中的第 9 页)概述的形状。嗯,大致就是这样的形状,自从我制作了这些幻灯片以来,Neo4j 已经进化了。

    Neo4j 的发展也改变了节点记录在磁盘上占用的字节数。 Neo4j 2.0 及更高版本的节点记录比那些幻灯片显示的稍大。如果您有兴趣查看这些记录的布局,您应该查看NodeRecord 类,然后从NodeStore 类开始并“向下”进入其依赖项以找到内存映射。

    除了查看错误的对象以查看 Neo4j 中不同缓存方法之间的差异之外,您的测量方法存在缺陷。比较对象的地址并不能告诉您有关这些对象大小的任何信息。 JVM 不保证一个接一个(及时)分配的两个对象将相邻地驻留在内存中,即使 JVM 确实使用了这样的分配策略,Neo4j 也可能在您分配的两个对象之间分配了多个对象正在比较。然后是垃圾收集器,它可能在你获取一个对象的地址和你获取下一个对象的地址之间移动了对象。因此,在 Java 中查看对象的地址几乎没有任何用处。如需在 Java 中测量对象大小的更好方法,请查看 Java Object Layout utility,或使用 Java 代理中的 Instrumentation.getObjectSize(...) method

    如前所述回答您的问题:

    1. 节点对象的大小不会改变,它们的地址不能保证在两次运行之间是相同的。根据我上面的描述,您不能依赖对象地址来计算对象大小。

    2. 由于您正在查看 NodeProxy 对象,因此无论 Neo4j 使用什么缓存策略,它们看起来都一样。为了查看NodeImpl 对象,您必须深入挖掘 Neo4j 的内部结构。由于看起来您使用的是 Neo4j 1.9,因此您将必须将 GraphDatabaseService 实例转换为 GraphDatabaseAPI(实现内部的接口),然后在该对象上调用 getNodeManager() 方法。从NodeManager 你可以调用getNodeIfCached( node.getId() ) 来获取NodeImpl 对象。请注意,此 API 在 Neo4j 版本之间不兼容,使用它是“如果密封破损,保修无效”的一种情况......

    3. 请查看NodeImpl 的源代码。至于何时以及如何将数据带入缓存,Neo4j 试图对此保持懒惰,只加载您使用的数据。如果你正在获取一个节点的关系,那些将被加载到缓存中,如果你正在获取属性,那些将被加载到缓存中。如果只获取关系,则永远不会加载属性,反之亦然。

    【讨论】:

    • 亲爱的@thobe,自 2014 年以来已经有一段时间了,两个主要版本发生了很大变化。上述课程/您的幻灯片中指定的格式是否仍然有效?非易失性存储器上的记录格式仍然有效。是否可以使用有关记录如何存储在页面缓存中的最新详细信息来更新或完成您的答案?
    猜你喜欢
    • 2019-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-13
    • 1970-01-01
    • 2010-10-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多