搜索技术博客-淘宝 http://searchtb.ruoguschool.com 关注技术 关注搜索 关注淘宝 Thu, 01 May 2014 14:52:41 +0000 zh-CN hourly 1 neo4j 底层存储结构分析(8) http://searchtb.ruoguschool.com/2014/05/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%908.html http://searchtb.ruoguschool.com/2014/05/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%908.html#comments Thu, 01 May 2014 14:51:54 +0000 五竹 http://searchtb.ruoguschool.com/?p=4385 上一节: neo4j 底层存储结构分析(7)

3.8  示例1:neo4j_exam

下面看一个简单的例子,然后看一下几个主要的存储文件,有助于理解<3–neo4j存储结构>描述的neo4j 的存储格式。

3.8.1    neo4j_exm 代码

上述程序执行后,会在target/neo4j-test00.db 下生成 neo4j 的 db 存储文件,

下面我们看几个主要的存储文件,来帮助我们对 neo4j 的存储格式有个直观的认识。

为了看文件的内容,笔者用二进制方式打开neo4j_exam的db存储文件,并用虚拟打印机输出到pdf 文件,并根据每个文件的格式,进行了着色。

3.8.2    neostore.nodestore.db.id 的内容

打开neo4j_exam的neostore.nodestore.db.id文件看到如下内容:

neostore.nodestore.db.id

id 文件的header 部分: sticky 值是0, nextFreeId是3,目前已回收可复用的 ID 是 02。

3.8.3    neostore.nodestore.db 的内容

neostore.nodestore.db

从neo4j_exam的neostore.nodestore.db文件内容可以看到,文件中保存了有 3 条node …

]]>
上一节: neo4j 底层存储结构分析(7)

3.8  示例1:neo4j_exam

下面看一个简单的例子,然后看一下几个主要的存储文件,有助于理解<3–neo4j存储结构>描述的neo4j 的存储格式。

3.8.1    neo4j_exm 代码

<div>

<b>package</b> com.wuzhu.neo4j_exam;

<b>import</b> java.util.List;

<b>import</b> java.util.ArrayList;

<b>import</b> java.util.Iterator;

<b>import</b> org.neo4j.graphdb.Direction;

<b>import</b> org.neo4j.graphdb.GraphDatabaseService;

<b>import</b> org.neo4j.graphdb.factory.GraphDatabaseFactory;

<b>import</b> org.neo4j.graphdb.Node;

<b>import</b> org.neo4j.graphdb.Relationship;

<b>import</b> org.neo4j.graphdb.Path;

<b>import</b> org.neo4j.graphdb.RelationshipType;

<b>import</b> org.neo4j.graphdb.Transaction;

<b>import</b> org.neo4j.graphdb.index.Index;

<b>import</b> org.neo4j.graphdb.traversal.Evaluation;

<b>import</b> org.neo4j.graphdb.traversal.Evaluator;

<b>import</b> org.neo4j.graphdb.traversal.Evaluators;

<b>import</b> org.neo4j.graphdb.traversal.Traverser;

<b>import</b> org.neo4j.kernel.EmbeddedReadOnlyGraphDatabase;

<b>import</b> org.neo4j.kernel.Traversal;

<b>import</b> org.neo4j.kernel.Uniqueness;

<b>import</b> org.neo4j.tooling.GlobalGraphOperations;

<b>import</b> com.alibaba.fastjson.JSON;

<b>public</b> <b>class</b> Neo4jTest00

{

GraphDatabaseService gds;

Node fromNode;

Node toNode;

Node companyNode;

Relationship relationship;

Relationship belongRelationship;

<b>private</b> <b>static</b> enum UserRelationship <b>implements</b> RelationshipType

{

FELLOW, BELONG

}

<b>public</b> <b>void</b> createDb()

{

String DB_PATH = "target/neo4j-test00.db";

GraphDatabaseFactory factory = <b>new</b> GraphDatabaseFactory();

gds = factory.newEmbeddedDatabase(DB_PATH);

GlobalGraphOperations ggo = GlobalGraphOperations.at(gds);

<b>try</b>(Transaction tx = gds.beginTx() )

{

fromNode = gds.createNode();

fromNode.setProperty("prop_key_table", "prop_value_table_person");

fromNode.setProperty("prop_key_name", "prop_value_name_mayu");

toNode = gds.createNode();

toNode.setProperty("prop_key_table", "prop_value_table_person");

toNode.setProperty("prop_key_name", "prop_value_name_liyanhong");

relationship = fromNode.createRelationshipTo(toNode,UserRelationship.FELLOW);

List<String> eventList = <b>new</b> ArrayList<String>();

//eventList.add("2013福布斯中国富豪榜:李彦宏第三、马化腾第五、马云第八 ");

//eventList.add("李彦宏推轻应用马云入股浏览器 移动入口争夺暗战升级 ");

eventList.add("2013fubushi zhongguo fuhaobang:liyanhong no.3 mahuateng no.5 mayu no.8 ");

eventList.add("liyanhong tui qinyingyong,mayu rugu liulanqi; yidong rukou zhengduo anzhan shengji");

relationship.setProperty("prop_key_event", JSON.toJSONString(eventList));

companyNode = gds.createNode();

companyNode.setProperty("prop_key_table", "company");

companyNode.setProperty("prop_key_name", "alibaba corp");

belongRelationship = fromNode.createRelationshipTo(companyNode,UserRelationship.BELONG);

belongRelationship.setProperty("event", "mayu ruhe zhuangkong alibaba? ");

tx.success();

Iterator<Node> iterator = ggo.getAllNodes().iterator();

<b>while</b> (iterator.hasNext())

{

Node node = iterator.next();

Iterator<String> keysIterator = node.getPropertyKeys().iterator();

System.out.println("nodeId=" + node.getId());

<b>while</b> (keysIterator.hasNext())

{

String key = keysIterator.next();

System.out.println("node property : " + key + "->" + node.getProperty(key));

}

Iterator<Relationship> relationshipsIterator = node.getRelationships().iterator();

<b>while</b> (relationshipsIterator.hasNext())

{

Relationship relationships = relationshipsIterator.next();

System.out.println("关系:" + relationships.getType());

Iterator<String> keysIterator2 = relationships.getPropertyKeys().iterator();

<b>while</b> (keysIterator2.hasNext())

{

String key = keysIterator2.next();

System.out.println("relationship property : "+ key + "->"

+ relationships.getProperty(key));

}

}

}

}

}

<b>public</b> <b>void</b> removeData()

{

<b>try</b> ( Transaction tx = gds.beginTx() )

{

belongRelationship.delete();

companyNode.delete();

tx.success();

}

}

<b>public</b> <b>void</b> stopDb()

{

gds.shutdown();

}

<b>public</b> <b>static</b> <b>void</b> main(String[] args)

{

Neo4jTest00 test00=<b>new</b> Neo4jTest00();

test00.createDb();

test00.removeData();

test00.stopDb();

}

}

上述程序执行后,会在target/neo4j-test00.db 下生成 neo4j 的 db 存储文件,

下面我们看几个主要的存储文件,来帮助我们对 neo4j 的存储格式有个直观的认识。

为了看文件的内容,笔者用二进制方式打开neo4j_exam的db存储文件,并用虚拟打印机输出到pdf 文件,并根据每个文件的格式,进行了着色。

3.8.2    neostore.nodestore.db.id 的内容

打开neo4j_exam的neostore.nodestore.db.id文件看到如下内容:

neostore.nodestore.db.id

id 文件的header 部分: sticky 值是0, nextFreeId是3,目前已回收可复用的 ID 是 02。

3.8.3    neostore.nodestore.db 的内容

neostore.nodestore.db

从neo4j_exam的neostore.nodestore.db文件内容可以看到,文件中保存了有 3 条node record 几率的数组和一个字符串“NodeStore v0.A.2”(文件类型描述TYPE_DESCRIPTOR和 neo4j 的 ALL_STORES_VERSION构成)。

其中3 条 node record 的内容如下:

a)        node_id=0 (即数组下标为0) 的node record 是在使用的, nextRelId=0, nextPropId=1, labels=0, extra=0

b)        node_id=1 (即数组下标为0) 的node record 是在使用的, nextRelId=0, nextPropId=3, labels=0, extra=0

c)        node_id=2 (即数组下标为0) 的node record 是已经释放了, nextRelId=1, nextPropId=4, labels=0, extra=0

结合 2.6.1 的源代码,可以的看到,fromNode 的 node_id=0, toNode的node_id=1, companyNode 的 node_id=2.

3.8.4    neostore.relationshipstore.db 的内容

neostore.relationshipstore.db

从neo4j_exam的neostore.relationshipstore.db文件内容可以看到,文件中保存了有 2 条 relationship record记录的数组和一个字符串“RelationshipStore v0.A.2”(文件类型描述TYPE_DESCRIPTOR和 neo4j 的 ALL_STORES_VERSION构成)。

其中2 个 relationship record 的内容如下:

字段 第1条记录 第2条记录
in_use 1 0
first_node 0 0
second_node 1 2
rel_type 0 1
first_prev_rel_id 1 2
first_next_rel_id -1 0
second_prev_rel_id 1 1
second_next_rel_id -1 -1
next_prop_id 5 6
first-in-chain-markers 3 3

3.8.5   neostore.relationshiptypestore.db的内容

neostore.relationshiptypestore.db

  • record[0].name_id=0×01
  • record[1].name_id=0×02

3.8.6   neostore.relationshiptypestore.db.names 的内容

neostore.relationshiptypestore.db.names

  • record[1]=”FELLOW”
  • record[2]=”BELONG”

3.8.7   neostore.propertystore.db的内容

neostore.propertystore.db

type=0xB 表示 SHORT_STRING, type=0×9 表示 STRING.

因为 companyNode 节点和 belongRelationship 关系已经删除,所以其属性property[4], property[5] , property[7] 的 block_header (key,type,value)部分填充为0。

3.8.8   neostore.propertystore.db.strings的内容

neostore.propertystore.db.strings

打开neo4j_exam的neostore.nodestore.db.id文件看到如上内容:

  •  第0个block 的前4个Bytes 保存 block_size=0×80, 即 block_header_size=8 和 string_block_size=120
  • 第1个block 的保存例子中关系relationship的属性值一部分: < ["2013fubushi zhongguo fuhaobang:liyanhong no.3 mahuateng no.5 mayu no.8 ","liyanhong tui qinyingyong,mayu rugu liulanq >, 其中 block_header的值如下:link_block=0, in_use=1, nr_of_bytes=0x78 , next_block=2
  • 第2个block 的保存例子中关系relationship的属性值一部分: < i; yidong rukou zhengduo anzhan shengji"] >, 其中 block_header的值如下:link_block=1, in_use=1, nr_of_bytes=0×28 , next_block=0xFFFFFFFF(即NULL)

3.8.9   neostore.propertystore.db.index的内容

neostore.propertystore.db.index

  • record[0].name_id=01
  • record[1].name_id=02
  • record[2].name_id=03
  • record[3].name_id=04

3.8.10 neostore.propertystore.db.index.keys的内容

neostore.propertystore.db.index.keys

  •  block[1]=”prop_key_table”
  • block[2]=”prop_key_name”
  • block[3]=”prop_key_event”
  • block[4]=”event”

4       参考

  1. <graph databases>
  2. http://blog.csdn.net/huaishu/article/details/11748927
  3. http://www.neo4j.org.cn/old-docs/
]]>
http://searchtb.ruoguschool.com/2014/05/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%908.html/feed 0
neo4j 底层存储结构分析(7) http://searchtb.ruoguschool.com/2014/05/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%907.html http://searchtb.ruoguschool.com/2014/05/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%907.html#comments Thu, 01 May 2014 14:22:41 +0000 五竹 http://searchtb.ruoguschool.com/?p=4370 上一节: neo4j 底层存储结构分析(6)
下一节: neo4j 底层存储结构分析(7)

3.7  Relationship 的存储

下面是neo4j graph db 中,Relationship数据存储对应的文件:

neostore.relationshipgroupstore.db

neostore.relationshipgroupstore.db.id

neostore.relationshipstore.db

neostore.relationshipstore.db.id

neostore.relationshiptypestore.db

neostore.relationshiptypestore.db.id

neostore.relationshiptypestore.db.names

neostore.relationshiptypestore.db.names.id

neo4j 中, Relationship 的存储是由 RelationshipStore , RelationshipGroupStore, RelationshipTypeTokenStore和StringPropertyStore 4种类型的Store配合来完成的. 其中RelationshipStore 是Relationship最主要的存储结构;当一个Node 的关系数达到一定的阀值时,才会对关系分组(group), RelationshipGroupStore 用来保存关系分组数据;RelationshipTypeTokenStore和StringPropertyStore 配合用来存储关系的类型。

关系的类型的字符串描述值是存在StringPropertyStore这样的DynamicStore 中,如果长度超过一个block ,则分block存储,并将其在StringPropertyStore中的第1个block 的 …

]]>
上一节: neo4j 底层存储结构分析(6)
下一节: neo4j 底层存储结构分析(7)

3.7  Relationship 的存储

下面是neo4j graph db 中,Relationship数据存储对应的文件:

neostore.relationshipgroupstore.db

neostore.relationshipgroupstore.db.id

neostore.relationshipstore.db

neostore.relationshipstore.db.id

neostore.relationshiptypestore.db

neostore.relationshiptypestore.db.id

neostore.relationshiptypestore.db.names

neostore.relationshiptypestore.db.names.id

neo4j 中, Relationship 的存储是由 RelationshipStore , RelationshipGroupStore, RelationshipTypeTokenStore和StringPropertyStore 4种类型的Store配合来完成的. 其中RelationshipStore 是Relationship最主要的存储结构;当一个Node 的关系数达到一定的阀值时,才会对关系分组(group), RelationshipGroupStore 用来保存关系分组数据;RelationshipTypeTokenStore和StringPropertyStore 配合用来存储关系的类型。

关系的类型的字符串描述值是存在StringPropertyStore这样的DynamicStore 中,如果长度超过一个block ,则分block存储,并将其在StringPropertyStore中的第1个block 的 block_id 保存到 RelationshipTypeTokenStore类型文件相应record 的name_id字段中。

neo4j_存储模型_relationship--20140410

ArrayPropertyStore的存储格式见< 3.3.2 DynamicStore 类型>,下面分别介绍一下RelationshipTypeTokenStore, RelationshipStore和RelationshipStore的文件存储格式。

3.7.1   RelationshipTypeTokenStore的主文件存储格式

neo4j_文件存储格式_RelationshipTypeTokenStore--20140412

类RelationshipTypeTokenStore对应的存储文件是neostore.relationshiptypestore.db,其对应的存储格式如上图所示:是一个长度为 RECORD_SIZE=5 Bytes 的 record 数组和和一个字符串描述符“RelationshipTypeStore v0.A.2”(文件类型描述TYPE_DESCRIPTOR和 neo4j 的 ALL_STORES_VERSION) 构成。访问时,可以通过 token_id 作为数组的下标进行访问。

record 是有 1Byte的 in_use 和 4Bytes 的 name_id 构成。

3.7.2   RelationshipStore的文件存储格式

类RelationshipTypeTokenStore对应的存储文件是neostore.relationshipstore.db,其文件存储格式示意图如下,整个文件是有一个 RECORD_SIZE=34Bytes 的定长数组和一个字符串描述符“RelationshipStore v0.A.2”(文件类型描述TYPE_DESCRIPTOR和 neo4j 的 ALL_STORES_VERSION构成)。访问时,可以通过 node_id 作为数组的下标进行访问。

neo4j_文件存储格式_RelationshipStore--20140410

</pre>
<div>// record header size

// directed|in_use(byte)+first_node(int)+second_node(int)+rel_type(int)+

// first_prev_rel_id(int)+first_next_rel_id+second_prev_rel_id(int)+

// second_next_rel_id+next_prop_id(int)+first-in-chain-markers(1)

public static final int RECORD_SIZE = 34;</div>
<pre>

下面介绍一下 relationship record 中每个字段的含义:

  • in_use(1 Byte) : 第 1 字节, 分成3部分.
// [pppp,nnnx]
// [    ,   x] in use flag
// [    ,nnn ] first node high order bits
// [pppp,    ] next prop high order bits
第1 bit 表示 record 是否在 use;
第2~4 bit 表示first_node的node_id的高3位;
第 5~8 bit表示 next_prop_id 的property_id 的 高4位
  • first_node(4 Bytes) : 第2~5字节是RelationShip的from_node 的node_id 的低32位. 加上inUse 字节的第 2~4 bit 作为高3位,构成一个完整的35位node_id。
  • second_node(4 Bytes) : 第6~9字节是RelationShip的to_node 的node_id 的低32位. 加上rel_type的第29~31 bit作为高3位,构成一个完整的35位node_id。
  • rel_type(4 Bytes) : 第 10~13 字节, 分成6部分;
// [ xxx,    ][    ,    ][    ,    ][    ,    ] second node high order bits,     0×70000000
// [    ,xxx ][    ,    ][    ,    ][    ,    ] first prev rel high order bits,  0xE000000// [    ,   x][xx  ,    ][    ,    ][    ,    ] first next rel high order bits,  0x1C00000// [    ,    ][  xx,x   ][    ,    ][    ,    ] second prev rel high order bits, 0×380000// [    ,    ][    , xxx][    ,    ][    ,    ] second next rel high order bits, 0×70000

// [    ,    ][    ,    ][xxxx,xxxx][xxxx,xxxx] type

    1.  第29~31 位是second_node 的node_id高3位;
    2.  第26~28 位是first_next_rel_id 的 relationship_id高3位;
    3.  第23~25 位是first_next_rel_id 的relationship_id高3位;
    4.  第20~22 位是second_prev_rel_id 的relationship_id高3位;
    5.  第17~19 位是second_next_rel_id 的relationship_id高3位;
    6.  第 1~16 位 表示 RelationShipType;
  • first_prev_rel_id(4 Bytes) : 第14~17字节是from_node 的排在本RelationShip 前面一个RelationShip的 relationship_id 的低32位. 加上rel_type的第 26~28 bit 作为高3位,构成一个完整的35位relationship_id。
  • first_next_rel_id(4 Bytes) : 第18~21字节是from_node 的排在本RelationShip 前面一个RelationShip的 relationship_id 的低32位. 加上rel_type的第 23~25 bit 作为高3位,构成一个完整的35位relationship_id。
  • second_prev_rel_id(4 Bytes) : 第22~25字节是from_node 的排在本RelationShip 前面一个RelationShip的 relationship_id 的低32位. 加上rel_type的第 20~22 bit 作为高3位,构成一个完整的35位relationship_id。
  • second_next_rel_id(4 Bytes) : 第26~29字节是from_node 的排在本RelationShip 前面一个RelationShip的 relationship_id 的低32位. 加上rel_type的第 17~19 bit 作为高3位,构成一个完整的35位relationship_id。
  • next_prop_id(4 Bytes) : 第30~33字节是本RelationShip第1个Property的property_id 的低32位. 加上in_use的第 5~8 bit 作为高3位,构成一个完整的36 位property_id。
  • first-in-chain-markers(1 Byte) : 目前只用了第1位和第2位,其作用笔者还没搞清楚。

3.7.2.1        RelationshipStore.java

与neostore.relationshipstore.db文件相对应的类是RelationshipStore,负责RelationshipRecord从neostore.relationshipstore.db文件的读写。下面看一下 neostore.relationshipstore.db 中 getRecord 成员函数,可以帮助理解 Relationship Record 的存储格式。

</pre>
<div>
private RelationshipRecord getRecord( long id, PersistenceWindow window,RecordLoad load )

{

Buffer buffer = window.getOffsettedBuffer( id );

// [    ,   x] in use flag

// [    ,xxx ] first node high order bits

// [xxxx,    ] next prop high order bits

long inUseByte = buffer.get();

boolean inUse = (inUseByte & 0x1) == Record.IN_USE.intValue();

if ( !inUse )

{

switch ( load )

{

case NORMAL:

throw new InvalidRecordException( "RelationshipRecord[" + id + "] not in use" );

case CHECK:

return null;

}

}

long firstNode = buffer.getUnsignedInt();

long firstNodeMod = (inUseByte & 0xEL) << 31;

long secondNode = buffer.getUnsignedInt();

// [ xxx,    ][    ,    ][    ,    ][    ,    ] second node high order bits,     0x70000000

// [    ,xxx ][    ,    ][    ,    ][    ,    ] first prev rel high order bits,  0xE000000

// [    ,   x][xx  ,    ][    ,    ][    ,    ] first next rel high order bits,  0x1C00000

// [    ,    ][  xx,x   ][    ,    ][    ,    ] second prev rel high order bits, 0x380000

// [    ,    ][    , xxx][    ,    ][    ,    ] second next rel high order bits, 0x70000

// [    ,    ][    ,    ][xxxx,xxxx][xxxx,xxxx] type

long typeInt = buffer.getInt();

long secondNodeMod = (typeInt & 0x70000000L) << 4;

int type = (int)(typeInt & 0xFFFF);

RelationshipRecord record = new RelationshipRecord( id,

longFromIntAndMod( firstNode, firstNodeMod ),

longFromIntAndMod( secondNode, secondNodeMod ), type );

record.setInUse( inUse );

long firstPrevRel = buffer.getUnsignedInt();

long firstPrevRelMod = (typeInt & 0xE000000L) << 7;

record.setFirstPrevRel( longFromIntAndMod( firstPrevRel, firstPrevRelMod ) );

long firstNextRel = buffer.getUnsignedInt();

long firstNextRelMod = (typeInt & 0x1C00000L) << 10;

record.setFirstNextRel( longFromIntAndMod( firstNextRel, firstNextRelMod ) );

long secondPrevRel = buffer.getUnsignedInt();

long secondPrevRelMod = (typeInt & 0x380000L) << 13;

record.setSecondPrevRel( longFromIntAndMod( secondPrevRel, secondPrevRelMod ) );

long secondNextRel = buffer.getUnsignedInt();

long secondNextRelMod = (typeInt & 0x70000L) << 16;

record.setSecondNextRel( longFromIntAndMod( secondNextRel, secondNextRelMod ) );

long nextProp = buffer.getUnsignedInt();

long nextPropMod = (inUseByte & 0xF0L) << 28;

byte extraByte = buffer.get();

record.setFirstInFirstChain( (extraByte & 0x1) != 0 );

record.setFirstInSecondChain( (extraByte & 0x2) != 0 );

record.setNextProp( longFromIntAndMod( nextProp, nextPropMod ) );

return record;

}

3.7.3   RelationshipGroupStore类型的存储格式

当Node的Relationship数量超过一个阀值时,neo4j 会对 Relationship 进行分组,以便提供性能。neo4j 中用来实现这一功能的类是 RelationshipGroupStore.

neo4j_文件存储格式_RelationshipGroupStore--20140412

其对应的文件存储格式如下:

整个文件是有一个 RECORD_SIZE=20Bytes 的定长数组和一个字符串“RelationshipGroupStore v0.A.2”(文件类型描述TYPE_DESCRIPTOR和 neo4j 的 ALL_STORES_VERSION构成)。访问时,可以通过 id 作为数组的下标进行访问。数组下标为0的 record 前4 Bytes 保存Relationship分组的阀值。

RelationshipGroupStore 的record 的格式如下:

  • inUse(1 Byte):第1字节,共分成4部分

// [    ,   x] in use

// [    ,xxx ] high next id bits

// [ xxx,    ] high firstOut bits

long inUseByte = buffer.get();

    1. 第1 bit: 表示 record 是否在 use;
    2.  第2~4 bit: 表示 next 的高3位;
    3. 第 5~7 bit:表示 firstOut高3位
    4.  第8 bit:没有用。
  • highByte(1 Byte):第1字节,共分成4部分

// [    ,   x] in use

// [    ,xxx ] high next id bits

// [ xxx,    ] high firstOut bits

long inUseByte = buffer.get();

    1.  第1 bit:没有用;
    2. 第2~4 bit: 表示 firstIn 的高3位;
    3. 第 5~7 bit:表示 firstLoop高3位
    4. 第8 bit:没有用。
  • next :
  • firstOut
  • firstIn
  • firstLoop
]]>
http://searchtb.ruoguschool.com/2014/05/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%907.html/feed 0
neo4j 底层存储结构分析(6) http://searchtb.ruoguschool.com/2014/05/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%906.html http://searchtb.ruoguschool.com/2014/05/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%906.html#comments Thu, 01 May 2014 12:22:47 +0000 五竹 http://searchtb.ruoguschool.com/?p=4355 上一节: neo4j 底层存储结构分析(5)
下一节: neo4j 底层存储结构分析(7)

3.6  Node 数据存储

neo4j 中, Node 的存储是由 NodeStore 和 ArrayPropertyStore 2中类型配合来完成的. node 的label 内容是存在ArrayPropertyStore这样的DynamicStore 中,如果长度超过一个block ,则分block存储,并将其在ArrayPropertyStore中的第1个block 的 block_id 保存到 NodeStore类型文件相应record 的labels字段中。

下面是neo4j graph db 中,Node数据存储对应的文件:

neostore.nodestore.db

neostore.nodestore.db.id

neostore.nodestore.db.labels

neostore.nodestore.db.labels.id

ArrayPropertyStore的存储格式见< 3.3.2 DynamicStore 类型>,下面介绍一下 NodeStore …

]]>
上一节: neo4j 底层存储结构分析(5)
下一节: neo4j 底层存储结构分析(7)

3.6  Node 数据存储

neo4j 中, Node 的存储是由 NodeStore 和 ArrayPropertyStore 2中类型配合来完成的. node 的label 内容是存在ArrayPropertyStore这样的DynamicStore 中,如果长度超过一个block ,则分block存储,并将其在ArrayPropertyStore中的第1个block 的 block_id 保存到 NodeStore类型文件相应record 的labels字段中。

下面是neo4j graph db 中,Node数据存储对应的文件:

neostore.nodestore.db

neostore.nodestore.db.id

neostore.nodestore.db.labels

neostore.nodestore.db.labels.id

ArrayPropertyStore的存储格式见< 3.3.2 DynamicStore 类型>,下面介绍一下 NodeStore 的文件存储格式。

3.6.1   NodeStore的主文件存储格式

NodeStore的主文件是neostore.nodestore.db, 其文件存储格式示意图如下,整个文件是有一个 RECORD_SIZE=15Bytes  的定长数组和一个字符串描述符“NodeStore v0.A.2”(文件类型描述TYPE_DESCRIPTOR和 neo4j 的 ALL_STORES_VERSION) 构成。访问时,可以通过 node_id 作为数组的下标进行访问。

neo4j_文件存储格式_NodeStore--20140410

neo4j_存储模型_node--20140410

&lt;div&gt;

// in_use(byte)+next_rel_id(int)+next_prop_id(int)+labels(5)+extra(byte)

public static final int RECORD_SIZE = 15;

下面介绍一下 node record 中每个字段的含义:

  • n  inUse(1 Byte):第1字节,共分成3部分

// [pppp,rrrx]

// [    ,   x] in use bit

// [    ,rrr  ] higher bits for rel id

// [pppp,    ] higher bits for prop id

long inUseByte = buffer.get();

    1. 第1 bit 表示 record 是否在 use;
    2. 第2~4 bit 表示 node 的第1个 relationship_id 的 高3位;
    3. 第 5~8 bit表示 node 的第1个property_id 的 高4位
  • next_rel_id(4 Bytes) : 第2~5字节是node 的第1个 relationship_id 的 低32位. 加上inUse 字节的第 2~4 bit作为高3位,构成一个完整的35位relationship_id。
  • next_prop_id(4 Bytes) : 第6~9字节是node 的第1个 property_id 的 低32位. 加上inUse 字节的第 5~8 bit作为高4位,构成一个完整的36 位 property_id。
  • labels(5 Bytes) : 第10~14字节是node 的label field。
  • extra(1 Byte) : 第15字节是 extra , 目前只用到第 1 bit ,表示该node 是否 dense, 缺省的配置是 该 node 的 relationshiop 的数量超过 50 个,这表示是 dense.

3.3.2   NodeStore.java

neo4j 中与neostore.nodestore.db文件相对应的类是NodeStore,负责NodeRecord在neostore.nodestore.db文件中的读写。

下面看一下 NodeStore.java 中 getRecord 成员函数,可以帮助理解 Node Record 的存储格式。

<br />&lt;div&gt;<br /><br />private NodeRecord getRecord( long id, PersistenceWindow window,<br /><br />RecordLoad load  )<br /><br />{<br /><br />Buffer buffer = window.getOffsettedBuffer( id );<br /><br />// [    ,   x] in use bit<br /><br />// [    ,xxx ] higher bits for rel id<br /><br />// [xxxx,    ] higher bits for prop id<br /><br />long inUseByte = buffer.get();<br /><br />boolean inUse = (inUseByte &amp; 0x1) == Record.IN_USE.intValue();<br /><br />if ( !inUse )<br /><br />{<br /><br />switch ( load )<br /><br />{<br /><br />case NORMAL:<br /><br />throw new InvalidRecordException( "NodeRecord[" + id + "] not in use" );<br /><br />case CHECK:<br /><br />return null;<br /><br />case FORCE:<br /><br />break;<br /><br />}<br /><br />}<br /><br />long nextRel = buffer.getUnsignedInt();<br /><br />long nextProp = buffer.getUnsignedInt();<br /><br />long relModifier = (inUseByte &amp; 0xEL) &lt;&lt; 31;<br /><br />long propModifier = (inUseByte &amp; 0xF0L) &lt;&lt; 28;<br /><br />long lsbLabels = buffer.getUnsignedInt();<br /><br />long hsbLabels = buffer.get() &amp; 0xFF; // so that a negative byte won't fill the "extended" bits with ones.<br /><br />long labels = lsbLabels | (hsbLabels &lt;&lt; 32);<br /><br />byte extra = buffer.get();<br /><br />boolean dense = (extra &amp; 0x1) &gt; 0;<br /><br />NodeRecord nodeRecord = new NodeRecord( id, dense, longFromIntAndMod( nextRel, relModifier ),<br /><br />longFromIntAndMod( nextProp, propModifier ) );<br /><br />nodeRecord.setInUse( inUse );<br /><br />nodeRecord.setLabelField( labels, Collections.&lt;DynamicRecord&gt;emptyList() );<br /><br />return nodeRecord;<br /><br />}<br /><br />
]]> http://searchtb.ruoguschool.com/2014/05/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%906.html/feed 0 neo4j 底层存储结构分析(5) http://searchtb.ruoguschool.com/2014/05/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%905.html http://searchtb.ruoguschool.com/2014/05/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%905.html#comments Thu, 01 May 2014 12:12:22 +0000 五竹 http://searchtb.ruoguschool.com/?p=4335 上一节: neo4j 底层存储结构分析(4)
下一节: neo4j 底层存储结构分析(6)

3.5 Property 的存储

下面是neo4j graph db 中,Property数据存储对应的文件:

neostore.propertystore.db

neostore.propertystore.db.arrays

neostore.propertystore.db.arrays.id

neostore.propertystore.db.id

neostore.propertystore.db.index

neostore.propertystore.db.index.id

neostore.propertystore.db.index.keys

neostore.propertystore.db.index.keys.id

neostore.propertystore.db.strings

neostore.propertystore.db.strings.id

neo4j 中, Property 的存储是由 PropertyStore, ArrayPropertyStore, StringPropertyStore 和PropertyKeyTokenStore 4种类型的Store配合来完成的.

类PropertyStore对应的存储文件是neostore.propertystore.db, 相应的用来存储 string/array 类型属性值的文件分别是neostore.propertystore.db.strings (StringPropertyStore) 和 neostore.propertystore.db.arrays(ArrayPropertyStore). 其存储模型示意图如下:…

]]>
上一节: neo4j 底层存储结构分析(4)
下一节: neo4j 底层存储结构分析(6)

3.5 Property 的存储

下面是neo4j graph db 中,Property数据存储对应的文件:

neostore.propertystore.db

neostore.propertystore.db.arrays

neostore.propertystore.db.arrays.id

neostore.propertystore.db.id

neostore.propertystore.db.index

neostore.propertystore.db.index.id

neostore.propertystore.db.index.keys

neostore.propertystore.db.index.keys.id

neostore.propertystore.db.strings

neostore.propertystore.db.strings.id

neo4j 中, Property 的存储是由 PropertyStore, ArrayPropertyStore, StringPropertyStore 和PropertyKeyTokenStore 4种类型的Store配合来完成的.

类PropertyStore对应的存储文件是neostore.propertystore.db, 相应的用来存储 string/array 类型属性值的文件分别是neostore.propertystore.db.strings (StringPropertyStore) 和 neostore.propertystore.db.arrays(ArrayPropertyStore). 其存储模型示意图如下:

neo4j_存储模型_property_2--20140423

其中PropertyStore是Property最主要的存储结构,当Property的Key-Value对的Value 是字符串或数组类型并且要求的存储空间比较大,在PropertyStore中保存不了,则会存在StringPropertyStore/ ArrayPropertyStore这样的DynamicStore 中。如果长度超过一个block ,则分block存储,并将其在StringPropertyStore/ ArrayPropertyStore中的第1个block 的 block_id 保存到 PropertyStore类型文件相应record 的PropertyBlock字段中。

 PropertyKeyTokenStore和StringPropertyStore 配合用来存储Propery的Key部分。Propery的Key是编码的,key 的 id 保存在 PropertyKeyTokenStore (即 neostore.propertystore.db.index),key 的字符串名保存在对应的StringPropertyStore类型文件neostore.propertystore.db.index.keys 中。

ArrayPropertyStore的存储格式见< 3.3.2 DynamicStore 类型>,下面分别介绍一下PropertyStore和PropertyKeyTokenStore(PropertyKeyTokenStore)的文件存储格式。

3.5.1   PropertyStore类型的存储格式

neostore.propertystore.db文件存储格式示意图如下,整个文件是有一个 RECORD_SIZE=41 Bytes 的定长数组和一个字符串描述符“PropertyStore v0.A.2”(文件类型描述TYPE_DESCRIPTOR和 neo4j 的 ALL_STORES_VERSION构成)。访问时,可以通过 prop_id 作为数组的下标进行访问。

neo4j_文件存储格式_PropertyStore--20140408

下面介绍一下 property record 中每个字段的含义:

  • highByte(1 Byte):第1字节,共分成2部分

/*

* [pppp,nnnn] previous, next high bits

*/

byte modifiers = buffer.get();

    1. 第1~4 bit 表示 next 的高4位;
    2. 第 5~8 bit表示 prev 的高4位
  • prev(4 Bytes) : Node或Relationship 的属性是通过双向链表方式组织的,prev 表示本属性在双向链表中的上一个属性的id。第2~5字节是prev property_id的 低32位. 加上highByte字节的第 5~8 bit作为高4位,构成一个完整的36位property_id。
  • next(4 Bytes) : next 表示本属性在双向链表中的下一个属性的id。第6~9字节是next property_id的 低32位. 加上highByte字节的第 1~4 bit作为高4位,构成一个完整的36位property_id。
  • payload:  payload 由block_header(8 Bytes)加3个property_block(8 Bytes)组成,共计 32 Bytes.  block_header 分成3部分:
    1. key_id(24 bits) : 第1 ~24 bit , property 的key 的 id
    2.  type( 4 bits ):   第25 ~28 bit , property 的 value 的类型,支持 string, Interger,Boolean, Float, Long,Double, Byte, Character,Short, array.
    3. payload(36 bits): 第29 ~64 bit, 共计36bit;对于Interger, Boolean, Float, Byte, Character , Short 类型的值,直接保存在payload;对于long,如果36位可以表示,则直接保存在payload,如果不够,则保存到第1个PropertyBlock中;double 类型,保存到第1个PropertyBlock中;对于 array/string ,如果编码后在 block_header及3个PropertyBlock 能保存,则直接保存;否则,保存到ArrayDynamicStore/StringDynamicStore 中, payload 保存其在ArrayDynamicStore中的数组下表。

3.5.2   String 类型属性值的保存过程

下面的代码片段展示了neo4j 中,比较长的 String 类型属性值的保存处理过程,其是如何分成多个 DynamicBlock 来存储的。

PropertyStore_encode

3.5.2.1        encodeValue 函数

encodeValue 函数是 PropertySTore.java 的成员函数, 它实现了不同类型的属性值的编码.

</pre>
<div>
public void encodeValue( PropertyBlock block, int keyId, Object value )

{

if ( value instanceof String )

{   // Try short string first, i.e. inlined in the property block

String string = (String) value;

if ( LongerShortString.encode( keyId, string, block, PropertyType.getPayloadSize() ) )

{

return;

}

// Fall back to dynamic string store

byte[] encodedString = encodeString( string );

Collection valueRecords = allocateStringRecords( encodedString );

setSingleBlockValue( block, keyId, PropertyType.STRING, first( valueRecords ).getId() );

for ( DynamicRecord valueRecord : valueRecords )

{

valueRecord.setType( PropertyType.STRING.intValue() );

block.addValueRecord( valueRecord );

}

}

else if ( value instanceof Integer )

{

setSingleBlockValue( block, keyId, PropertyType.INT, ((Integer) value).longValue() );

}

else if ( value instanceof Boolean )

{

setSingleBlockValue( block, keyId, PropertyType.BOOL, ((Boolean) value ? 1L : 0L) );

}

else if ( value instanceof Float )

{

setSingleBlockValue( block, keyId, PropertyType.FLOAT, Float.floatToRawIntBits( (Float) value ) );

}

else if ( value instanceof Long )

{

long keyAndType = keyId | (((long) PropertyType.LONG.intValue()) << 24);

if ( ShortArray.LONG.getRequiredBits( (Long) value ) <= 35 )

{   // We only need one block for this value, special layout compared to, say, an integer

block.setSingleBlock( keyAndType | (1L << 28) | ((Long) value << 29) );

}

else

{   // We need two blocks for this value

block.setValueBlocks( new long[]{keyAndType, (Long) value} );

}

}

else if ( value instanceof Double )

{

block.setValueBlocks( new long[]{

keyId | (((long) PropertyType.DOUBLE.intValue()) << 24),

Double.doubleToRawLongBits( (Double) value )} );

}

else if ( value instanceof Byte )

{

setSingleBlockValue( block, keyId, PropertyType.BYTE, ((Byte) value).longValue() );

}

else if ( value instanceof Character )

{

setSingleBlockValue( block, keyId, PropertyType.CHAR, (Character) value );

}

else if ( value instanceof Short )

{

setSingleBlockValue( block, keyId, PropertyType.SHORT, ((Short) value).longValue() );

}

else if ( value.getClass().isArray() )

{   // Try short array first, i.e. inlined in the property block

if ( ShortArray.encode( keyId, value, block, PropertyType.getPayloadSize() ) )

{

return;

}

// Fall back to dynamic array store

Collection arrayRecords = allocateArrayRecords( value );

setSingleBlockValue( block, keyId, PropertyType.ARRAY, first( arrayRecords ).getId() );

for ( DynamicRecord valueRecord : arrayRecords )

{

valueRecord.setType( PropertyType.ARRAY.intValue() );

block.addValueRecord( valueRecord );

}

}

else

{

throw new IllegalArgumentException( "Unknown property type on: " + value + ", " + value.getClass() );

}

}

3.5.2.2        allocateStringRecords 函数

allocateStringRecords 函数是 PropertySTore.java 的成员函数.

</pre>
<div>
private Collection allocateStringRecords( byte[] chars )

{

return stringPropertyStore.allocateRecordsFromBytes( chars );

}

3.5.2.3        allocateRecordsFromBytes 函数

allocateRecordsFromBytes 函数是 AbstractDynamicStore .java 的成员函数.

</pre>
<div>
protected Collection allocateRecordsFromBytes( byte src[] )

{

return allocateRecordsFromBytes( src, Collections.emptyList().iterator(),

recordAllocator );

}

3.5.2.4        allocateRecordsFromBytes 函数

allocateRecordsFromBytes 函数是 AbstractDynamicStore .java 的成员函数.

</pre>
<div>
public static Collection allocateRecordsFromBytes(

byte src[], Iterator recordsToUseFirst,

DynamicRecordAllocator dynamicRecordAllocator )

{

assert src != null : "Null src argument";

List recordList = new LinkedList<>();

DynamicRecord nextRecord = dynamicRecordAllocator.nextUsedRecordOrNew( recordsToUseFirst );

int srcOffset = 0;

int dataSize = dynamicRecordAllocator.dataSize();

do

{

DynamicRecord record = nextRecord;

record.setStartRecord( srcOffset == 0 );

if ( src.length - srcOffset > dataSize )

{

byte data[] = new byte[dataSize];

System.arraycopy( src, srcOffset, data, 0, dataSize );

record.setData( data );

nextRecord = dynamicRecordAllocator.nextUsedRecordOrNew( recordsToUseFirst );

record.setNextBlock( nextRecord.getId() );

srcOffset += dataSize;

}

else

{

byte data[] = new byte[src.length - srcOffset];

System.arraycopy( src, srcOffset, data, 0, data.length );

record.setData( data );

nextRecord = null;

record.setNextBlock( Record.NO_NEXT_BLOCK.intValue() );

}

recordList.add( record );

assert !record.isLight();

assert record.getData() != null;

}

while ( nextRecord != null );

return recordList;

}

3.5.3   ShortArray 类型属性值的保存过程

ShortArray.encode( keyId, value, block, PropertyType.getPayloadSize() ), 它是在 kernel/impl/nioneo/store/ShortArray.java 中实现的,下面是其代码片段。

</pre>
<div>
public static boolean encode( int keyId, Object array, PropertyBlock target, int payloadSizeInBytes )

{

/*

*  If the array is huge, we don't have to check anything else.

*  So do the length check first.

*/

int arrayLength = Array.getLength( array );

if ( arrayLength > 63 )/*because we only use 6 bits for length*/

{

return false;

}

ShortArray type = typeOf( array );

if ( type == null )

{

return false;

}

int requiredBits = type.calculateRequiredBitsForArray( array, arrayLength );

if ( !willFit( requiredBits, arrayLength, payloadSizeInBytes ) )

{

// Too big array

return false;

}

final int numberOfBytes = calculateNumberOfBlocksUsed( arrayLength, requiredBits ) * 8;

if ( Bits.requiredLongs( numberOfBytes ) > PropertyType.getPayloadSizeLongs() )

{

return false;

}

Bits result = Bits.bits( numberOfBytes );

// [][][    ,bbbb][bbll,llll][yyyy,tttt][kkkk,kkkk][kkkk,kkkk][kkkk,kkkk]

writeHeader( keyId, type, arrayLength, requiredBits, result );

type.writeAll( array, arrayLength, requiredBits, result );

target.setValueBlocks( result.getLongs() );

return true;

}

private static void writeHeader( int keyId, ShortArray type, int arrayLength, int requiredBits, Bits result )

{

result.put( keyId, 24 );

result.put( PropertyType.SHORT_ARRAY.intValue(), 4 );

result.put( type.type.intValue(), 4 );

result.put( arrayLength, 6 );

result.put( requiredBits, 6 );

}

3.5.4   PropertyKeyTokenStore的文件存储格式

neo4j_文件存储格式_PropertyIndexStore--20140410

类PropertyTypeTokenStore对应的存储文件名是neostore.propertystore.db.index,其对应的存储格式如上图所示: 是一个长度为 RECORD_SIZE=9Bytes 的 record 数组和和一个字符串“PropertyIndexStore v0.A.2”(文件类型描述TYPE_DESCRIPTOR和 neo4j 的 ALL_STORES_VERSION构成)。访问时,可以通过 token_id 作为数组的下标进行访问。

record 是由 in_use(1 Byte) ,prop_count(4 Bytes), name_id(4 Bytes)构成。

]]>
http://searchtb.ruoguschool.com/2014/05/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%905.html/feed 0
neo4j 底层存储结构分析(4) http://searchtb.ruoguschool.com/2014/04/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%904.html http://searchtb.ruoguschool.com/2014/04/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%904.html#comments Thu, 24 Apr 2014 10:42:29 +0000 五竹 http://searchtb.ruoguschool.com/?p=4314 上一节: neo4j 底层存储结构分析(3)
下一节: neo4j 底层存储结构分析(5)

3.3.2   DynamicStore 类型

3.3.2.1        AbstractDynamicStore 的存储格式

neo4j 中对于字符串等变长值的保存策略是用一组定长的 block 来保存,block之间用单向链表链接。类 AbstractDynamicStore 实现了该功能,下面是其注释说明。

/**

 * An abstract representation of a dynamic store. The difference between a

 * normal AbstractStore and a AbstractDynamicStore is

 * that

]]>
上一节: neo4j 底层存储结构分析(3)
下一节: neo4j 底层存储结构分析(5)

3.3.2   DynamicStore 类型

3.3.2.1        AbstractDynamicStore 的存储格式

neo4j 中对于字符串等变长值的保存策略是用一组定长的 block 来保存,block之间用单向链表链接。类 AbstractDynamicStore 实现了该功能,下面是其注释说明。

/**

 * An abstract representation of a dynamic store. The difference between a

 * normal AbstractStore and a AbstractDynamicStore is

 * that the size of a record/entry can be dynamic.

 * 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.

 * A dynamic store don’t have a IdGenerator because the position of a

 * record can’t be calculated just by knowing the id. Instead one should use a

 * AbstractStore 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.

 * Note, the first block of a dynamic store is reserved and contains information

 * about the store.

 */

 neo4j_文件存储格式_StringPropertyStore--20140412

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 结构如下:

  • inUse(1 Byte):第1字节,共分成3部分

[x__ ,    ]  0: start record, 1: linked record

[   x,    ]  inUse

[    ,xxxx]  high next block bits

    • 第1~4 bit 表示next_block 的高4位
    • 第5 bit表示block 是否在 use;
    • 第8 bit 表示 block 是否是单向链表的第1个 block;0 表示第1个block, 1表示后续 block.
  • nr_of_bytes(3Bytes):本 block 中保存的数据的长度。
  • next_block(4Bytes): next_block 的低 4 个字节,加上 inUse 的第1~4 位,next_block 的实际长度共 36 bit。以数组方式存储的单向链表的指针,指向保存同一条数据的下一个 block 的id.

3.3.2.2        AbstractDynamicStore.java

下面看一下 AbstractDynamicStore.java 中 getRecord() 和readAndVerifyBlockSize() 成员函数,可以帮助理解 DynamicStore 的存储格式。

  • getRecord( long blockId, PersistenceWindow window, RecordLoad load )

private DynamicRecord getRecord( long blockId, PersistenceWindow window, RecordLoad load )</pre>
<div>{

DynamicRecord record = new DynamicRecord( blockId );

Buffer buffer = window.getOffsettedBuffer( blockId );

/*

* First 4b

* [x   ,    ][    ,    ][    ,    ][    ,    ] 0: start record, 1: linked record

* [   x,    ][    ,    ][    ,    ][    ,    ] inUse

* [    ,xxxx][    ,    ][    ,    ][    ,    ] high next block bits

* [    ,    ][xxxx,xxxx][xxxx,xxxx][xxxx,xxxx] nr of bytes in the data field in this record

*

*/

long firstInteger = buffer.getUnsignedInt();

boolean isStartRecord = (firstInteger & 0x80000000) == 0;

long maskedInteger = firstInteger & ~0x80000000;

int highNibbleInMaskedInteger = (int) ( ( maskedInteger ) >> 28 );

boolean inUse = highNibbleInMaskedInteger == Record.IN_USE.intValue();

if ( !inUse && load != RecordLoad.FORCE )

{

throw new InvalidRecordException( "DynamicRecord Not in use, blockId[" + blockId + "]" );

}

int dataSize = getBlockSize() - BLOCK_HEADER_SIZE;

int nrOfBytes = (int) ( firstInteger & 0xFFFFFF );

/*

* Pointer to next block 4b (low bits of the pointer)

*/

long nextBlock = buffer.getUnsignedInt();

long nextModifier = ( firstInteger & 0xF000000L ) << 8;

long longNextBlock = longFromIntAndMod( nextBlock, nextModifier );

boolean readData = load != RecordLoad.CHECK;

if ( longNextBlock != Record.NO_NEXT_BLOCK.intValue()

&& nrOfBytes < dataSize || nrOfBytes > dataSize )

{

readData = false;

if ( load != RecordLoad.FORCE )

{

throw new InvalidRecordException( "Next block set[" + nextBlock

+ "] current block illegal size[" + nrOfBytes + "/" + dataSize + "]" );

}

}

record.setInUse( inUse );

record.setStartRecord( isStartRecord );

record.setLength( nrOfBytes );

record.setNextBlock( longNextBlock );

/*

* Data 'nrOfBytes' bytes

*/

if ( readData )

{

byte byteArrayElement[] = new byte[nrOfBytes];

buffer.get( byteArrayElement );

record.setData( byteArrayElement );

}

return record;

}

  • readAndVerifyBlockSize()
</pre>
<div>protected void readAndVerifyBlockSize() throws IOException

{

ByteBuffer buffer = ByteBuffer.allocate( 4 );

getFileChannel().position( 0 );

getFileChannel().read( buffer );

buffer.flip();

blockSize = buffer.getInt();

if ( blockSize <= 0 )

{

throw new InvalidRecordException( "Illegal block size: " +

blockSize + " in " + getStorageFileName() );

}

}

3.3.2.3        类DynamicArrayStore, DynamicStringStore

类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.relationshiptypestore.db.names StringPropertyStore NAME_STORE_BLOCK_SIZE=30
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
neostore.schemastore.db SchemaStore BLOCK_SIZE=56

block_size 通过配置文件或缺省值来设置的,下面的代码片段展示了neostore.propertystore.db.strings 文件的创建过程及block_size 的大小如何传入。

1)        GraphDatabaseSettings.java

</pre>
<div>public static final Setting string_block_size = setting("string_block_size", INTEGER, "120",min(1));

public static final Setting array_block_size = setting("array_block_size", INTEGER, "120",min(1));

public static final Setting label_block_size = setting("label_block_size", INTEGER, "60",min(1));</div>
<pre>
      2)        StoreFactory.java的Configuration 类
</pre>
<div>public static abstract class Configuration

{

public static final Setting string_block_size = GraphDatabaseSettings.string_block_size;

public static final Setting array_block_size = GraphDatabaseSettings.array_block_size;

public static final Setting label_block_size = GraphDatabaseSettings.label_block_size;

public static final Setting dense_node_threshold = GraphDatabaseSettings.dense_node_threshold;

}

3)        StoreFactory.java的createPropertyStore 函数

</pre>
<div>public void createPropertyStore( File fileName )

{

createEmptyStore( fileName, buildTypeDescriptorAndVersion( PropertyStore.TYPE_DESCRIPTOR ));

int stringStoreBlockSize = config.get( Configuration.string_block_size );

int arrayStoreBlockSize = config.get( Configuration.array_block_size )

createDynamicStringStore(new File( fileName.getPath() + STRINGS_PART), stringStoreBlockSize, IdType.STRING_BLOCK);

createPropertyKeyTokenStore( new File( fileName.getPath() + INDEX_PART ) );

createDynamicArrayStore( new File( fileName.getPath() + ARRAYS_PART ), arrayStoreBlockSize );

}

4)        StoreFactory.java的createDynamicStringStore函数

</pre>
<div>private void createDynamicStringStore( File fileName, int blockSize, IdType idType )

{

createEmptyDynamicStore(fileName, blockSize, DynamicStringStore.VERSION, idType);

}

5)        StoreFactory.java的createEmptyDynamicStore 函数

</pre>
<div>
/**

* Creates a new empty store. A factory method returning an implementation

* should make use of this method to initialize an empty store. Block size

* must be greater than zero. Not that the first block will be marked as

* reserved (contains info about the block size). There will be an overhead

* for each block of <CODE>AbstractDynamicStore.BLOCK_HEADER_SIZE</CODE>bytes.

*/

public void createEmptyDynamicStore( File fileName, int baseBlockSize,

String typeAndVersionDescriptor, IdType idType)

{

int blockSize = baseBlockSize;

// sanity checks

…

blockSize += AbstractDynamicStore.BLOCK_HEADER_SIZE;

// write the header

try

{

FileChannel channel = fileSystemAbstraction.create(fileName);

int endHeaderSize = blockSize

+ UTF8.encode( typeAndVersionDescriptor ).length;

ByteBuffer buffer = ByteBuffer.allocate( endHeaderSize );

buffer.putInt( blockSize );

buffer.position( endHeaderSize - typeAndVersionDescriptor.length() );

buffer.put( UTF8.encode( typeAndVersionDescriptor ) ).flip();

channel.write( buffer );

channel.force( false );

channel.close();

}

catch ( IOException e )

{

throw new UnderlyingStorageException( "Unable to create store "

+ fileName, e );

}

idGeneratorFactory.create( fileSystemAbstraction, new File( fileName.getPath() + ".id"), 0 );

// TODO highestIdInUse = 0 works now, but not when slave can create store files.

IdGenerator idGenerator = idGeneratorFactory.open(fileSystemAbstraction,

new File( fileName.getPath() + ".id"),idType.getGrabSize(), idType, 0 );

idGenerator.nextId(); // reserve first for blockSize

idGenerator.close();

}

]]>
http://searchtb.ruoguschool.com/2014/04/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%904.html/feed 1
neo4j 底层存储结构分析(3) http://searchtb.ruoguschool.com/2014/04/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%903.html http://searchtb.ruoguschool.com/2014/04/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%903.html#comments Thu, 24 Apr 2014 10:16:28 +0000 五竹 http://searchtb.ruoguschool.com/?p=4290 上一节: neo4j 底层存储结构分析(2)
下一节: neo4j 底层存储结构分析(4)

3.3  通用的Store 类型

3.3.1    id 类型

下面是 neo4j db 中,每种Store都有自己的ID文件(即后缀.id 文件),它们的格式都是一样的。

[test00]$ls -lh target/neo4j-test00.db/ |grep .id

-rw-r–r–9 04-11 13:28 neostore.id

-rw-r–r–9 04-11 13:28 neostore.labeltokenstore.db.id

-rw-r–r–9 04-11 13:28 neostore.labeltokenstore.db.names.id

-rw-r–r–9 04-11 13:28 neostore.nodestore.db.id

-rw-r–r–9 04-11

]]>
上一节: neo4j 底层存储结构分析(2)
下一节: neo4j 底层存储结构分析(4)

3.3  通用的Store 类型

3.3.1    id 类型

下面是 neo4j db 中,每种Store都有自己的ID文件(即后缀.id 文件),它们的格式都是一样的。

[test00]$ls -lh target/neo4j-test00.db/ |grep .id

-rw-r–r–9 04-11 13:28 neostore.id

-rw-r–r–9 04-11 13:28 neostore.labeltokenstore.db.id

-rw-r–r–9 04-11 13:28 neostore.labeltokenstore.db.names.id

-rw-r–r–9 04-11 13:28 neostore.nodestore.db.id

-rw-r–r–9 04-11 13:28 neostore.nodestore.db.labels.id

-rw-r–r–9 04-11 13:28 neostore.propertystore.db.arrays.id

-rw-r–r–9 04-11 13:28 neostore.propertystore.db.id

-rw-r–r–9 04-11 13:28 neostore.propertystore.db.index.id

-rw-r–r–9 04-11 13:28 neostore.propertystore.db.index.keys.id

-rw-r–r–9 04-11 13:28 neostore.propertystore.db.strings.id

-rw-r–r–9 04-11 13:28 neostore.relationshipgroupstore.db.id

-rw-r–r–9 04-11 13:28 neostore.relationshipstore.db.id

-rw-r–r–9 04-11 13:28 neostore.relationshiptypestore.db.id

-rw-r–r–9 04-11 13:28 neostore.relationshiptypestore.db.names.id

-rw-r–r–9 04-11 13:28 neostore.schemastore.db.id

3.3.1.1        ID类型文件的存储格式

neo4j_文件存储格式_id--20140408

neo4j 中后缀为 “.id”的文件格式如上图所示,由文件头(9 Bytes)和 long类型 数组 2部分构成:

  • sticky(1 byte) : if sticky the id generator wasn’t closed properly so it has to berebuilt (go through the node, relationship, property, rel type etc files).
  • nextFreeId(long) : 保存最大的ID,该值与对应类型的存储数组的数组大小相对应。
  • reuseId(long):用来保存已经释放且可复用的ID值。通过复用ID ,可以减少资源数组的空洞,提高磁盘利用率。

3.3.1.2        IdGeneratorImpl.java

每一种资源类型的ID 分配 neo4j 中是通过 IdGeneratorImpl 来实现的,其功能是负责ID管理分配和回收复用。对于节点,关系,属性等每一种资源类型,都可以生成一个IdGenerator  实例来负责其ID管理分配和回收复用。

3.3.1.2.1       读取id 文件进行初始化

下面试 IdGeneratorImpl.java 中, 读取id 文件进行初始化的过程,IdGeneratorImpl 会从 id 文件中读取grabSize 个可复用的ID (reuseId) 到idsReadFromFile(LinkedList<Long>) 中,在需要申请id 时优先分配 idsReadFromFile中的可复用ID。

<div>

// initialize the id generator and performs a simple validation

private synchronized void initGenerator()

{

try

{

fileChannel = fs.open( fileName, "rw" );

ByteBuffer buffer = ByteBuffer.allocate( HEADER_SIZE );

readHeader( buffer );

markAsSticky( buffer );

fileChannel.position( HEADER_SIZE );

maxReadPosition = fileChannel.size();

defraggedIdCount = (int) (maxReadPosition - HEADER_SIZE) / 8;

readIdBatch();

}

catch ( IOException e )

{

throw new UnderlyingStorageException(

"Unable to init id generator " + fileName, e );

}

}

private void readHeader( ByteBuffer buffer ) throws IOException

{

readPosition = fileChannel.read( buffer );

if ( readPosition != HEADER_SIZE )

{

fileChannel.close();

throw new InvalidIdGeneratorException(

"Unable to read header, bytes read: " + readPosition );

}

buffer.flip();

byte storageStatus = buffer.get();

if ( storageStatus != CLEAN_GENERATOR )

{

fileChannel.close();

throw new InvalidIdGeneratorException( "Sticky generator[ " +

fileName + "] delete this id file and build a new one" );

}

this.highId.set( buffer.getLong() );

}

private void readIdBatch()

{

if ( !canReadMoreIdBatches() )

return;

try

{

int howMuchToRead = (int) Math.min( grabSize*8, maxReadPosition-readPosition );

ByteBuffer readBuffer = ByteBuffer.allocate( howMuchToRead );

fileChannel.position( readPosition );

int bytesRead = fileChannel.read( readBuffer );

assert fileChannel.position() <= maxReadPosition;

readPosition += bytesRead;

readBuffer.flip();

assert (bytesRead % 8) == 0;

int idsRead = bytesRead / 8;

defraggedIdCount -= idsRead;

for ( int i = 0; i < idsRead; i++ )

{

long id = readBuffer.getLong();

if ( id != INTEGER_MINUS_ONE )

{

idsReadFromFile.add( id );

}

}

}

catch ( IOException e )

{

throw new UnderlyingStorageException(

"Failed reading defragged id batch", e );

}

}

3.3.1.2.2       释放id(freeId)

用户释放一个 id 后,会先放入 releasedIdList (LinkedList<Long>),当releasedIdList 中回收的 id 个数超过 grabSize 个时, 写入到 id 文件的末尾。所以可见,对于一个 IdGeneratorImpl, 最多有 2 * grabSize 个 id 缓存(releasedIdList 和 idsReadFromFile)。

<div>

/**

* Frees the <CODE>id</CODE> making it a defragged id that will be

* returned by next id before any new id (that hasn't been used yet) is

* returned.

* <p>

* This method will throw an <CODE>IOException</CODE> if id is negative or

* if id is greater than the highest returned id. However as stated in the

* class documentation above the id isn't validated to see if it really is

* free.

*/

@Override

public synchronized void freeId( long id )

{

if ( id == INTEGER_MINUS_ONE )

{

return;

}

if ( fileChannel == null )

{

throw new IllegalStateException( "Generator closed " + fileName );

}

if ( id < 0 || id >= highId.get() )

{

throw new IllegalArgumentException( "Illegal id[" + id + "]" );

}

releasedIdList.add( id );

defraggedIdCount++;

if ( releasedIdList.size() >= grabSize )

{

writeIdBatch( ByteBuffer.allocate( grabSize*8 ) );

}

}

3.3.1.2.3       申请id ( nextId)

当用户申请一个 id  时,IdGeneratorImpl 在分配时,有2种分配策略: “正常的分配策略” 和激进分配策略”(aggressiveReuse),可以根据配置进行选择。

n  “正常的分配策略”:

a)        首先从idsReadFromFile 中分配; 如果 idsReadFromFile 为空,则先从对应的 id 文件中读取已释放且可复用的 id 到idsReadFromFile.

b)        如果 idsReadFromFile 及 id 文件中没有已释放且可复用的 id了,则分配全新的id,即id = highId.get()  并将highId 加1;

n   “激进分配策略”(aggressiveReuse):

a)        首先从releasedIdList(刚回收的ID List)中分配。

b)        releasedIdList分配光了,则从idsReadFromFile 中分配; 如果 idsReadFromFile 为空,则先从对应的 id 文件中读取已释放且可复用的 id 到idsReadFromFile.

c)        如果 idsReadFromFile 及 id 文件中没有已释放且可复用的 id了,则分配全新的id,即id = highId.get()  并将highId 加1;

<div>

/**

* 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.

*/

@Override

public synchronized long nextId()

{

assertStillOpen();

long nextDefragId = nextIdFromDefragList();

if ( nextDefragId != -1 ) return nextDefragId;

long id = highId.get();

if ( id == INTEGER_MINUS_ONE )

{

// Skip the integer -1 (0xFFFFFFFF) because it represents

// special values, f.ex. the end of a relationships/property chain.

id = highId.incrementAndGet();

}

assertIdWithinCapacity( id );

highId.incrementAndGet();

return id;

}

]]>
http://searchtb.ruoguschool.com/2014/04/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%903.html/feed 0
neo4j 底层存储结构分析(2) http://searchtb.ruoguschool.com/2014/04/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%902.html http://searchtb.ruoguschool.com/2014/04/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%902.html#comments Thu, 24 Apr 2014 08:46:44 +0000 五竹 http://searchtb.ruoguschool.com/?p=4244 上一节: neo4j 底层存储结构分析(1)
下一节: neo4j 底层存储结构分析(3)

3       neo4j存储结构

neo4j 中,主要有4类节点,属性,关系等文件是以数组作为核心存储结构;同时对节点,属性,关系等类型的每个数据项都会分配一个唯一的ID,在存储时以该ID 为数组的下标。这样,在访问时通过其ID作为下标,实现快速定位。所以在图遍历等操作时,可以实现 free-index。

3.1  neo4j 的 store 部分类图

neo4j-类图--store继承图

3.1.1   CommonAbstractStore.java

CommonAbstractStore 是所有 Store 类的基类,下面的代码片段是 CommonAbstractStore 的成员变量,比较重要的是飘红的几个,特别是IdGenerator,每种Store 的实例都有自己的 id 分配管理器; StoreChannel 是负责Store文件的读写和定位;WindowsPool 是与Store Record相关的缓存,用来提升性能的。

1.2  neo4j 的db文件及对应的存储格式类型

文件名 文件存储格式
neostore.labeltokenstore.db LabelTokenStore(TokenStore)
neostore.labeltokenstore.db.id
]]>
上一节: neo4j 底层存储结构分析(1)
下一节: neo4j 底层存储结构分析(3)

3       neo4j存储结构

neo4j 中,主要有4类节点,属性,关系等文件是以数组作为核心存储结构;同时对节点,属性,关系等类型的每个数据项都会分配一个唯一的ID,在存储时以该ID 为数组的下标。这样,在访问时通过其ID作为下标,实现快速定位。所以在图遍历等操作时,可以实现 free-index。

3.1  neo4j 的 store 部分类图

neo4j-类图--store继承图

3.1.1   CommonAbstractStore.java

CommonAbstractStore 是所有 Store 类的基类,下面的代码片段是 CommonAbstractStore 的成员变量,比较重要的是飘红的几个,特别是IdGenerator,每种Store 的实例都有自己的 id 分配管理器; StoreChannel 是负责Store文件的读写和定位;WindowsPool 是与Store Record相关的缓存,用来提升性能的。

</pre>
<div>public abstract class CommonAbstractStore implements IdSequence

{

public static abstract class Configuration

{

public static final Setting store_dir = InternalAbstractGraphDatabase.Configuration.store_dir;

public static final Setting neo_store = InternalAbstractGraphDatabase.Configuration.neo_store;

public static final Setting read_only = GraphDatabaseSettings.read_only;

public static final Setting backup_slave = GraphDatabaseSettings.backup_slave;

public static final Setting use_memory_mapped_buffers = GraphDatabaseSettings.use_memory_mapped_buffers;

}

public static final String ALL_STORES_VERSION = "v0.A.2";

public static final String UNKNOWN_VERSION = "Uknown";

protected Config configuration;

private final IdGeneratorFactory idGeneratorFactory;

private final WindowPoolFactory windowPoolFactory;

protected FileSystemAbstraction fileSystemAbstraction;

protected final File storageFileName;

protected final IdType idType;

protected StringLogger stringLogger;

private IdGenerator idGenerator = null;

private StoreChannel fileChannel = null;

private WindowPool windowPool;

private boolean storeOk = true;

private Throwable causeOfStoreNotOk;

private FileLock fileLock;

private boolean readOnly = false;

private boolean backupSlave = false;

private long highestUpdateRecordId = -1;

1.2  neo4j 的db文件及对应的存储格式类型

文件名 文件存储格式
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)
neostore.schemastore.db.id ID 类型
]]>
http://searchtb.ruoguschool.com/2014/04/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%902.html/feed 0
neo4j 底层存储结构分析(1) http://searchtb.ruoguschool.com/2014/04/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%901.html http://searchtb.ruoguschool.com/2014/04/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%901.html#comments Thu, 24 Apr 2014 08:31:35 +0000 五竹 http://searchtb.ruoguschool.com/?p=4202 –五竹(孙权)  20140424

1       neo4j 中节点和关系的物理存储模型

1.1  neo4j存储模型

neo4j_存储模型_graphdatabase

    The node records contain only a pointer to their first property and their first relationship (in what is oftentermed the _relationship chain). From here, we can follow the (doubly)

]]>
–五竹(孙权)  20140424

1       neo4j 中节点和关系的物理存储模型

1.1  neo4j存储模型

neo4j_存储模型_graphdatabase

    The node records contain only a pointer to their first property and their first relationship (in what is oftentermed the _relationship chain). From here, we can follow the (doubly) linked-list of relationships until we find the one we’re interested in, the  LIKES relationship from  Node 1 to  Node 2 in this case. Once we’ve found the relationship record of interest, we can simply read its properties if there are any via the same singly-linked list structure as node properties, or we can examine the node records that it relates via its start node and end node IDs. These IDs, multiplied by the node record size, of course give the immediate offset of both nodes in the node store file.

上面的英文摘自<Graph Databases>(作者:IanRobinson) 一书,描述了 neo4j 的存储模型。Node和Relationship 的 Property 是用一个 Key-Value 的双向列表来保存的; Node 的 Relatsionship 是用一个双向列表来保存的,通过关系,可以方便的找到关系的 from-to Node. Node 节点保存第1个属性和第1个关系ID。

通过上述存储模型,从一个Node-A开始,可以方便的遍历以该Node-A为起点的图。下面给个示例,来帮助理解上面的存储模型,存储文件的具体格式在第2章详细描述。

1.2  示例1

neo4j_存储结构示例_关系图例子--20140329

在这个例子中,A~E表示Node 的编号,R1~R7 表示 Relationship 编号,P1~P10 表示Property 的编号。

  • Node 的存储示例图如下,每个Node 保存了第1个Property 和 第1个Relationship:

neo4j_存储结构示例_node存储--20140329

  • 关系的存储示意图如下:

neo4j_存储结构示例_relationship存储_20140329

从示意图可以看出,从 Node-B 开始,可以通过关系的 next 指针,遍历Node-B 的所有关系,然后可以到达与其有关系的第1层Nodes,在通过遍历第1层Nodes的关系,可以达到第2层Nodes,…

2       neo4j graph db的存储文件介绍

当我们下载neo4j-community-2.1.0-M01 并安装,然后拿 neo4j embedded-example 的EmbeddedNeo4j 例子跑一下,可以看到在target/neo4j-hello-db下会生成如下neo4j graph db 的存储文件。

-rw-r–r–     11 04-11 13:28 active_tx_log

drwxr-xr-x   4096 04-11 13:28 index

-rw-r–r–  23740 04-11 13:28 messages.log

-rw-r–r–     78 04-11 13:28 neostore

-rw-r–r–      9 04-11 13:28 neostore.id

-rw-r–r–     22 04-11 13:28 neostore.labeltokenstore.db

-rw-r–r–      9 04-11 13:28 neostore.labeltokenstore.db.id

-rw-r–r–     64 04-11 13:28 neostore.labeltokenstore.db.names

-rw-r–r–      9 04-11 13:28 neostore.labeltokenstore.db.names.id

-rw-r–r–     61 04-11 13:28 neostore.nodestore.db

-rw-r–r–      9 04-11 13:28 neostore.nodestore.db.id

-rw-r–r–     93 04-11 13:28 neostore.nodestore.db.labels

-rw-r–r–      9 04-11 13:28 neostore.nodestore.db.labels.id

-rw-r–r–    307 04-11 13:28 neostore.propertystore.db

-rw-r–r–    153 04-11 13:28 neostore.propertystore.db.arrays

-rw-r–r–      9 04-11 13:28 neostore.propertystore.db.arrays.id

-rw-r–r–      9 04-11 13:28 neostore.propertystore.db.id

-rw-r–r–     61 04-11 13:28 neostore.propertystore.db.index

-rw-r–r–      9 04-11 13:28 neostore.propertystore.db.index.id

-rw-r–r–    216 04-11 13:28 neostore.propertystore.db.index.keys

-rw-r–r–      9 04-11 13:28 neostore.propertystore.db.index.keys.id

-rw-r–r–    410 04-11 13:28 neostore.propertystore.db.strings

-rw-r–r–      9 04-11 13:28 neostore.propertystore.db.strings.id

-rw-r–r–     69 04-11 13:28 neostore.relationshipgroupstore.db

-rw-r–r–      9 04-11 13:28 neostore.relationshipgroupstore.db.id

-rw-r–r–     92 04-11 13:28 neostore.relationshipstore.db

-rw-r–r–      9 04-11 13:28 neostore.relationshipstore.db.id

-rw-r–r–     38 04-11 13:28 neostore.relationshiptypestore.db

-rw-r–r–      9 04-11 13:28 neostore.relationshiptypestore.db.id

-rw-r–r–    140 04-11 13:28 neostore.relationshiptypestore.db.names

-rw-r–r–      9 04-11 13:28 neostore.relationshiptypestore.db.names.id

-rw-r–r–     82 04-11 13:28 neostore.schemastore.db

-rw-r–r–      9 04-11 13:28 neostore.schemastore.db.id

-rw-r–r–      4 04-11 13:28 nioneo_logical.log.active

-rw-r–r–   2249 04-11 13:28 nioneo_logical.log.v0

drwxr-xr-x   4096 04-11 13:28 schema

-rw-r–r–      0 04-11 13:28 store_lock

-rw-r–r–    800 04-11 13:28 tm_tx_log.1

2.1  存储 node 的文件

1)          存储节点数据及其序列Id

  • neostore.nodestore.db:  存储节点数组,数组的下标即是该节点的ID
  • neostore.nodestore.db.id  :存储最大的ID 及已经free的ID

2)          存储节点label及其序列Id

  •  neostore.nodestore.db.labels  :存储节点label数组数据,数组的下标即是该节点label的ID
  • neostore.nodestore.db.labels.id

2.2  存储 relationship 的文件

1)          存储关系数据及其序列Id

  • neostore.relationshipstore.db 存储关系 record 数组数据
  • neostore.relationshipstore.db.id

2)          存储关系组数据及其序列Id

  • neostore.relationshipgroupstore.db  存储关系 group数组数据
  • neostore.relationshipgroupstore.db.id

3)          存储关系类型及其序列Id

  •  neostore.relationshiptypestore.db  存储关系类型数组数据
  •  neostore.relationshiptypestore.db.id

4)          存储关系类型的名称及其序列Id

  • neostore.relationshiptypestore.db.names存储关系类型 token 数组数据
  • neostore.relationshiptypestore.db.names.id

2.3  存储 label 的文件

1)          存储label token数据及其序列Id

  • neostore.labeltokenstore.db  存储lable token 数组数据
  • neostore.labeltokenstore.db.id

2)          存储label token名字数据及其序列Id

  • neostore.labeltokenstore.db.names  存储 label token 的 names 数据
  • neostore.labeltokenstore.db.names.id

2.4  存储 property 的文件

1)          存储属性数据及其序列Id

  • neostore.propertystore.db  存储 property 数据
  • neostore.propertystore.db.id

2)          存储属性数据中的数组类型数据及其序列Id

  • neostore.propertystore.db.arrays  存储 property (key-value 结构)的Value值是数组的数据。
  • neostore.propertystore.db.arrays.id

3)          属性数据为长字符串类型的存储文件及其序列Id

  • neostore.propertystore.db.strings     存储 property (key-value 结构)的Value值是字符串的数据。
  • neostore.propertystore.db.strings.id

4)          属性数据的索引数据文件及其序列Id

  • neostore.propertystore.db.index       存储 property (key-value 结构)的key 的索引数据。
  • neostore.propertystore.db.index.id

5)          属性数据的键值数据存储文件及其序列Id

  •  neostore.propertystore.db.index.keys     存储 property (key-value 结构)的key 的字符串值。
  • neostore.propertystore.db.index.keys.id

2.5  其他的文件

1)          存储版本信息

  •  neostore
  • neostore.id

2)          存储 schema 数据

  • neostore.schemastore.db
  •  neostore.schemastore.db.id

3)          活动的逻辑日志

  • nioneo_logical.log.active

4)          记录当前活动的日志文件名称

  •  active_tx_log
 
]]>
http://searchtb.ruoguschool.com/2014/04/neo4j-%e5%ba%95%e5%b1%82%e5%ad%98%e5%82%a8%e7%bb%93%e6%9e%84%e5%88%86%e6%9e%901.html/feed 0
HQueue:基于HBase的消息队列 http://searchtb.ruoguschool.com/2014/04/hqueue%ef%bc%9a%e5%9f%ba%e4%ba%8ehbase%e7%9a%84%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97.html http://searchtb.ruoguschool.com/2014/04/hqueue%ef%bc%9a%e5%9f%ba%e4%ba%8ehbase%e7%9a%84%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97.html#comments Thu, 24 Apr 2014 06:35:11 +0000 凌柏 http://searchtb.ruoguschool.com/?p=4144 ​1. HQueue简介

HQueue是一淘搜索网页抓取离线系统团队基于HBase开发的一套分布式、持久化消息队列。它利用HTable存储消息数据,借助HBase Coprocessor将原始的KeyValue数据封装成消息数据格式进行存储,并基于HBase Client API封装了HQueue Client API用于消息存取。

HQueue可以有效使用在需要存储时间序列数据、作为MapReduce Job和iStream等输入、输出供上下游共享数据等场合。

​2. HQueue特性

由于HQueue是基于HBase进行消息存取的,因此站在HDFS和HBase的肩膀上,使得其具备如下特点:

​(1)支持多Partitions,可根据需求设置Queue的规模,支持高并发访问(HBase的多Region);

​(2)​支持自动Failover,任何机器Down掉,Partition可自动迁移至其他机器(HBase的Failover机制);

(3)​支持动态负载均衡,Partition可以动态被调度到最合理的机器上(HBase的LoadBalance机制,可动态调整);

​(4)利用HBase进行消息的持久化存储,不丢失数据(HBase HLog和HDFS Append);

​(5)队列的读写模式与HBase的存储特性天然切合,具备良好的并发读写性能(最新消息存储在MemStore中,写消息直接写入MemStore,通常场景下都是内存级操作);

​(6)支持消息按Topic进行分类存取(HBase中的Qualifier);

​(7)支持消息TTL,自动清理过期消息(HBase支持KeyValue级别的TTL);

​(8)HQueue = HTable Schema Design + HQueue Coprocessor + HBase Client Wrapper,完全扩展开发,无任何Hack工作,可随HBase自动升级;

(9)​HQueue Client API基于HBase Client Wrapper进行简单封装,HBase的ThriftServer使得其支持多语言API,因此HQueue也很容易封装出多语言API;
(10)HQueue …

]]>
​1. HQueue简介

HQueue是一淘搜索网页抓取离线系统团队基于HBase开发的一套分布式、持久化消息队列。它利用HTable存储消息数据,借助HBase Coprocessor将原始的KeyValue数据封装成消息数据格式进行存储,并基于HBase Client API封装了HQueue Client API用于消息存取。

HQueue可以有效使用在需要存储时间序列数据、作为MapReduce Job和iStream等输入、输出供上下游共享数据等场合。

​2. HQueue特性

由于HQueue是基于HBase进行消息存取的,因此站在HDFS和HBase的肩膀上,使得其具备如下特点:

​(1)支持多Partitions,可根据需求设置Queue的规模,支持高并发访问(HBase的多Region);

​(2)​支持自动Failover,任何机器Down掉,Partition可自动迁移至其他机器(HBase的Failover机制);

(3)​支持动态负载均衡,Partition可以动态被调度到最合理的机器上(HBase的LoadBalance机制,可动态调整);

​(4)利用HBase进行消息的持久化存储,不丢失数据(HBase HLog和HDFS Append);

​(5)队列的读写模式与HBase的存储特性天然切合,具备良好的并发读写性能(最新消息存储在MemStore中,写消息直接写入MemStore,通常场景下都是内存级操作);

​(6)支持消息按Topic进行分类存取(HBase中的Qualifier);

​(7)支持消息TTL,自动清理过期消息(HBase支持KeyValue级别的TTL);

​(8)HQueue = HTable Schema Design + HQueue Coprocessor + HBase Client Wrapper,完全扩展开发,无任何Hack工作,可随HBase自动升级;

(9)​HQueue Client API基于HBase Client Wrapper进行简单封装,HBase的ThriftServer使得其支持多语言API,因此HQueue也很容易封装出多语言API;
(10)HQueue Client API可以天然支持Hadoop MapReduce Job和iStream的InputFormat机制,利用Locality特性将计算调度到存储最近的机器;

​(11)HQueue支持消息订阅机制(HQueue 0.3及后续版本)。

3. HQueue系统设计及处理流程

3.1. HQueue系统结构

​HQueue系统结构如图(1)所示:

HQueue系统结构图(1):HQueue系统结构

其中:​

​(1)每个Queue对应一个HTable,创建Queue可以通过Presharding Table方式创建,有利于负载均衡。

​(2)每个Queue可以有多个Partitions(HBase Regions),这些Partitions均匀分布在HBase集群中的多个Region Servers中。

​(3)每个Partition可以在HBase集群的多个Region Servers中动态迁移。任何一台Region Server挂掉,运行在其上的HQueue Partition可以自动迁移到其他Region Server上,并且数据不会丢失。当集群负载不均衡时,HQueue Partition会自动被HMaster迁移到负载低的Region Server。

​(4)每个Message对应一个HBase KeyValue Pair,按MessageID即时间顺序存储在HBase Region中。MessageID由Timestamp和同一Timestamp下自增的SequenceID构成,详细信息参见《Message存储结构》部分。

​3.2. Message存储结构

​Message存储结构如图(2)所示:

Message存储结构

图(2):Message存储结构

其中:​

​(1)RowKey:由PartitionID和MessageID构成。

  • ​PartitionID:​一个Queue可以有多个Partitions,目前最多支持Short.MAX_VALUE个Partitions。Partition ID可以不在创建Message对象时指定,而是在发送消息时设定,或者不指定而使用一个随机Partition ID。
  • ​MessageID:即消息ID,它由Timestamp和SequenceID两部分组成。Timestamp是消息写入HQueue时的时间戳,单位为毫秒。SequenceID是同一Timestamp下消息的顺序编号,目前最多支持同一Timestamp下Short.MAX_VALUE个Messages。

​(2)Column:由Column Family和Message Topic构成。

  • Column Family:HBase Column Family,此处为固定值“message”。
  • Message Topic :HBase Column Qualifier,消息Topic名称。用户可以根据需要将Message存储在不同的Topics之下,也可以从Queue中获取感兴趣的Topics消息数据。

​(3)Value:即消息内容。

​3.3. HQueue消息写入及Coprocessor处理流程

​HQueue利用HQueue Client API写入消息数据,为保证消息唯一和有序,HQueue利用Coprocessor处理用户写入消息的MessageID,然后立即放入HBase MemStore中,使其可以被访问到,最后持久化的HLog中。具体的处理逻辑如图(3)所示:

数据写入及Coprocessor处理流程

​图(3):数据写入及Coprocessor处理流程

​其中:

​(1)HQueue封装了HQueue Client API,用户可以使用其提供Put等方法向HQueue中写入消息。

(2)HQueue Client会使用Message.makeKeyValueRow()用于完成将Message数据结构转换成HBase Rowkey。HQueue所要求的RowKey格式可以参加上述内容。
(3)HQueue Client在完成RowKey的转换后,会调用HTable的put方法按照HBase标准的写入流程来完成消息的写入。
(4)HQueue上注册有HQueueCoprocessor,它扩展自BaseRegionObserver。HRegion在真正写入消息数据前,会调用HQueueCoprocessor的preBatchMutate方法,该方法主要用于调整MessageID,保证MessageID唯一并且有序。
(5)在HQueueCoprocessor的preBatchMutate方法中同时会调整Durability为SKIP_WAL,这样HBase将不会主动将消息数据持久化进HLog。
(6)HRegion在写入消息数据后,会调用HQueueCoprocessor的postBatchMutate方法,该方法主要完成将消息数据持久化进HLog的功能。

​3.4. HQueue Scan处理流程

​为了方便从Queue中Scan数据,HQueue封装了ClientScanner,提供了QueueScanner、PartitionScanner和CombinedPartitionScanner等Scanner,用于不同的场景。HQueue Scan的具体处理流程如图(4)所示:

HQueue Scan处理流程

图(4):HQueue Scan处理流程

其中:

(1)用户可以根据需要从HQueue Client中获取所需的Queue Scanner,目前主要提供三种Scanner:

  • QueueScanner:用于Scan Queue中全部Partitions的数据;
  • ​PartitionScanner:用于Scan Queue中指定Partition的数据;
  • ​CombinedPartitionScanner:用于Scan Queue中若干指定Partitions的数据。

(2)用户获取到Scanner之后,可以循环调用Scanner的next方法依次取出消息数据,直至无数据返回,本次Scan结束。Scan结束后,用户应主动关闭Scanner以便及时释放资源。
(3)用户在不再使用先前创建的Queue对象时,应主动关闭Queue以便及时释放资源。

​3.5. HQueue订阅流程

3.5.1. 整体流程

HQueue自0.3版本开始提供订阅功能,一个订阅者可以订阅一个Queue的多个Partitions、多个Topics。与用户使用Scanner主动Scan消息数据的方式相比,订阅方式具有(1)消息数据一旦写入Queue便会被主动推送至订阅者,消息送达更为及时;(2)订阅者被动接收新消息,可以省去HQueue无新消息数据时多余的Scan操作,减少系统开销等优点。

HQueue订阅流程处理逻辑如图(5)所示:

HQueue订阅流程处理逻辑

​图(5):HQueue订阅流程处理逻辑

其中:

(1)HQueue订阅主要由Subscriber、ZooKeeper和Coprocessor这三部分组成。其中:

  • ​Subscrier:即订阅者。主要完成向ZoeoKeeper写入订阅信息、启动监听、接收新消息并回调注册在其上的消息处理函数(MessageListener)等功能。
  • ​ZooKeeper:用于保存订阅者提交的订阅信息,主要包括订阅者订阅的Queue、Partitions和Topics;订阅者的地址和Checkpoint等信息,更为详细信息参见后续描述。
  • ​Coprocessor:主要完成从ZooKeeper获取订阅信息、使用InternalScanner从Queue中Scan最新的消息、将新消息发送至订阅者并将当前Checkpoint更新至ZooKeeper等功能。

(2)Coprocessor的主要处理流程如下:
Step 1:创建Subscriber,添加订阅信息和消息处理函数,将订阅信息写入ZooKeeper,启动监听等待接收新消息。写入ZooKeeper中的订阅信息主要包括:

  • ​订阅者订阅的Queue名称;
  • ​订阅者订阅的Queuee Partitions以及各Partition上消息的起始ID。一个订阅者可以订阅多个Partitions,如果没有指定,那么认为订阅该Queue的所有Partitions。
  • ​订阅者订阅的消息Topics。一个订阅者可以订阅多个主题,如果没有指定,那么认为订阅该Queue上的所有Topics。
  • ​订阅者的Addresss/Hostname和监听端口。用户创建订阅者时可以指定监听端口,如果没有指定,那么会随机选择一个当前可用端口作为监听端口。

Step 2:Coprocessor从ZooKeeper获取订阅信息并向ZooKeeper注册相关Watcher,以便ZooKeeper中订阅信息发生变化时ZooKeeper能够及时通知Coprocessor。Coprocessor在获取到订阅信息后,会根据需要创建SubscriptionWorker等工作线程,以便从HQueue Partition中Scan消息并将消息发送至Subscriber。
Step 3:Coprocessor从HQueue Partition中Scan新消息。
Step 4:Coprocessor将新消息发送至Subscriber。
Step 5:Subscriber在接收到新消息时,会回调注册在其上的回调函数。
Step 6:待新消息发送成功后,Coprocessor会将消息的Checkpoint更新至ZooKeeper以便后续使用。
Step 7:Subscriber取消订阅,并从ZooKeeper中删除必要的订阅信息。
Step 8:ZooKeeper会通过注册在其上的Watcher将Subscriber订阅信息的变化通知至Coprocessor,Coprocessor根据订阅信息的变化,暂停SubscriptionWorker等工作线程等。

3.5.2. HQueue Subscriber

​HQueue Subscriber结构和主要处理逻辑如图(6)所示:

HQueue Subscriber结构和主要处理逻辑

​图(6):HQueue Subscriber结构和主要处理逻辑

其中:

​(1)Subscriber主要由两部分组成:SubscriberZooKeeper和Thrift Server。其中,SubscriberZooKeeper主要完成与ZooKeeper相关的若干操作,包括写入订阅信息、删除订阅信息等。Coprocessor与Subscriber之间的通讯通过Thrift来完成,Subscriber中启动Thrift Server,监听指定的端口,等待接收Coprocessor发送过来的新消息。

(2)Subscriber通过Thrift Server接收到新消息后,会回调注册在其上的回调函数(MessageListeners),并将状态码返回给Coprocessor。
(3)可以在一个Subscriber上注册多个MessageListeners,多个MessageListeners会被依次调用。

​3.5.3. HQueue Coprocessor

​HQueue Coprocessor结构和主要处理逻辑如图(7)所示:

HQueue Coprocessor结构和主要处理逻辑

​图(7):HQueue Coprocessor结构和主要处理逻辑

其中:

​(1)Coprocessor:主要由两部分构成SubscriptionZooKeeper和SubscriptionWorker。

  • SubscriptionZooKeeper:主要完成与ZooKeeper相关的工作,包括从ZooKeeper获取订阅信息并注册相关Watcher、SubscriptionWorker将Checkpoint更新至ZooKeeper等操作。
  • SubscriptionWorker又主要包括MessageScanner和MessageSender两部分,主要完成Scan新消息、发送消息至Subscriber和更新Checkpoint等操作。

(2)MessageScanner主要完成创建InternalScanner,从Queue Partition中Scan新消息,并将其放入缓冲队列中等操作。

  • ​当缓冲队列中没有空闲空间时,MessageScanner会等待直至缓冲队列中的消息被MessageSender消费掉,腾出剩余空间。
  • ​当Queue Partition中没有新消息时,MessageScanner会主动Sleep,当有新消息写入时,Coprocessor会通过SubscriptionWorker唤醒MessageScanner,开始新一轮Scan。

(3)MessageSender主要完成从缓冲队列中取出新消息,将其发送至Subscriber,并等待Subscriber发回响应等操作。当缓冲队列中没有新消息时,MessageSender会等待直至有新消息到来。
(4)MessageSender中的CheckpointUpdater会定时将当前的Checkpoint写入ZooKeeper中的相关订阅节点中,以便后续使用。

​3.5.4. 订阅信息层次结构

HQueue相关订阅信息保存在ZooKeeper,ZooKeeper中订阅信息的层次结构如图(8)所示:

订阅信息层次结构

图(8):订阅信息层次结构

其中:

(1)订阅者节点(subscriber_x)上会记录该订阅者在Queue Partition上的Checkpoint。该Checkpoint由Subscriber在发起订阅时写入,并由SubscriptionWorker MessageSender中的CheckpointUpdater来更新。
(2)订阅者节点下会有两个临时性节点:address和topics,分别保存订阅者的IP Address/Hostname:Port和订阅的主题。当订阅者主动取消订阅时会删除这两个临时节点,当订阅者意外退出时,等Session失效后,ZooKeeper会删除该临时节点。

​3.5.5. 订阅者Thrift Service

HQueue订阅功能使用Thrift来简化对多语言客户端的支持。Subscriber启动Thrift Server,监听指定端口,接收消息,并回调MessageListeners以便处理消息。用于描述HQueue Subscriber所提供服务的接口定义如下所示:

namespace java com.etao.hadoop.hbase.queue.thrift.generated
/**
* HQueue MessageID
*/
struct TMessageID {
  1: i64 timestamp,
  2: i16 sequenceID
}
/**
* HQueue Message
*/
struct TMessage {
  1: optional TMessageID id,
  2: optional i16 partitionID,
  3: binary topic,
  4: binary value
}
/**
* HQueue Subscriber Service
*/
service HQueueSubscriberService {
  i32 consumeMessages(1:list<TMessage> messages)
}

4. HQueue使用

4.1. HQueue Toolkit

为方便用户使用,HQueue封装了HQueue Client API用于存取消息数据。自HQueue 0.3版本,HQueue日志运维工具集成进HQueue Shell中,构成HQueue Toolkit,为用户提供一站式服务,方便用户管理Queue以及Queue订阅者。

同HBase Shell使用方式相似,用户使用$ ${HBASE_HOME}/bin/hqueue shell便可以进入HQueue Shell命令行工具。需要注意的是,用户在使用HQueue Toolkit之前需要确保已经部署HQueue Toolkit。

​ HQueue Toolkit中包括创建Queue、Disable Queue、Enable Queue、删除Queue和清空Queue等命令。​使用示例如下:

(1)创建队列

USAGE:create ‘queue_name’, partition_count, ttl, [Configuration Dictionary]

DESCRIPTIONS:

  • queue_name:待创建的HQueue的名称,必选参数。
  • partition_count:待创建的HQueue的Partition个数,必选参数。
  • ttl:失效时间,必选参数。
  • Configuration Dictonary:可选配置参数。目前支持的配置参数为:(1)hbase.hqueue.partitionsPerRegion;(2)hbase.hregion.memstore.flush.size;(3)hbase.hregion.majorcompaction;(4)hbase.hstore.compaction.min;(5)hbase.hstore.compaction.max;(6)hbase.hqueue.compression;(7)hbase.hstore.blockingStoreFiles等。
EXAMPLES:
  • hqueue> create ‘q1′, 32, 86400
  • hqueue> create ‘q1′, 32, 86400, {‘hbase.hqueue.partitionsPerRegion’ => ’4′, ‘hbase.hstore.compaction.min’ => ’16′, ‘hbase.hstore.compaction.max’ => ’32′}

(2)清空队列

USAGE:truncate_queue 'queue_name'
DESCRIPTIONS:
  • queue_name:待清空的Queue名称,必选参数。
EXAMPLES:
  • hqueue(main):013:0> truncate_queue 'replication_dev_2_test_queue'
需要注意的是:该命令与HBase Shell中的truncate有所不同,该命令仅会删除Queue中的数据,而保留Queue的Presharding信息。
​    更多操作请参阅:http://searchwiki.taobao.ali.com/index.php/HQueue_Toolkit#Queue.E7.AE.A1.E7.90.86
(3)新增订阅者
USAGE:add_subscriber 'queue_name', 'subscriber_name'
DESCRIPTIONS:
  • queue_name:队列名称,必选参数。
  • subscriber_name:订阅者名称,必选参数。
EXAMPLES:
  • add_subscriber 'replication_dev_2_test_queue', 'subscriber_1'

(4)删除订阅者

USAGE:delete_subscriber 'subscriber_name', 'queue_name'
DESCRIPTIONS:
  • queue_name:订阅者所订阅的Queue名称,必选参数。
  • subscriber_name:订阅者名称,必选参数。
EXAMPLES:
  • hqueue(main):040:0> delete_subscriber 'replication_dev_2_test_queue', 'subscriber_1'

更多信息可以参阅:http://searchwiki.taobao.ali.com/index.php/HQueue_Toolkit#.E8.AE.A2.E9.98.85.E8.80.85.E7.AE.A1.E7.90.86

4.2. Put

​HQueue Client API中的Put相关操作可以完成将用户消息数据写入HQueue中,Put支持批量操作,具体使用方式示例如下:

HQueue queue = new HQueue(queueName);

String topic1 = "crawler";
String value1 = "http://www.360test.com";

// 写入单条消息数据,不指定Partition ID。在不指定Partition ID的情况下,将会在Queue的所有Partitions中随机选取一个。
Message message1 = new Message(Bytes.toBytes(topic1), Bytes.toBytes(value1));
queue.put(message);

// 写入Message时,显式指定PartitionID。
short partitionID = 10;
queue.put(partitionID, message1);

List<Message> messages = new ArrayList<Message>();
messages.add(message1);

String topic2 = "dump";
String value2 = "http://www.jd.com";
Message message2 = new Message(Bytes.toBytes(topic2), Bytes.toBytes(value2));
messages.add(message2);

// 写入多条消息数据,不指定Partition ID。		
queue.put(messages);

// 写入多条消息数据,指定Partition ID。
queue.put(partitionID, messages);

queue.close();

4.3. Scan

​为方便用户从Queue中Scan消息数据,HQueue Client API提供了三种自定义Scanner,分别为:QueueScanner、PartitionScanner和CombinedPartitionScanner,使用示例如下:

String queueName = "subscription_queue";
Queue queue = new HQueue(queueName);

// 起始时间戳
long currentTimestamp = System.currentTimeMillis();
MessageID startMessageID = new MessageID(currentTimestamp - 6000);
MessageID stopMessageID = new MessageID(currentTimestamp);

Scan scan = new Scan(startMessageID, stopMessageID);
// 添加主题
scan.addTopic(Bytes.toBytes("topic1"));
scan.addTopic(Bytes.toBytes("topic2"));

Message message = null;

// 使用QueueScanner,扫描Queue下全部Partitions中的数据
QueueScanner queueScanner = queue.getQueueScanner(scan);
while ((message = queueScanner.next()) != null) {
    // no-op
}
queueScanner.close();

short partitionID1 = 1;

// 使用PartitionScanner,扫描Queue中指定的Partition的数据
PartitionScanner partitionScanner = queue.getPartitionScanner(partitionID1, scan);
while ((message = partitionScanner.next()) != null) {
    // no-op
}
​partitionScanner.close();

short partitionID2 = 2;
Map<Short, Scan> partitions = new HashMap<Short, Scan>();
// 添加多个Partitions
partitions.put(partitionID1, scan);
partitions.put(partitionID2, scan);

CombinedPartitionScanner combinedScanner = queue.getCombinedPartitionScanner(partitions);
while ((message = combinedScanner.next()) != null) {
    // no-op
}
​combinedScanner.close();
​
​queue.close();

​4.4. 订阅消息

​HQueue自0.3版本开始提供订阅功能,使用方式示例如下:

HQueue queue = null;
HQueueSubscriber subscriber = null;

try {
    String queueName = "subscription_queue";
    queue = new HQueue(queueName);

    Set<Pair<Short, MessageID>> partitions = new HashSet<Pair<Short, MessageID>>();

    // 添加所订阅的Partitions		
    Pair<Short, MessageID> partition1 = new Pair<Short, MessageID>((short)0, null);
    partitions.add(partition1);
    Pair<Short, MessageID> partition2 = new Pair<Short, MessageID>((short)1, null);
    partitions.add(partition2);
    Pair<Short, MessageID> partition3 = new Pair<Short, MessageID>((short)2, null);
    partitions.add(partition3);

    // 添加所订阅的Topics			
    Set<String> topics = new HashSet<String>();
    topics.add("topic_1");
    topics.add("topic_2");
    topics.add("topic_3");

    // 订阅者名称		    
    String subscriberName = "subscriber_1";

    Subscription subscription = new Subscription(subscriberName, topics);
    subscription.addPartitions(partitions);

    // 添加回调函数			
    List<MessageListener> listeners = new LinkedList<MessageListener>();
    MessageListener blackHoleListener = new BlackHoleMessageListener(subscriberName);
    listeners.add(blackHoleListener);

    // 创建订阅者			
    subscriber = queue.createSubscriber(subscription, listeners);

    subscriber.start();

    Thread.sleep(600000L);
​    subscriber.stop("Time out, request to stop subscriber:" + subscriberName);
​} catch (Exception ex) {
    LOG.error("Received unexpected exception when testing subscription.", ex);
} finally {
    if (queue != null) {
        try {
	    queue.close();
	    queue=null;
        } catch (IOException ex) {
            // ignore the exception
        }
    }
}

4.5. ThriftServer API

​HBase自带的ThriftServer实现了对HTable的多语言API支持,HQueue在HBase ThriftServer中扩展了对HQueue的支持,使得C++、Python和PHP等语言也可以方便地访问HQueue。

​HQueue目前提供的Thrift API如下所示:

1 ScannerID messageScannerOpen(1:Text queueName,2:i16 partitionID,3:TMessageScan messageScan) 根据Scan,打开Queue中某个Partition上的Scanner
2 TMessage messageScannerGet(1:ScannerID id) 逐条获取Message
3 list<TMessage> messageScannerGetList(1:ScannerID id,2:i32 nbMessages) 批量获取Messages
4 void messageScannerClose(1:ScannerID id) 关闭ScannerID
5 void putMessage(1:Text queueName,2:TMessage tMessage) 向Queue中写入Message,使用随机的Partition ID
6 void putMessages(1:Text queueName,2:list<TMessage> tMessages) 向Queue中批量写入Messages,使用随机的Partition ID
7 void putMessageWithPid(1:Text queueName,2:i16 partitionID,3:TMessage tMessage) 向Queue中写入Message,使用指定的Partition ID
8 void putMessagesWithPid(1:Text queueName,2:i16 partitionID,3:list<TMessage> tMessages) 向Queue中批量写入Messages,使用指定的Partition ID
9 list<Text> getQueueLocations(1:Text queueName) 获取Queue中所有Partition所在主机的地址

5. 总结

以上是对HQueue概念、特性、系统设计、处理流程以及应用等方面的简单阐述,希望对大家有所帮助。

]]>
http://searchtb.ruoguschool.com/2014/04/hqueue%ef%bc%9a%e5%9f%ba%e4%ba%8ehbase%e7%9a%84%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97.html/feed 0
当cpu飙升时,找出php中可能有问题的代码行 http://searchtb.ruoguschool.com/2014/04/%e5%bd%93cpu%e9%a3%99%e5%8d%87%e6%97%b6%ef%bc%8c%e6%89%be%e5%87%baphp%e4%b8%ad%e5%8f%af%e8%83%bd%e6%9c%89%e9%97%ae%e9%a2%98%e7%9a%84%e4%bb%a3%e7%a0%81%e8%a1%8c.html http://searchtb.ruoguschool.com/2014/04/%e5%bd%93cpu%e9%a3%99%e5%8d%87%e6%97%b6%ef%bc%8c%e6%89%be%e5%87%baphp%e4%b8%ad%e5%8f%af%e8%83%bd%e6%9c%89%e9%97%ae%e9%a2%98%e7%9a%84%e4%bb%a3%e7%a0%81%e8%a1%8c.html#comments Thu, 24 Apr 2014 04:06:11 +0000 沧龙 http://searchtb.ruoguschool.com/?p=4188 当你发现一个平时占用cpu比较少的进程突然间占用cpu接近100%时,你如何找到导致cpu飙升的原因?我的思路是,首先找到进程正在执行的代码行,从而确定可能有问题的代码段。然后,再仔细分析有问题的代码段,从而找出原因。

如果你的程序使用的是c、c++编写,那么你可以很容易的找到正在执行的代码行。但是,程序是php编写的,如何找到可能有问题的代码行呢?这个问题就是本文要解决的问题。

背景知识:

如果你对c语言不熟悉的话,可以略过,直接看 示例演示。

大家都知道php是一个解释性语言。用户编写的php代码会生成opcode,由解释器引擎去解释执行。在解释执行过程中,有一个全局变量包含了执行过程中用到的各种数据。它就是executor_globals。在源码的Zend/zend_globals.h 文件中可以找到他的类型定义。

这里我们只说两个对我们比较重要的变量,active_op_array 和 current_execute_data。
active_op_array变量中保存了引擎正在执行的op_array(想了解什么是op_array请点击查看)。在Zend/zend_compile.h中有关于op_array的数据类型的定义。

看完定义,就不用我多说了把。定义中,filename和 function_name分别保存了正在执行的文件名和方法名。

current_execute_data保存了正在执行的op_array的execute_data。execute_data保存了每个op_array执行过程中的一些数据。其定义在,Zend/zend_compile.h:

定义中的opline就是正在执行的opcode。opcode的结构定义如下:

其中lineno就是opcode所对应的行号。

示例说明:
看完上面的数据结构定义,你是否已经知道如何找php正在执行的文件名,方法名和行号呢?如果还有疑问的话,那就接着看下面的例子。创建一个文件test.php,代码如下:

cli方式执行php脚本,加入执行的进程号为14973。我们使用gdb命令来调试进程。

很显然,他正在执行第四行的sleep方法。

如果上面的方法你感觉麻烦,那你可以使用.gdbinit文件。这个文件在php源码的根目录下。使用方法如下:

题外话:
​从php5.6开始,php中集成了一个phpdbg的工具。可以像gdb调试c语言程序一样,调试php程序。感兴趣的话,可以打开下面的连接看看。

https://wiki.php.net/rfc/phpdbg

http://phpdbg.com/docs…

]]>
当你发现一个平时占用cpu比较少的进程突然间占用cpu接近100%时,你如何找到导致cpu飙升的原因?我的思路是,首先找到进程正在执行的代码行,从而确定可能有问题的代码段。然后,再仔细分析有问题的代码段,从而找出原因。

如果你的程序使用的是c、c++编写,那么你可以很容易的找到正在执行的代码行。但是,程序是php编写的,如何找到可能有问题的代码行呢?这个问题就是本文要解决的问题。

背景知识:

如果你对c语言不熟悉的话,可以略过,直接看 示例演示。

大家都知道php是一个解释性语言。用户编写的php代码会生成opcode,由解释器引擎去解释执行。在解释执行过程中,有一个全局变量包含了执行过程中用到的各种数据。它就是executor_globals。在源码的Zend/zend_globals.h 文件中可以找到他的类型定义。

struct _zend_executor_globals {
	zval **return_value_ptr_ptr;

	zval uninitialized_zval;
	zval *uninitialized_zval_ptr;

	zval error_zval;
	zval *error_zval_ptr;

	zend_ptr_stack arg_types_stack;

	/* symbol table cache */
	HashTable *symtable_cache[SYMTABLE_CACHE_SIZE];
	HashTable **symtable_cache_limit;
	HashTable **symtable_cache_ptr;

	zend_op **opline_ptr;

	HashTable *active_symbol_table;
	HashTable symbol_table;		/* main symbol table */

	HashTable included_files;	/* files already included */

	JMP_BUF *bailout;

	int error_reporting;
	int orig_error_reporting;
	int exit_status;

	zend_op_array *active_op_array;

	HashTable *function_table;	/* function symbol table */
	HashTable *class_table;		/* class table */
	HashTable *zend_constants;	/* constants table */

	zend_class_entry *scope;
	zend_class_entry *called_scope; /* Scope of the calling class */

	zval *This;

	long precision;

	int ticks_count;

	zend_bool in_execution;
	HashTable *in_autoload;
	zend_function *autoload_func;
	zend_bool full_tables_cleanup;

	/* for extended information support */
	zend_bool no_extensions;

#ifdef ZEND_WIN32
	zend_bool timed_out;
	OSVERSIONINFOEX windows_version_info;
#endif

	HashTable regular_list;
	HashTable persistent_list;

	zend_vm_stack argument_stack;

	int user_error_handler_error_reporting;
	zval *user_error_handler;
	zval *user_exception_handler;
	zend_stack user_error_handlers_error_reporting;
	zend_ptr_stack user_error_handlers;
	zend_ptr_stack user_exception_handlers;

	zend_error_handling_t  error_handling;
	zend_class_entry      *exception_class;

	/* timeout support */
	int timeout_seconds;

	int lambda_count;

	HashTable *ini_directives;
	HashTable *modified_ini_directives;

	zend_objects_store objects_store;
	zval *exception, *prev_exception;
	zend_op *opline_before_exception;
	zend_op exception_op[3];

	struct _zend_execute_data *current_execute_data;

	struct _zend_module_entry *current_module;

	zend_property_info std_property_info;

	zend_bool active;

	void *saved_fpu_cw;

	void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};

这里我们只说两个对我们比较重要的变量,active_op_array 和 current_execute_data。
active_op_array变量中保存了引擎正在执行的op_array(想了解什么是op_array请点击查看)。在Zend/zend_compile.h中有关于op_array的数据类型的定义。

struct _zend_op_array {
	/* Common elements */
	zend_uchar type;
	char *function_name;
	zend_class_entry *scope;
	zend_uint fn_flags;
	union _zend_function *prototype;
	zend_uint num_args;
	zend_uint required_num_args;
	zend_arg_info *arg_info;
	zend_bool pass_rest_by_reference;
	unsigned char return_reference;
	/* END of common elements */

	zend_bool done_pass_two;

	zend_uint *refcount;

	zend_op *opcodes;
	zend_uint last, size;

	zend_compiled_variable *vars;
	int last_var, size_var;

	zend_uint T;

	zend_brk_cont_element *brk_cont_array;
	int last_brk_cont;
	int current_brk_cont;

	zend_try_catch_element *try_catch_array;
	int last_try_catch;

	/* static variables support */
	HashTable *static_variables;

	zend_op *start_op;
	int backpatch_count;

	zend_uint this_var;

	char *filename;
	zend_uint line_start;
	zend_uint line_end;
	char *doc_comment;
	zend_uint doc_comment_len;
	zend_uint early_binding; /* the linked list of delayed declarations */

	void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};

看完定义,就不用我多说了把。定义中,filename和 function_name分别保存了正在执行的文件名和方法名。

current_execute_data保存了正在执行的op_array的execute_data。execute_data保存了每个op_array执行过程中的一些数据。其定义在,Zend/zend_compile.h:

struct _zend_execute_data {
    struct _zend_op *opline;
    zend_function_state function_state;
    zend_function *fbc; /* Function Being Called */
    zend_class_entry *called_scope;
    zend_op_array *op_array;
    zval *object;
    union _temp_variable *Ts;
    zval ***CVs;
    HashTable *symbol_table;
    struct _zend_execute_data *prev_execute_data;
    zval *old_error_reporting;
    zend_bool nested;
    zval **original_return_value;
    zend_class_entry *current_scope;
    zend_class_entry *current_called_scope;
    zval *current_this;
    zval *current_object;
    struct _zend_op *call_opline;
};

定义中的opline就是正在执行的opcode。opcode的结构定义如下:

struct _zend_op {
	opcode_handler_t handler;
	znode result;
	znode op1;
	znode op2;
	ulong extended_value;
	uint lineno;
	zend_uchar opcode;
};

其中lineno就是opcode所对应的行号。

示例说明:
看完上面的数据结构定义,你是否已经知道如何找php正在执行的文件名,方法名和行号呢?如果还有疑问的话,那就接着看下面的例子。创建一个文件test.php,代码如下:

<?php
function test1(){
        while(true){
              sleep(1);
        }
}
test1();
?>

cli方式执行php脚本,加入执行的进程号为14973。我们使用gdb命令来调试进程。

$sudo gdb -p 14973
(gdb) print (char *)executor_globals.active_op_array->filename
$1 = 0x9853a34 "/home/xinhailong/test/php/test.php"
(gdb) print (char *)executor_globals.active_op_array->function_name
$2 = 0x9854db8 "test1"
(gdb) print executor_globals->current_execute_data->opline->lineno
$3 = 4

很显然,他正在执行第四行的sleep方法。

如果上面的方法你感觉麻烦,那你可以使用.gdbinit文件。这个文件在php源码的根目录下。使用方法如下:

$sudo gdb -p 14973
(gdb) source /home/xinhailong/.gdbinit
(gdb) zbacktrace
[0xa453f34] sleep(1) /home/xinhailong/test/php/test.php:4
[0xa453ed0] test1() /home/xinhailong/test/php/test.php:7
(gdb)

题外话:
​从php5.6开始,php中集成了一个phpdbg的工具。可以像gdb调试c语言程序一样,调试php程序。感兴趣的话,可以打开下面的连接看看。

https://wiki.php.net/rfc/phpdbg

http://phpdbg.com/docs

]]>
http://searchtb.ruoguschool.com/2014/04/%e5%bd%93cpu%e9%a3%99%e5%8d%87%e6%97%b6%ef%bc%8c%e6%89%be%e5%87%baphp%e4%b8%ad%e5%8f%af%e8%83%bd%e6%9c%89%e9%97%ae%e9%a2%98%e7%9a%84%e4%bb%a3%e7%a0%81%e8%a1%8c.html/feed 0