Neo4j主要经过构成图来存储数据,图中的数据包括节点、关系以及节点的属性和关系的属性,关系能够是双向的,也能够是只有单向的.html
如下是它的一些特色 + 支持完整的ACID(原子性、一致性、隔离性和持久性) + 支持常数级时间复杂度的图遍历 + 支持查询的数据导出为JSON和XLS格式 + 支持经过浏览器图形化界面形式访问 + 能够经过多种语言进行访问管理(Java、Python、Ruby、PHP、C#、Js)java
原生图处理:存在免索引邻接属性(Index Free Adjacency),提供快速高效的图遍历node
使用免索引邻接的数据库引擎中的每一个节点都会维护其对相邻节点的引用。所以每一个节点都表现为其附近节点的微索引,这比使用全局索引代价小不少。这意味着查询时间与图的总体规模无关,它仅和所搜索图的数量成正比。 相反,一个非原生图数据库引擎使用(全局)索引链接各个节点。这些索引对每一个遍历都添加一个间接层,所以会致使更大的计算成本。原生图处理的拥护者认为免索引邻接相当重要,由于它提供快速、高效的图遍历。 索引查找在小型网络中能够工做,但对于大图的查询代价过高。具备原生图处理能力的图数据库在查询是否是使用索引查找来扮演联系的角色,而是使用免索引邻接来确保高性能遍历的。 非原生图处理引擎使用索引进行节点间遍历
上图中要寻找Alice的朋友,咱们必需要首先执行索引查找,成本为O(log n ) ,这对于偶尔或者浅层的查找来讲是能够接受的,但当咱们改变遍历的方向时,他的代价就变得很是昂贵起来,若是相对于寻找alice的朋友,就必需要执行多个索引来完成查找,每一个节点所表明的人都有可能把Alice看成他的朋友,这使得成本很高,找到Alice的朋友代价是O(log n ) ,而找到和Alice交朋友的人的代价则是O(mlogn)。mysql
索引查找在小型网络中还能够,可是在大图中的查询代价过高,具备原生图处理能力的图数据库在查询时不是使用索引查找的,而是使用免索引零链接来确保高性能的遍历的,下图为Neo4j使用关系而非索引实现快速遍历sql
在通用图数据库中,能够以极小的代价双向(从尾部到头部或者从头部到尾部)遍历关系,上图中寻找ALICE的朋友,直接向外寻找friend就能够。其遍历的成本为O(1),要寻和Alice交朋友的人,咱们只须要全部指向ALICE的friend关系联系在一块儿便可,这样的成本是O(1).数据库
免索引邻接(index-free adjacency) 是图数据库相比于传统的 mysql 的优点的核心 key,那么图数据库用什么结构去存储 index-free adjacency 是关键设计点。api
架构上层是对外访问的 api,右边是事务管理,左边有 cache 等,下面咱们看下 disk 上存储的结构:数组
neo4j 在磁盘上会分不一样的 store file 存储浏览器
一个重要的设计点是 store 中存储的 record 都是固定大小的,固定大小带来的好处是:由于每一个 record 的大小固定,所以给定 id就能快速进行定位。缓存
节点与关系的存储文件的物理结构图 上图第一个是 node record 的结构:
图中的节点和联系的存储文件都是固定大小的,每一个记录长度为9字节,所以能够能够在O(1)的时间复杂度下计算位置.
节点(指向联系和属性的单向链表,neostore.nodestore.db):第一个字节,表示是否被使用的标志位,后面4个字节,表明关联到这个节点的第一个关系的ID,再接着的4个字符,表明第一个属性ID,后面紧接着的5个字符是表明当前节点的标签,指向该节点的标签存储,最后一个字符做为保留位. 联系(双向链表,neostore.relationshipstore.db):第一个字节,表示是否被使用的标志位,后面4个字节,表明起始节点的ID,再接着的4个字符,表明结束个节点的ID,而后是关系类型占用5个字节,而后依次接着是起始节点的上下联系和结束节点的上下节点,以及一个指示当前记录是否位于联系链的最前面.
同时还有属性存储(neostore.propertystore.db)也是固定大小,每一个属性记录包括4个属性块(一个属性记录最多容纳4个属性)和指向属性链中下一个属性的ID. 属性记录包括属性类型和指向属性索引文件的指针(neostore.propertysotre.db.index). 同时属性记录中能够内联和动态存储,在属性值存储占用小时,会直接存储在属性记录中,对于大属性值,能够分别存储在动态字符存储(neostore.propertysotre.db.strings)和动态数组存储(neostore.propertysotre.db.arrays)中,因为动态记录一样由记录大小固定的记录链表组成,所以大字符串和大数组会占据多个动态记录.
节点存储文件用来存储节点的记录。每一个用户级的图中建立的节点最终会终结于节点存储,其物理文件是"neostore.nodestore.db"。像大多数Neo4j存储文件同样,节点存储区是固定大小的记录存储,每一个记录长度为9字节。经过大小固定的记录能够快速查询存储文件中的节点。 一个节点记录的第一个字节是“是否在使用”标志位。它告诉数据库该记录目前是被用于存储节点,仍是可回收用于表示一个新的节点。接下来的4字节表示关联到该节点的第一个联系,随后4字节表示该节点的第一个属性的ID。标签的5字节指向该节点的标签存储(若是标签不多的话也能够内联到节点中)。最后的字节extra是标志保留位。这样一个标志是用来标识紧密链接节点的,而省下的空间为未来预留。节点记录是至关轻量级的:它真的只是几个指向联系和属性列表的指针。 相应的,联系被存储于联系存储文件中,物理文件是neostore.relationshipstore.db。像节点存储同样,联系存储区的记录的大小也是固定的。每一个联系记录包含联系的起始点ID和结束节点ID、联系类型的指针(存储在联系类型存储区),起始节点和结束节点的上一个联系和下一个联系,以及一个指示当前记录是否位于联系链最前面。
下面是 relation record 的结构:
刚开始是开始和结束节点的 node id,接着是 relation type pointer,而后开始和结束节点的前驱和后继 relation id
更形象一点的图
一个可能的搜索过程是:对于给定的一个 node record,能够经过 id 进行简单的偏移计算获得 node,而后经过 relation_id 定位到 relation record,而后获得 end node id,经过偏移计算获得 node
两个节点记录都包含一个指向该节点的第一个属性的指针和联系链中第一个联系的指针。要读取节点的属性,咱们从指向第一个属性的指针开始遍历单向链表结构。要找到一个节点的联系,咱们从指向第一个联系(在示例中为LIKES联系)的节点联系指针开始,顺着特定节点的联系的双向链表寻找(即起始节点的双向链表或结束节点的双向链表),直到找到感兴趣的联系。一旦找到了咱们想要的联系记录,咱们可使用和寻找节点属性同样的单向链表结构读取这种联系的属性(若是有的话),也可使用联系关联的起始节点ID和结束节点ID检查它们的节点记录。用这些ID乘以节点记录的大小,就能够当即算出每一个节点在节点存储文件中的偏移量。 联系存储文件中的双向链表:
这种 partner 的关系自然就是双向的,可是咱们存储的时候,难道要存储两个关系吗,以下图:
那确定是不须要的,这种存储就是一种浪费,那到底 neo4j 中是怎么存储 partner 这种双向关系的呢? 答案是:以任意一个节点为开端,另外一个为尾端,即存储成为单向的关系
在 neo4j 中任意的关系都有一个 start node 和一个 end node,并且 start node 和 end node 都会有个关联的双向链表,这个双向链表中就记录了从该节点出去和进入的全部关系
在这个例子中,A ~ E表示Node 的编号,R1~R7 表示 Relationship 编号,P1~P10 表示Property 的编号。
Node 的存储示例图以下,每一个Node 保存了第1个Property 和 第1个Relationship:
关系的存储示意图以下:
从示意图能够看出,从 Node-B 开始,能够经过关系的 next 指针,遍历Node-B 的全部关系,而后能够到达与其有关系的第1层Nodes,在经过遍历第1层Nodes的关系,能够达到第2层Nodes,...
下载neo4j-community-3.5.6并安装,运行几个例子后 在安装目录的/data/databases/graph.db下
[root@localhost graph.db]# ll 总用量 596 drwxr-xr-x 2 root root 10 5月 27 10:32 index -rw-r--r-- 1 root root 8192 5月 28 09:30 neostore -rw-r--r-- 1 root root 1152 5月 28 09:30 neostore.counts.db.a -rw-r--r-- 1 root root 1152 5月 28 09:26 neostore.counts.db.b -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.id -rw-r--r-- 1 root root 49152 5月 28 09:30 neostore.labelscanstore.db -rw-r--r-- 1 root root 8190 5月 27 16:59 neostore.labeltokenstore.db -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.labeltokenstore.db.id -rw-r--r-- 1 root root 8192 5月 27 16:59 neostore.labeltokenstore.db.names -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.labeltokenstore.db.names.id -rw-r--r-- 1 root root 8190 5月 28 09:30 neostore.nodestore.db -rw-r--r-- 1 root root 297 5月 28 09:30 neostore.nodestore.db.id -rw-r--r-- 1 root root 8192 5月 27 10:32 neostore.nodestore.db.labels -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.nodestore.db.labels.id -rw-r--r-- 1 root root 48954 5月 28 09:30 neostore.propertystore.db -rw-r--r-- 1 root root 73728 5月 28 09:30 neostore.propertystore.db.arrays -rw-r--r-- 1 root root 137 5月 28 09:30 neostore.propertystore.db.arrays.id -rw-r--r-- 1 root root 265 5月 28 09:30 neostore.propertystore.db.id -rw-r--r-- 1 root root 8190 5月 27 16:59 neostore.propertystore.db.index -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.propertystore.db.index.id -rw-r--r-- 1 root root 8192 5月 27 16:59 neostore.propertystore.db.index.keys -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.propertystore.db.index.keys.id -rw-r--r-- 1 root root 16384 5月 28 09:30 neostore.propertystore.db.strings -rw-r--r-- 1 root root 89 5月 28 09:30 neostore.propertystore.db.strings.id -rw-r--r-- 1 root root 8192 5月 27 10:32 neostore.relationshipgroupstore.db -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.relationshipgroupstore.db.id -rw-r--r-- 1 root root 32640 5月 28 09:30 neostore.relationshipstore.db -rw-r--r-- 1 root root 273 5月 28 09:30 neostore.relationshipstore.db.id -rw-r--r-- 1 root root 8190 5月 27 16:59 neostore.relationshiptypestore.db -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.relationshiptypestore.db.id -rw-r--r-- 1 root root 8192 5月 27 16:59 neostore.relationshiptypestore.db.names -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.relationshiptypestore.db.names.id -rw-r--r-- 1 root root 8192 5月 27 10:32 neostore.schemastore.db -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.schemastore.db.id -rw-r--r-- 1 root root 229292 5月 28 09:30 neostore.transaction.db.0
存储 node 的文件
存储 relationship 的文件
存储 label 的文件
存储 property 的文件
其余的文件
neo4j 中,主要有4类节点,属性,关系等文件是以数组做为核心存储结构;同时对节点,属性,关系等类型的每一个数据项都会分配一个惟一的ID,在存储时以该ID 为数组的下标。这样,在访问时经过其ID做为下标,实现快速定位。因此在图遍历等操做时,能够实现 free-index。
CommonAbstractStore 是全部 Store 类的基类,下面的代码片断是 CommonAbstractStore 的成员变量,比较重要的是飘红的几个,特别是IdGenerator,每种Store 的实例都有本身的 id 分配管理器; StoreChannel 是负责Store文件的读写和定位;WindowsPool 是与Store Record相关的缓存,用来提高性能的。
public abstract class CommonAbstractStore<RECORD extends AbstractBaseRecord,HEADER extends StoreHeader> implements RecordStore<RECORD>, AutoCloseable { static final String UNKNOWN_VERSION = "Unknown"; protected final Config configuration; protected final PageCache pageCache; protected final IdType idType; protected final IdGeneratorFactory idGeneratorFactory; protected final Log log; protected final String storeVersion; protected final RecordFormat<RECORD> recordFormat; final File storageFile; private final File idFile; private final String typeDescriptor; protected PagedFile pagedFile; protected int recordSize; private IdGenerator idGenerator; private boolean storeOk = true; private RuntimeException causeOfStoreNotOk; private final StoreHeaderFormat<HEADER> storeHeaderFormat; private HEADER storeHeader; private final OpenOption[] openOptions;
文件名 文件存储格式
neostore.labeltokenstore.db LabelTokenStore(TokenStore)
neostore.labeltokenstore.db.id ID 类型
neostore.labeltokenstore.db.names StringPropertyStore (AbstractDynamicStore, NAME_STORE_BLOCK_SIZE = 30)
neostore.labeltokenstore.db.names.id ID 类型
neostore.nodestore.db NodeStore
neostore.nodestore.db.id ID 类型
neostore.nodestore.db.labels ArrayPropertyStore (AbstractDynamicStorelabel_block_size=60)
neostore.nodestore.db.labels.id ID 类型
neostore.propertystore.db PropertyStore
neostore.propertystore.db.arrays ArrayPropertyStore (AbstractDynamicStorearray_block_size=120)
neostore.propertystore.db.arrays.id ID 类型
neostore.propertystore.db.id ID 类型
neostore.propertystore.db.index PropertyIndexStore
neostore.propertystore.db.index.id ID 类型
neostore.propertystore.db.index.keys StringPropertyStore (AbstractDynamicStore, NAME_STORE_BLOCK_SIZE = 30)
neostore.propertystore.db.index.keys.id ID 类型
neostore.propertystore.db.strings StringPropertyStore (AbstractDynamicStorestring_block_size=120)
neostore.propertystore.db.strings.id ID 类型
neostore.relationshipgroupstore.db RelationshipGroupStore
neostore.relationshipgroupstore.db.id ID 类型
neostore.relationshipstore.db RelationshipStore
neostore.relationshipstore.db.id ID 类型
neostore.relationshiptypestore.db RelationshipTypeTokenStore(TokenStore)
neostore.relationshiptypestore.db.id ID 类型
neostore.relationshiptypestore.db.names StringPropertyStore (AbstractDynamicStore, NAME_STORE_BLOCK_SIZE = 30)
neostore.relationshiptypestore.db.names.id ID 类型
neostore.schemastore.db SchemaStore(AbstractDynamicStore, BLOCK_SIZE = 56)
下面是 neo4j db 中,每种Store都有本身的ID文件(即后缀.id 文件),它们的格式都是同样的。
[root@localhost graph.db]# ll | grep .id -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.id -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.labeltokenstore.db.id -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.labeltokenstore.db.names.id -rw-r--r-- 1 root root 297 5月 28 09:30 neostore.nodestore.db.id -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.nodestore.db.labels.id -rw-r--r-- 1 root root 137 5月 28 09:30 neostore.propertystore.db.arrays.id -rw-r--r-- 1 root root 265 5月 28 09:30 neostore.propertystore.db.id -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.propertystore.db.index.id -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.propertystore.db.index.keys.id -rw-r--r-- 1 root root 89 5月 28 09:30 neostore.propertystore.db.strings.id -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.relationshipgroupstore.db.id -rw-r--r-- 1 root root 273 5月 28 09:30 neostore.relationshipstore.db.id -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.relationshiptypestore.db.id -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.relationshiptypestore.db.names.id -rw-r--r-- 1 root root 9 5月 28 09:30 neostore.schemastore.db.id
neo4j 中后缀为 ".id"的文件格式如上图所示,由文件头(9 Bytes)和 long类型 数组 2部分构成:
原先,每一种资源类型的ID 分配 neo4j 中是经过 IdGeneratorImpl 来实现的,其功能是负责ID管理分配和回收复用。对于节点,关系,属性等每一种资源类型,均可以生成一个IdGenerator 实例来负责其ID管理分配和回收复用。
当前版本(v3.5)有所变更,应该是将原 IdGeneratorImpl 拆成了 IdGeneratorImpl 和 IdContainer .
FreeIdKeeper.java /** * Instances of this class maintain a list of free ids with the potential of overflowing to disk if the number * of free ids becomes too large. This class has no expectations and makes no assertions as to the ids freed. * Such consistency guarantees, for example uniqueness of values, should be imposed from users of this class. * <p> * There is no guarantee as to the ordering of the values returned (i.e. FIFO, LIFO or any other temporal strategy), * primarily because the aggressiveMode argument influences exactly that behaviour. * <p> * The {@link #aggressiveMode} parameter controls whether or not IDs which are freed during this lifecycle will * be allowed to be reused during the same lifecycle. The alternative non-aggressive behaviour is that the IDs * will only be reused after a close/open cycle. This would generally correlate with a restart of the database. */
此类的实例维护一个可用ID列表,若是可用ID的数量太大,则可能溢出到磁盘。 此类没有指望,也没有对释放的ID作出断言。 一致性应有用户保证(诸如值的惟一性)
对于返回值的顺序(即FIFO、LIFO或任何其余时间策略)没有任何保证,主要是由于参数 aggressiveMode 影响. 参数 aggressiveMode 控制了在生命周期中已经被释放的 ID 是否会在同个生命周期中被复用. 另外一种非激进行为是指,ID只在一个周期关闭/打开后才从新使用.这一般与从新启动数据库接近。
IDContainer.java /** * This class handles the persisting of a highest id in use. A sticky byte is present in the header to indicate * whether the file was closed properly. It also handel delegation of reusable ids to the {@link FreeIdKeeper} * class. * * This class is <b>not thread-safe</b> and synchronization need to be handed by the caller. */
此类处理使用中的最高ID的持久化。 头部中的 sticky byte 指示文件是否正确关闭. 它还将可重用ID委托给 freeidmeeper 类.
此类是 非线程安全 的,同步须要由调用者进行处理。
IdGeneratorImpl.java /** * This class generates unique ids for a resource type. For example, nodes in a * nodes space are connected to each other via relationships. On nodes and * relationship one can add properties. We have three different resource types * here (nodes, relationships and properties) where each resource needs a unique * id to be able to differ resources of the same type from each other. Creating * three id generators (one for each resource type ) will do the trick. * <p> * <CODE>IdGenerator</CODE> makes use of so called "defragged" ids. A * defragged id is an id that has been in use one or many times but the resource * that was using it doesn't exist anymore. This makes it possible to reuse the * id and that in turn makes it possible to write a resource store with fixed * records and size (you can calculate the position of a record by knowing the * id without using indexes or a translation table). * <p> * The id returned from {@link #nextId} may not be the lowest * available id but will be one of the defragged ids if such exist or the next * new free id that has never been used. * <p> * The {@link #freeId} will not check if the id passed in to it really is free. * Passing a non free id will corrupt the id generator and {@link #nextId} * method will eventually return that id. * <p> * The {@link #close()} method must always be invoked when done using an * generator (for this time). Failure to do will render the generator as * "sticky" and unusable next time you try to initialize a generator using the * same file. There can only be one id generator instance per id generator file. * <p> * In case of disk/file I/O failure an <CODE>IOException</CODE> is thrown. */
此类为资源类型生成惟一的ID.例如,节点空间中的节点(node)经过关系(relationship)相连.节点和关系上均可以添加属性(properties). 这里有3种不一样的资源类型(节点,关系,属性),每种资源类型都须要惟一的 ID 以和同类资源区分开来. 建立 3 种 ID 生成器(Idgenerator)(每类资源1种)以实现.
IdGenerator 使用所谓的'碎片'id.碎片 id 指的是一类 id,它们被使用过一次或者屡次,但使用其的资源已不存在. 这使得重用 id 成为可能,反过来,能够写入固定记录和大小的资源存储(不使用索引和转换表,只须要知道id就能够计算记录的地址).
从 {[@link] #nextId} 返回的ID可能不是最低的可用ID,但将是碎片ID之一(若是存在)或下一个从未使用过的新空闲ID。
{[@link] freeId}不检查传递给它的 ID 是否真的是空闲.传非空闲ID将损坏ID生成器,{[@link] nextid}方法最终将返回该ID。
每次调用完生成器(generator)后,老是要调用{[@link] #close()}方法.不然将使生成器'粘滞',这样下次你试图用同一个文件初始化生成器时就不可用. 每一个id 生成器文件只能有一个id生成器实例.
若是磁盘/文件的I/O出现故障,将抛出一个 IOException
public IdContainer( FileSystemAbstraction fs, File file, int grabSize, boolean aggressiveReuse ) { if ( grabSize < 1 ) { throw new IllegalArgumentException( "Illegal grabSize: " + grabSize ); } this.file = file; this.fs = fs; this.grabSize = grabSize; this.aggressiveReuse = aggressiveReuse; } public boolean init() { boolean result = true; try { if ( !fs.fileExists( file ) ) { createEmptyIdFile( fs, file, 0, false ); result = false; } fileChannel = fs.open( file, OpenMode.READ_WRITE ); initialHighId = readAndValidateHeader(); markAsSticky(); this.freeIdKeeper = new FreeIdKeeper( new OffsetChannel( fileChannel, HEADER_SIZE ), grabSize, aggressiveReuse ); closed = false; } catch ( IOException e ) { throw new UnderlyingStorageException( "Unable to init id file " + file, e ); } return result; }
/*************************** FreeIdKeeper.java *****************************/ /* * After this method returns, if there were any entries found, they are placed in the readFromDisk list. */ private void readIdBatch() { try { readIdBatch0(); } catch ( IOException e ) { throw new UnderlyingStorageException( "Failed reading free id batch", e ); } } private void readIdBatch0() throws IOException { if ( stackPosition == 0 ) { return; } long startPosition = max( stackPosition - batchSize * ID_ENTRY_SIZE, 0 ); int bytesToRead = toIntExact( stackPosition - startPosition ); ByteBuffer readBuffer = ByteBuffer.allocate( bytesToRead ); channel.position( startPosition ); channel.readAll( readBuffer ); stackPosition = startPosition; readBuffer.flip(); int idsRead = bytesToRead / ID_ENTRY_SIZE; for ( int i = 0; i < idsRead; i++ ) { long id = readBuffer.getLong(); readFromDisk.enqueue( id ); } if ( aggressiveMode ) { truncate( startPosition ); } }
/********* IdGeneratorImpl.java **********/ public synchronized void freeId( long id ) { idContainer.assertStillOpen(); if ( IdValidator.isReservedId( id ) ) { return; } if ( id < 0 || id >= highId ) { throw new IllegalArgumentException( "Illegal id[" + id + "], highId is " + highId ); } idContainer.freeId( id ); } /********* IdContainer.java **********/ public void freeId( long id ) { freeIdKeeper.freeId( id ); } /********************** FreeIdKeeper.java **********************/ public void freeId( long id ) { freeIds.enqueue( id ); freeIdCount++; if ( freeIds.size() >= batchSize ) { long endPosition = flushFreeIds( ByteBuffer.allocate( batchSize * ID_ENTRY_SIZE ) ); if ( aggressiveMode ) { stackPosition = endPosition; } } }
当用户申请一个 id 时,IdGeneratorImpl 在分配时,有2种分配策略:"正常的分配策略" 和"激进分配策略"(aggressiveMode),能够根据配置进行选择。
/** * Returns the next "free" id. If a defragged id exist it will be returned * else the next free id that hasn't been used yet is returned. If no id * exist the capacity is exceeded (all values <= max are taken) and a * {@link UnderlyingStorageException} will be thrown. * * @return The next free id * @throws UnderlyingStorageException * If the capacity is exceeded * @throws IllegalStateException if this id generator has been closed */ @Override public synchronized long nextId() { assertStillOpen(); long nextDefragId = idContainer.getReusableId(); if ( nextDefragId != IdContainer.NO_RESULT ) { return nextDefragId; } if ( IdValidator.isReservedId( highId ) ) { highId++; } IdValidator.assertValidId( idType, highId, max ); return highId++; } /********************* IdContainer.java **********************/ /** * @return next free id or {@link IdContainer#NO_RESULT} if not available */ public long getReusableId() { return freeIdKeeper.getId(); } /*************************** FreeKeeper.java ****************************/ public long getId() { long result; if ( freeIds.size() > 0 && aggressiveMode ) { result = freeIds.dequeue(); freeIdCount--; } else { result = getIdFromDisk(); if ( result != NO_RESULT ) { freeIdCount--; } } return result; } private long getIdFromDisk() { if ( readFromDisk.isEmpty() ) { readIdBatch(); } if ( !readFromDisk.isEmpty() ) { return readFromDisk.dequeue(); } else { return NO_RESULT; } }
其中, getIdFromDisk() 从 readFromDisk 文件中读取已释放且可复用的 id. 没有已释放且可复用的 id了,则分配全新的id.
/** * An abstract representation of a dynamic store. Record size is set at creation as the contents of the * first record and read and used when opening the store in future sessions. * <p> * Instead of a fixed record this class uses blocks to store a record. If a * record size is greater than the block size the record will use one or more * blocks to store its data. * <p> * A dynamic store don't have a {@link IdGenerator} because the position of a * record can't be calculated just by knowing the id. Instead one should use * another store and store the start block of the record located in the * dynamic store. Note: This class makes use of an id generator internally for * managing free and non free blocks. * <p> * Note, the first block of a dynamic store is reserved and contains information * about the store. * <p> * About configuring block size: Record size is the whole record size including the header (next pointer * and what not). The term block size is equivalent to data size, which is the size of the record - header size. * User configures block size and the block size is what is passed into the constructor to the store. * The record size is what's stored in the header (first record). {@link #getRecordDataSize()} returns * the size which was configured at the store creation, {@link #getRecordSize()} returns what the store header says. */
动态存储的抽象表示。记录大小在建立时设置,为在之后的会话中打开存储时,首先记录并读取并使用。 此类使用块来存储记录,而不是固定记录。
若是记录大小大于块大小,则记录将使用一个或多个块来存储其数据。
动态存储没有{[@link] IdGenerator},由于不能只经过ID来计算记录。应该使用另外一个存储,存储储位于动态存储中的记录的起始块。 注意:此类在内部使用 Id生成器管理空闲和非空闲块。
注意,动态存储的第一个块被保留,并包含有关存储的信息。
关于配置块大小:记录大小是包括头(下一个指针等等)在内的整个记录大小。术语块大小等价于数据大小,数据大小是记录头大小。 户配置块大小,块大小是传递到构造函数到存储的。 记录大小是存储在头文件中的内容(第一个记录)。 {[@link] #getRecordDataSize()}返回在建立存储时配置的大小,{[@link] #getRecordSize()}返回存储头的内容。
AbstractDynamicStore 类对应的存储文件格式如上图所示, 整个文件是有一个 block_size=BLOCK_HEADER_SIZE(8Bytes)+block_content_size 的定长数组和一个字符串 "StringPropertyStore v0.A.2" 或 "ArrayPropertyStore v0.A.2"或"SchemaStore v0.A.2" (文件类型描述TYPE_DESCRIPTOR和 neo4j 的 ALL_STORES_VERSION构成)。 访问时,能够经过 id 做为数组的下标进行访问。其中,文件的第1个 record 中前4 字节用来保存 block_size。文件的第2个 record开始保存实际的block数据,它由8个字节的block_header和定长的 block_content(可配置)构成. block_header 结构以下:
类SchemaStore,DynamicArrayStore(ArrayPropertyStore), DynamicStringStore(StringPropertyStore)都是继承成自类AbstractDynamicStore,因此与类DynamicArrayStore, DynamicStringStore和 SchemaStore对应文件的存储格式,都是遵循AbstractDynamicStore的存储格式,除了block块的大小(block_size)不一样外。
db 文件 存储类型 block_size
neostore.labeltokenstore.db.names StringPropertyStore NAME_STORE_BLOCK_SIZE=30
neostore.propertystore.db.index.keys StringPropertyStore NAME_STORE_BLOCK_SIZE=30
neostore.rela tionshiptypestore.db.names StringPropertyStore
neostore.propertystore.db.strings StringPropertyStore string_block_size=120
neostore.nodestore.db.labels ArrayPropertyStore label_block_size=60
neostore.propertystore.db.arrays ArrayPropertyStore array_block_size=120
block_size 经过配置文件或缺省值来设置的,下面的代码片断展现了neostore.propertystore.db.strings 文件的建立过程及block_size 的大小如何传入。
GraphDatabaseSettings.java /** * Block size properties values depends from selected record format. * We can't figured out record format until it will be selected by corresponding edition. * As soon as we will figure it out properties will be re-evaluated and overwritten, except cases of user * defined value. */ @Description( "Specifies the block size for storing strings. This parameter is only honored when the store is " + "created, otherwise it is ignored. " + "Note that each character in a string occupies two bytes, meaning that e.g a block size of 120 will hold " + "a 60 character long string before overflowing into a second block. " + "Also note that each block carries a ~10B of overhead so record size on disk will be slightly larger " + "than the configured block size" ) @Internal public static final Setting<Integer> string_block_size = buildSetting( "unsupported.dbms.block_size.strings", INTEGER, "0" ).constraint( min( 0 ) ).build(); @Description( "Specifies the block size for storing arrays. This parameter is only honored when the store is " + "created, otherwise it is ignored. " + "Also note that each block carries a ~10B of overhead so record size on disk will be slightly larger " + "than the configured block size" ) @Internal public static final Setting<Integer> array_block_size = buildSetting( "unsupported.dbms.block_size.array_properties", INTEGER, "0" ).constraint( min( 0 ) ).build(); @Description( "Specifies the block size for storing labels exceeding in-lined space in node record. " + "This parameter is only honored when the store is created, otherwise it is ignored. " + "Also note that each block carries a ~10B of overhead so record size on disk will be slightly larger " + "than the configured block size" ) @Internal public static final Setting<Integer> label_block_size = buildSetting( "unsupported.dbms.block_size.labels", INTEGER, "0" ).constraint( min( 0 ) ).build(); @Description( "Specifies the size of id batches local to each transaction when committing. " + "Committing a transaction which contains changes most often results in new data records being created. " + "For each record a new id needs to be generated from an id generator. " + "It's more efficient to allocate a batch of ids from the contended id generator, which the transaction " + "holds and generates ids from while creating these new records. " + "This setting specifies how big those batches are. " + "Remaining ids are freed back to id generator on clean shutdown." ) @Internal public static final Setting<Integer> record_id_batch_size = buildSetting( "unsupported.dbms.record_id_batch_size", INTEGER, "20" ).constraint( range( 1, 1_000 ) ).build();
NeoStores.java CommonAbstractStore createPropertyStringStore() { return createDynamicStringStore( layout.propertyStringStore(), layout.idPropertyStringStore(), IdType.STRING_BLOCK, GraphDatabaseSettings.string_block_size ); } CommonAbstractStore createPropertyArrayStore() { return createDynamicArrayStore( layout.propertyArrayStore(), layout.idPropertyArrayStore(), IdType.ARRAY_BLOCK, GraphDatabaseSettings.array_block_size ); }
下面是neo4j graph db 中,Property数据存储对应的文件:
neostore.propertystore.db PropertyStore
neostore.propertystore.db.arrays ArrayPropertyStore (AbstractDynamicStorearray_block_size=120)
neostore.propertystore.db.arrays.id ID 类型
neostore.propertystore.db.id ID 类型
neostore.propertystore.db.index PropertyIndexStore
neostore.propertystore.db.index.id ID 类型
neostore.propertystore.db.index.keys StringPropertyStore (AbstractDynamicStore, NAME_STORE_BLOCK_SIZE = 30)
neostore.propertystore.db.index.keys.id ID 类型
neostore.propertystore.db.strings StringPropertyStore (AbstractDynamicStorestring_block_size=120)
neostore.propertystore.db.strings.id ID 类型