java对象底层原存储结构图解_图解图库JanusGraph系列-一文知晓“图数据“底层存储结构...
大家好,我是洋仔,JanusGraph圖解系列文章,實時更新~
圖數(shù)據(jù)庫文章總目錄:
轉(zhuǎn)載文章請保留以下聲明:
一:存儲模式
留言或私信我,邀請你加入“圖數(shù)據(jù)庫交流”微信群!
1、圖內(nèi)容
本文以下所有內(nèi)容基于:JanusGraph基于屬性圖來進行構(gòu)造圖數(shù)據(jù):
屬性圖: 屬性圖是由 頂點(Vertex),邊(Edge),屬性(Property)組成的有向圖
Vertex可以包含Properties;Edge也可以包含Properties;
2、存儲方法
圖存儲的方式常用的有兩種:鄰接列表 和 鄰接矩陣
JanusGraph采用鄰接列表進行圖數(shù)據(jù)的存儲,如下圖所示:(此處將圖中節(jié)點抽象為 只有節(jié)點,沒有屬性)
在Janusgraph中一個頂點的鄰接列表包含該節(jié)點對應的屬性和關聯(lián)的邊,下述會詳細說明 Janusgraph中鄰接列表是如何實現(xiàn)的;
3、圖切割方式
圖的切割方式分為兩種:按節(jié)點切割(Vertex Cut)和按邊切割(Edge Cut)Vertex Cut:根據(jù)點進行切割,每個邊只存儲一次,只要是節(jié)點對應的邊便會多一份該節(jié)點的存儲
Edge Cut:根據(jù)邊進行切割,以節(jié)點為中心,邊會存儲兩次,源節(jié)點的鄰接列表存儲一次,目標節(jié)點的鄰接列表存儲一次
在Janusgraph中既存在Edge Cut,也存在Vertex Cut的情況;
在默認的情況下使用邊切割,而針對熱點節(jié)點可以通過配置makeVertexLabel('product').partition()來將節(jié)點類型為product類型的節(jié)點進行Vertex Cut;
也就是說,在沒有上述makeVertexLabel('product').partition()配置的話,JanusGraph所有的圖數(shù)據(jù)都是以Edge Cut的方式來進行切割存儲的;
具體可以查看文章:《JanusGraph-分區(qū)》中自定義分區(qū)部分中關于圖切割部分的介紹;
我們例子來說明一下:
如下圖: 張三用戶節(jié)點通過手機號關聯(lián)出來李四用戶節(jié)點張三 和 李四 代表Vertex;指向的name、age、gender代表張三的屬性
edgeA 和edgeB 代表Edge;也可以包含邊的屬性,例如下圖中邊包含屬性create_time
按邊切割后:節(jié)點張三name(property)age(property)gender(property)edgeA(edge)
phonephone(property)edgeA(edge)edgeB(edge)
李四name(property)age(property)gender(property)edgeB(edge)
上述可以看到,按照邊切割后每一條邊會存儲兩次!
二:BigTable模型在JanusGraph的存儲中, JanusGraph將圖形的鄰接列表的表示存儲在支持Bigtable數(shù)據(jù)模型的任何存儲后端中
BigTable模型如下圖:
在Bigtable數(shù)據(jù)模型中,每個表是行的集合,由一個key唯一標識。
每行由任意(可以很大數(shù)量但是必須有限數(shù)量)數(shù)量的cell組成;cell由column和value組成,column唯一標識某一個cell。
上述圖中,有兩部分需要排序的支持:sorted by key 和 sorted by column:sorted by key:標識存儲后端存儲的數(shù)據(jù)時按照key的大小進行排序存儲的
sorted by column:這是JanusGraph對Bigtable數(shù)據(jù)模型有一個額外要求,存儲edge(邊)的單元格必須按column排序,并且列范圍指定的單元格子集必須是有效可檢索的; 這句話詳細解答在下述文章中有體現(xiàn)
在Bigtable模型中的行稱為“寬行”,因為它們支持大量cell,并且不必像關系數(shù)據(jù)庫中那樣預先定義這些cell的column。
在關系型數(shù)據(jù)庫中我們必須先定義好表的schema,才可以存儲數(shù)據(jù),如果存儲過程中想要改變表結(jié)構(gòu),則所有的數(shù)據(jù)都要對變化的列做出變化。但是Bigtable模型存儲中就不必如此,每個行的column不同,我們可以隨時僅對某一行進行變化,也不許預先定義行的schema,只需要定義圖的schema即可。
此外,特定的Bigtable實現(xiàn)可以使行按其鍵的順序排序。JanusGraph可以利用這樣的鍵序來有效地劃分圖形,從而為非常大的圖形提供更好的加載和遍歷性能。
JanusGraph是如何基于BigTable數(shù)據(jù)模型針對于自身的圖數(shù)據(jù)特性進行設計的呢?
下面我們看下JanusGraph的邏輯存儲結(jié)構(gòu)
三:存儲邏輯結(jié)構(gòu)JanusGraph基于使用BigTable模型的存儲后端 實現(xiàn)了自己的存儲的邏輯結(jié)構(gòu)
ps:為了更好的理解,下面部分知識點會基于HBase存儲后端進行進一步的解釋!
1、整體結(jié)構(gòu)
在JanusGraph中,以節(jié)點為中心,按切邊的方式存儲數(shù)據(jù)的。比如在Hbase中節(jié)點的ID作為HBase的Rowkey,節(jié)點上的每一個屬性和每一條邊,作為該Rowkey行的一個個獨立的Cell。即每一個屬性、每一條邊,都是一個個獨立的KCV結(jié)構(gòu)(Key-Column-Value)
上圖中,我們可以發(fā)現(xiàn)圖的存儲整體分為三部分:vertex id、property、edge:vertex id: 對應節(jié)點的唯一id,如果底層存儲使用的是Hbase則代表著當前行的Rowkey,唯一代表某一個節(jié)點
property: 代表節(jié)點的屬性
edge: 代表節(jié)點的對應的邊
排序方式分為三種:sorted by id、sorted by type、sorted by sort key:sorted by id: 依據(jù)vertex id在存儲后端進行順序存儲
sorted by type:此處的個人理解為針對于property 和 edge的類型進行排序,保證同種類型的屬性或者邊連續(xù)存儲在一塊便于遍歷查找; // TODO? 深層次理解?
sorted by sort key: sort key是邊組成以的一部分,主要作用是,在同種類型的edge下,針對于sort key進行排序存儲,提升針對于指定sort key的檢索速度;下面edge結(jié)構(gòu)部分有詳細介紹
2、Vertex id 的結(jié)構(gòu)
此處的Vertex id唯一標識圖中的某一個節(jié)點;節(jié)點vertex id的組成結(jié)構(gòu)我們在源碼類IDManager的一段注釋中可以發(fā)現(xiàn):/* --- JanusGraphElement id bit format ---
* [ 0 | count | partition | ID padding (if any) ]
*/
這是在Janusgraph在生成所有的id時統(tǒng)一的格式包含vertex id\edge id\property id的時候,這個順序也 就是標識我們再使用gremlin查詢出節(jié)點時,節(jié)點上標識的vertex id; 這個id值的順序不同于hbase真實存儲Rowkey的順序!!!!!!!
在對vertex id進行序列化存儲時,位置有所調(diào)整為:[ partition | 0 | count | ID padding (if any) ] 如下圖:
從圖中可以看出:Vertex ID共包含一個字節(jié)、8位、64個bit
Vertex ID由partition id、count、ID padding三部分組成
最高位5個bit是partition id。partition是JanusGraph抽象出的一個概念。當Storage Backend是HBase時,JanusGraph會根據(jù)partition數(shù)量,自動計算并配置各個HBase Region的split key,從而將各個partition均勻映射到HBase的多個Region中。然后通過均勻分配partition id最終實現(xiàn)數(shù)據(jù)均勻打散到Storage Backend的多臺機器中
中間的count部分是流水號,其中最高位比特固定為0;出去最高位默認的0,count的最大值為2的(64-5-1-3)=55次冪大小:3 6028 7970 1896 3968,總共可以生成30000兆個id,完全滿足節(jié)點的生成
最后幾個bit是ID padding, 表示Vertex的類型。具體的位數(shù)長度根據(jù)不同的Vertex類型而不同。最常用的普通Vertex,其值為'000'
為什么在序列化存儲vertex id時,需要調(diào)整順序序列化作為RowKey存儲到Hbase呢?
我們通過下面的3個問題來回答:為什么JausGraph分配的邏輯區(qū)間值,可以影響hbase物理存儲呢? 可以將分區(qū)相同的數(shù)據(jù)存放的更近呢?在上述描述中,hbase使用vertex id作為rowkey,hbase根據(jù)rowkey順序排序存儲; 每個hbase region存儲是一段連續(xù)的Rowkey行;
在janusgraph的vertex id的設計中,可以發(fā)現(xiàn)將分區(qū)值放到了64位的前5位存儲! 在存儲數(shù)據(jù)到hbase時,對rowkey進行排序,因為partition id在前5位,所以同一個分區(qū)的vertex id對應的rowkey值相差較小,所以會存儲在一塊;如何快速的查詢到不同類型的節(jié)點呢? 換個說法如何快速的確定當前的行就是我們需要的節(jié)點類型的行呢?在JanusGraph的vertex id中包含的 ID padding就代表當前的節(jié)點類型(注意此處的類型!=lable)。000標識為普通節(jié)點,在id的組成部分中,我們經(jīng)過前面的分析,最前面是partition id,只有把 ID padding放在最后幾個字節(jié)便于查找了;為什么查詢出的節(jié)點顯示的vertex id要把0|count放在最前面、partiton和id padding放在后面呢?這里我們猜測一下:count占用55位數(shù)據(jù)! 試想如果把count不放在最前面,那么id的最小值比2的55次冪還大,顯示不友好! 如果把0|count放在最前面呢?就會有兩個效果:
0在有符號表示中標識當前id始終為正整數(shù)!
count是趨勢遞增的,所以id值也是從小到大趨勢遞增的,所以節(jié)點id的最小值在2的8次冪周邊大小; 比把count放在后面顯示的id值友好多了~~~
vertex id是如何保證全局唯一性的呢?
主要是基于數(shù)據(jù)庫 + 號段模式進行分布式id的生成;
體現(xiàn)在圖中就是partition id + count 來保證分布式全局唯一性; 針對不同的partition都有自己的0-2的55次冪的范圍的id; 每次要生成vertex id時,首先獲取一個partition,獲取對應partition對應的一組還未使用的id,用來做count;
janusgraph在底層存儲中存儲了對應的partition使用了多少id,從而保證了再生成新的分布式vertex id時,不會重復生成!
ps : JanusGraph中分布式唯一vertex id、edge id、property id的生成分析,請看《圖解JanusGraph系列-分布式唯一id的生成機制》
3、edge 和 property的結(jié)構(gòu)
在上述的JanusGraph的整體結(jié)構(gòu)中,property和edge都是作為cell存儲在底層存儲中;其中cell又分為column和value兩部分,下圖展示了這兩部分的邏輯結(jié)構(gòu):
下面我們詳細分析一下 property 和 edge對應的邏輯結(jié)構(gòu);
3.1 edge的結(jié)構(gòu)
Edge的Column組成部分:label id:邊類型代表的id,在創(chuàng)建圖schema的時候janusgraph自動生成的label id,不同于邊生成的唯一全局id
direction:圖的方向,out:0、in:1
sort key:可以指定邊的屬性為sort key,可多個;在同種類型的edge下,針對于sort key進行排序存儲,提升針對于指定sort key的檢索速度;該key中使用的關系類型必須是屬性非唯一鍵或非唯一單向邊標簽;
存儲的為配置屬性的value值,可多個(只存property value是因為,已經(jīng)在schema的配置中保存有當前Sort key對應的屬性key了,所以沒有必要再存一份)
adjacent vertex id:target節(jié)點的節(jié)點id,其實存儲的是目標節(jié)點id和源節(jié)點id的差值,這也可以減少存儲空間的使用
edge id:邊的全局唯一id
Edge的value組成部分:signature key:邊的簽名key該key中使用的關系類型必須是屬性非唯一鍵或非唯一單向邊標簽;
存儲壓縮后的配置屬性的value值,可多個(只存property value是因為,已經(jīng)在schema的配置中保存有當前signature key對應的屬性key了,所以沒有必要再存一份)
主要作用提升edge的屬性的檢索速度,將常用檢索的屬性設置為signature key,提升查找速度
other properties:邊的其他屬性注意! 不包含配置的sort key和signature key屬性值,因為他們已經(jīng)在對應的位置存儲過了,不需要多次存儲!
此處的屬性,要插入屬性key label id和屬性value來標識是什么屬性,屬性值是什么;
此處的property的序列化結(jié)構(gòu)不同于下述所說的vertex節(jié)點的property結(jié)構(gòu),edge中other properties這部分存儲的屬性只包含:proeprty key label id + property value;不包含property全局唯一id!
詳細解釋及思考:
在進行詳細分析前,請大家思考幾個問題,如下:基于上述的edge邏輯結(jié)構(gòu),JanusGraph是如何構(gòu)造鄰接列表的 或者 是如何獲取源節(jié)點的鄰接節(jié)點的?
上述的Edge邏輯結(jié)構(gòu)中的,每部分的排列的順序的含義是什么?
1、基于上述的edge邏輯結(jié)構(gòu),JanusGraph是如何構(gòu)造鄰接列表的 或者 是如何獲取源節(jié)點的鄰接節(jié)點的?
從上述的整體結(jié)構(gòu)部分中,我們可以知道,vertexId行后跟著當前的節(jié)點關聯(lián)的所有的edge;
而在上述的edge的邏輯結(jié)構(gòu)中,有一個adjacent vertex id字段,通過這個字段就可以獲取到target節(jié)點的vertex id,就相當于指向了target節(jié)點,整理一下:
如上圖,通過上述的條件,就可以構(gòu)造一個VertexA指向VertexB 和 VertexC的鄰接鏈表;
其實,JanusGraph可以理解為構(gòu)造的是雙向鄰接列表, 依據(jù)上圖,我們知道vertexA 和 vertexB 和 vertexC存在邊關系; 當我們構(gòu)造vertexB的鄰接列表時,會包含指向vertexA的節(jié)點,只是說在edge對應的邏輯結(jié)構(gòu)中邊的方向不同而已:
總結(jié):JanusGraph通過vertex id行中包含所有關聯(lián)的edge,edge邏輯結(jié)構(gòu)中包含指向target節(jié)點的數(shù)據(jù)來組成雙向鄰接列表的結(jié)構(gòu);
2、上述的Edge邏輯結(jié)構(gòu)中的,每部分的排列的順序的含義是什么?
首先,在查詢的時候為了提升查詢速度,我們首先要過濾的是什么,針對于edge毋庸置疑是邊的類型和邊的方向;
所以,為了我們可以更快的拿到類型和方向,所以在edge的存儲結(jié)構(gòu)中,我們發(fā)現(xiàn)作者將類型和方向存放在了column中,并且是column的最前面部分;這樣我們可以直接通過判斷column的第一部分字節(jié)就可以對邊類型和方向進行過濾!ps:雖然我們在寫Gremlin語句的時候,可能是語句寫的是先過濾邊的屬性或者其他,但是JanusGraph會針對我們的gremlin語句進行優(yōu)化為先過濾邊類型和方向
接下來,我們可能對邊的屬性進行過濾,我們怎樣提升經(jīng)常要過濾的屬性的查詢速度呢? 我們將經(jīng)常用于范圍查詢的屬性配置為sort key,然后就可以在過濾完邊類型和方向后快速的進行屬性的范圍過濾(此處快速的指過濾配置為sort key的屬性);
3.2 property的結(jié)構(gòu)
property的存儲結(jié)構(gòu)十分的簡單,只包含key id、property id和value三部分:key id:屬性label對應的id,有創(chuàng)建schema時JanusGraph創(chuàng)建; 不同于屬性的唯一id
property id:屬性的唯一id,唯一代表某一個屬性
value:屬性值
注意:屬性的類型包含SINGLE、LIST和SET三種Cardinality;當屬性被設置為LIST類型時,因為LIST允許當前的節(jié)點存在多個相同的屬性kv對,僅通過key id也就是屬性的label id是無法將相同的屬性label區(qū)分出來的
所以在這種情況下,JanusGraph的property的存儲結(jié)構(gòu)有所變化, property id也將會被存儲在column中,如下圖:
四:index存儲結(jié)構(gòu)
1、Composite Index-vertex index結(jié)構(gòu)
圖一(唯一索引Composite Index結(jié)構(gòu)圖):
圖二(非唯一索引Composite Index結(jié)構(gòu)圖):
Rowkey由index label id 和properties value兩大部分組成:index label id:標識當前索引類型
properties value:索引中包含屬性的所有屬性值,可多個; 存在壓縮存儲,如果超過16000個字節(jié),則使用GZIP對property value進行壓縮存儲!
Column由第一個字節(jié)0 和 vertex id組成:第一個字節(jié)0:無論是唯一索引,還是非唯一索引此部分都會存在;如圖一
vertex id:非唯一索引才會在column中存在,用于分別多個相同索引值對應的不同節(jié)點;如圖二
value由vertex id組成:vertex id:針對于rowkey + column查詢到的value是vertex id,然后通過vertex id查詢對應的節(jié)點
2、Composite Index-edge index結(jié)構(gòu)
圖一(唯一索引Composite Index結(jié)構(gòu)圖):
圖二(非唯一索引Composite Index結(jié)構(gòu)圖):
Rowkey同Vertex index部分
Column由第一個字節(jié)0 和 edge id組成:第一個字節(jié)0:無論是唯一索引,還是非唯一索引此部分都會存在;如圖一
edge id:非唯一索引才會在column中存在,用于分別多個相同索引值對應的不同節(jié)點;如圖二
value由以下4部分組成:edge id:邊id
out vertex id:邊對應的出邊id
type id:edge 的label type id
in vertex id:邊對應的入邊id
2、Mixed Index結(jié)構(gòu)
這里以ES作為第三方索引庫為例,這里只介紹普通的范圍查找的mixed index的構(gòu)造:
ES的概念為:index 包含多個 type;每個type包含多個document id,每個document id包含多個field name 和對應的field value;
在Jausgraph中index:包含兩種,janusgraph_edge 和 janusgraph_vertex兩種
type:可自定義
document id:edge id或者 vertex id
field name:索引對應屬性的label string
field value:屬性對應的property value
基于倒排索引的查詢順序為,給定過一個property label 和 property value查詢對應的Vertex id 或者 edge id,則查詢滿足要求的field name 和 field value,就可以獲取到對應的document id即Vertex id 或者 edge id;
五:序列化數(shù)據(jù)案例
以序列化實例來看下上述所說的整體結(jié)構(gòu)
測試節(jié)點數(shù)據(jù):{
"label":"user",
"propertyMap":{
"create_time":"2016-12-09 02:29:26",
"user_name":"張三",
"user_id":"test110"
},
"vertexId":4152
}
測試邊數(shù)據(jù):{
"edgeId":17514510,
"label":"user_login_phone_number",
"propertyMap":{
"productid":"2"
},
"sourceId":4152,
"targetId":40964120
}
跟蹤Janusgraph源碼,獲取其序列化信息,后端存儲使用Hbase:
節(jié)點序列化后數(shù)據(jù)(不包含索引):
邊序列化后數(shù)據(jù)(不包含索引):
節(jié)點的vertex id序列化后的數(shù)據(jù)為56 0 0 0 0 0 0 -128;一個節(jié)點對應的屬性和邊的Rowkey相同,依據(jù)qualifier也就是column來進行區(qū)分;
在邊的序列化結(jié)果中,包含兩部分:一部分是節(jié)點4152的kcv,一個是節(jié)點40964120的kcv;這地方也可以說明JanusGraph是采用的雙向鄰接鏈表進行圖存儲的
五:Schema的使用
從上述來看,我們可以知道,JanusGraph圖的schema該怎樣定義主要是由edge labels 、property keys 和vertex labels 組成(Each JanusGraph graph has a schema comprised of the edge labels, property keys, and vertex labels used therein)
JanusGraph的schema可以顯式或隱式創(chuàng)建,推薦用戶采用顯式定義的方式。JanusGraph的schema是可以在使用過程中修改的,而且不會導致服務宕機,也不會拖慢查詢速度。
比如一個簡單的顯示定義的銷售圖的scheme:
當然,我們也可以添加一些其他的可以組成schema的元素,上述三個是必須的,另外的比如索引(index)等,主要的結(jié)構(gòu)還是:JanusGraph Schema
|-----------Vertex Lables
|-----------Property Keys
|-----------Edge Labels
和關系型數(shù)據(jù)庫不同,圖數(shù)據(jù)的schema是定義一張圖,而非定義一個vertex的。在Mysql中,我們通常將建立一張表定義為創(chuàng)建一個schema,而在JanusGraph中,一個Graph用于一個schema。
六:源碼分析
七:總結(jié)JanusGraph采用Edge cut的方式進行圖切割,并且按照雙向鄰接列表的形式進行圖存儲
JanusGraph每個節(jié)點都是對應的kcv結(jié)構(gòu); vertex id唯一標識節(jié)點;對應的行cell存儲節(jié)點屬性和對應的邊
節(jié)點id的分布式唯一性采用數(shù)據(jù)庫+號段模式進行生成;
總結(jié)
以上是生活随笔為你收集整理的java对象底层原存储结构图解_图解图库JanusGraph系列-一文知晓“图数据“底层存储结构...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux QT 结束当前进程_Linu
- 下一篇: java boolean 多线程_JAV