一篇看懂圖資料庫janusgraph儲存結構

yoylee_web發表於2019-01-22

簡介

JanusGraph是以鄰接列表儲存的,這意味著圖形儲存為鄰接列表的頂點集合。頂點的鄰接列表包含所有點的邊(出邊和入邊,包含邊的屬性)和頂點對應的屬性。 JanusGraph按排序順序維護鄰接列表中的每個頂點,其順序由排序鍵和邊標籤的排序順序定義。排序順序允許使用以頂點為中心的索引有效地檢索鄰接列表的子集。 JanusGraph圖形的鄰接列表可以儲存在支援Bigtable資料模型的任何儲存後端中。 對於圖形的儲存,對於圖的拆分格式有兩種方式:按點切割、按邊切割。

  • 按點切割:根據點進行切割,每個邊只儲存一次
  • 按邊切割:根據邊進行切割,以節點為中心,邊會儲存兩次,JanusGraph就是採用的按邊切割,按邊切割後以鄰接列表的形式儲存圖形。

通過以鄰接列表格式儲存圖形,JanusGraph確保所有頂點的邊和屬性緊密地儲存在儲存後端中(比如Hbase\Cassandra等),從而可以加速遍歷。缺點是每個邊必須儲存兩次一次作為source頂點的出邊被儲存,一次作為target頂點入邊被儲存,當然這也使我們可以在source Vertex和target Vertex中任意頂點都可以快速找到對端。

Bigtable資料模型:

來自官網的示意圖:

在這裡插入圖片描述
在Bigtable資料模型中,每個表是行的集合,由一個key唯一標識。每行由任意(可以很大數量但是必須有限數量)數量的cell組成。cell由column和value組成。cell由給定行內的column唯一標識。 cell(單元格)、column(列)、value(列值) Bigtable模型中的行稱為“寬行”,因為它們支援大量cell,並且不必像關聯式資料庫中那樣預先定義這些cell的column。在關係型資料庫中我們必須先定義好表的schema,才可以儲存資料,如果儲存過程中想要改變表結構,則所有的資料都要對變化的列做出變化。但是Bigtable模型儲存中就不必如此,每個行的column不同,我們可以隨時僅對某一行進行變化,也不許預先定義行的schema,只需要定義圖的schema即可。 JanusGraph對Bigtable資料模型有一個額外要求:儲存邊的單元格必須按column排序,並且列範圍指定的單元格子集必須是有效可檢索的(例如,通過使用索引結構,跳過列表或進行二進位制搜尋)。 此外,特定的Bigtable實現可以使行按其鍵的順序排序。JanusGraph可以利用這樣的鍵序來有效地劃分圖形,從而為非常大的圖形提供更好的載入和遍歷效能。但是,這並不是必需的。

JanusGraph的儲存:

來自官網的示意圖:

在這裡插入圖片描述
前面說過,JanusGraph使用Bigtable模型進行儲存資料,如果使用的儲存後臺支援鍵順序(如Hbase),則鄰接列表將按Vertex Id排序進行順序儲存的。 在JanusGraph儲存資料的表中,行的唯一key可以是任意字串(目前最大為64KB,儘管使用者大多數只使用10-100位元組,下面介紹了vertex id的組成)。每次在一行中讀或寫資料都是一個原子操作(儘管一行中不同列可能正在進行讀或寫),這個設計使客戶端可以更加方便的推匯出在併發更新相同行時的系統行為。 在JanusGraph中,是點為中心,按切邊的方式儲存資料的。比如在Hbase中節點的ID作為HBase的Rowkey,節點上的每一個屬性和每一條邊,作為該Rowkey的一個個獨立的Cell。即每一個屬性、每一條邊,都是一個個獨立的KCV結構(Key-Column-Value)。

1:具體案例

比如我們下面有一個圖,

在這裡插入圖片描述
拆分定義後為:
在這裡插入圖片描述
然後其儲存格式就基本就可以確定了。

2:vertex id的組成:

  1. Vertex ID以Rowkey的形式儲存在HBase中,Vertex ID共包含64個bit。
  2. Vertex ID由partition id、count、ID padding三部分組成。 2. Vertex ID由partition id、count、ID padding三部分組成。
  3. 最高位5個bit是partition id。partition是JanusGraph抽象出的一個概念。當Storage Backend是HBase時,JanusGraph會根據partition數量,自動計算並配置各個HBase Region的split key,從而將各個partition均勻對映到HBase的多個Region中。然後通過均勻分配partition id最終實現資料均勻打散到Storage Backend的多臺機器中。
  4. 中間的count部分是流水號,其中最高位位元固定為0.
  5. 最後幾個bit是ID padding, 表示Vertex的型別。具體的位數長度根據不同的Vertex型別而不同。最常用的普通Vertex,其值為'000'。

其組成圖為(騰訊雲社群):

在這裡插入圖片描述

3:邊和屬性在cell中具體的儲存形式

下面來自官網的示意圖:

在這裡插入圖片描述

深藍色框表示使用可變長度編碼方案編碼的數字,以減少它們消耗的位元組數。紅框表示使用關聯屬性鍵中引用壓縮後設資料序列化的一個或多個屬性值(即物件)。灰色框表示未壓縮的屬性值(即序列化物件)

上圖中我們可以看出,Edge和Property在cell中都是由column(列)和value(值)組成。 Edge中column由labelid(邊標籤id)+direction(邊的方向,相對於節點的出邊或者入邊)+sort key(用於邊排序的key)+adjacent vertex id(臨近頂點的id)+edge id(邊id)組成,value由signature key(簽名密匙)+other properties(邊的其他屬性)組成。

注意:此處的組成元素SortKey是一種特殊的屬性,JanusGraph允許在定義Edge Label時指定其中的一個或多個屬性為Sort Key,主要的作用是:將資料序列化進行儲存時,序列化中edge會根據你設定的sort key進行排序,比如A有多個朋友關係的edge,每一個edge都有一個建立時間(createtime),sort key便可以在儲存時將邊按照建立時間進行順序儲存,這樣便於查詢某個createtime的邊,也便於範圍查詢。

此處的signature key觀看原始碼其實就是一個list陣列, 裡面儲存的是邊的property的key id(注意不是property id),作用是:邊的other properties是被序列化儲存在磁碟中,當我們查詢 邊是否包含某一屬性時不可能將其序列化回來再進行查詢,這時候signature key的作用就體現出來了,通過其就可以知道這條邊有什麼屬性,就可以更快的進行查詢。現在再看‘紅框表示使用關聯屬性鍵中引用壓縮後設資料序列化的一個或多個屬性值’這句話,說的就是我引用屬性的key id 。 signature key有一種空間的換時間的感覺。。

Property中column由key id(屬性的鍵id)組成,value由屬性id+屬性值組成。

這裡注意key id 和 property id,key id 是屬性key的id,舉個栗子:name:李陽,這裡的key id就是name這個property key的id,而name:李陽整體有一個id就是property id 了

4:其中對於property的儲存:

一個Property Key所關聯的屬性值有可能有一個,也有可能有多個,因此,JanusGraph使用Cardinality來描述Property Key的這種特點。有SINGLE,LIST和SET三種型別, 原始碼中的PropertyKey介面可以看到:

public interface PropertyKey extends RelationType {
    Class<?> dataType();
    Cardinality cardinality();
}
複製程式碼
  • SINGLE表示一個Property Key只對應一個Value,這是最常用的場景。

Cardinality為SINGLE時的儲存結構,HBase的列名只儲存Property Key的ID。具體的Property Value值以及Property ID(JanusGraph為每一個Property分配的唯一ID),都存放在Cell的Value中。另外,如果該Property還有額外的Remaining properties,也會放在Value中。Remaining properties一般不使用,僅在一些特殊場景下,用於為該Property記錄更多的附加資訊(比如儲存後設資料Edge Labe的定義等)。

  • LIST表示一個Property Key可以對應多個Value,多個Value可以有重複值。

Candinality為LIST時的儲存結構,各個部分與Cardinality為SINGLE時的結構相似,區別在於屬性的ID被放在了列名中,而不是放在Value中。

  • SET表示一個Property Key可以對應多個Value,多個Value不可以有重複值。

Candinality為SET儲存結構,各個部分與Cardinality為SINGLE時的結構相似,區別在於屬性的值被放在了列名中,而不是放在Value中,前提是去除了重複。

JanusGraph的Property,在不同的Cardinality下,資料儲存結構略有不同。整體原則是:根據Cardinality的不同,列名本身能夠確定唯一的一個屬性即可。

5:Edge label 的多樣性

預設的多重性是MULTI

  • MULTI:允許任意一對頂點之間的同一標籤具有多個邊。
  • SIMPLE:在任何一對頂點之間最多允許擁有此類標籤的一個邊。
  • MANY2ONE:在圖形中的任何頂點上最多允許此標籤的一個傳出邊,但不對傳入邊施加約束。邊標籤mother是MANY2ONE多樣性的一個例子,因為每個人最多隻有一個母親,但母親可以有多個孩子。
  • ONE2MANY:在圖形中的任何頂點上最多允許此標籤的一個傳入邊,但不對傳出邊施加約束。邊標籤winnerOf是ONE2MANY多樣性的一個例子,因為每個比賽最多隻贏一個人,但一個人可以贏得多個比賽。
  • ONE2ONE:在圖表的任何頂點上最多允許此標籤的一個傳入邊和一個傳出邊。邊標籤結婚是ONE2ONE多樣性的一個例子,因為一個人與另一個人結婚。

Property Key和Edge Label被抽象成了Relation Type,並採用相同的資料結構。所以說 Property Key和Edge Label不能被設定為相同的名字。

6:序列化:

每個邊和屬性都作為一個cell儲存在其相鄰頂點的行中。它們會被序列化並且column的位元組順序會遵循edge 的column中的sort key進行儲存。變數id編碼方案和壓縮物件序列化使每個edge/cell的儲存所佔空間都儘可能小。 邊的序列化從邊標籤的唯一ID開始(由JanusGraph指定)。這通常是一個小數字,並且使用變數id編碼進行壓縮。該id的最後一位是偏移量,用於儲存這個邊是入邊(in)還是出邊(out)。接下來,儲存包括sort key 的屬性值,它是使用edge label定義的,因此排序鍵物件後設資料可以引用邊緣標籤。之後,儲存相鄰頂點的id。JanusGraph不儲存實際的頂點id,而是儲存擁有此鄰接列表的頂點的id。頂點id後跟此邊的id,JanusGraph為每條邊分配了一個唯一的id。這就是edge cell的column。edge的cell的column包含邊的壓縮序列化後的簽名屬性(由標籤的簽名鍵定義)以及未壓縮序列化的邊的任何其他屬性。 屬性的序列化表示更簡單,column中只包含屬性的鍵ID。屬性id和屬性值儲存在value中。但是,如果將屬性鍵定義為list()或者set(),則property id也儲存在column中(上面property儲存中說到過)。

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可以顯式或隱式建立,推薦使用者採用顯式定義的方式。JanusGraph的schema是可以在使用過程中修改的,而且不會導致服務當機,也不會拖慢查詢速度。,比如一個簡單的顯示定義的銷售圖的scheme:

<propertyKey value="salesman_id" explain="銷售人員id" index="true" type="java.lang.String" />
<propertyKey value="real_name" explain="姓名" index="" type="java.lang.String" />
<propertyKey value="role" explain="角色" type="" />
<propertyKey value="city_code" explain="所處城市程式碼" index="" type="" />
<propertyKey value="create_time" explain="建立時間" index="" type="" />

<edgeLabel value="saleman_service_for" explain="銷售引導">
    <propertys>
        <property value="create_time"/>
    </propertys>
</edgeLabel>
<edgeLabel value="own_salaman_Idcard" explain="銷售身份">
    <propertys>
        <property value="create_time"/>
    </propertys>
</edgeLabel>

<index elementType="vertex" indexType="compositeIndex" name="salesman_id_I"  >
    <propertyKeys>
        <propertyKey value="salesman_id" />
    </propertyKeys>
</index>

<vertexLabel value="salesman" explain="銷售"  >
    <propertys>
        <property value="salesman_id"  />
        <property value="real_name" />
        <property value="role"  />
        <property value="city_code"  />
    </propertys>
    <edges>
        <edge value="saleman_service_for" direction="out" />
        <edge value="own_salaman_Idcard" direction="out" />
    </edges>
</vertexLabel>
複製程式碼

當然,我們也可以新增一些其他的可以組成schema的元素,上述三個是必須的,另外的比如索引(index)等,主要的結構還是:

JanusGraph Schema
            |-----------Vertex Lables
            |-----------Property Keys
            |-----------Edge Labels
複製程式碼

和通關係型資料庫不同,圖資料的schema是定義一張圖,而非定義一個vertex的。在Mysql中,我們通常將建立一張表定義為建立一個schema,而在JanusGraph中,一個Graph用於一個schema。

refer:部落格 部落格

如果轉載此博文,請附上本文連結,謝謝合作~ :juejin.im/user/5c3036…

如果感覺這篇文章對您有所幫助,請點選一下“喜歡”或者“關注”博主,您的喜歡和關注將是我前進的最大動力!

相關文章