圖解JanusGraph內部資料儲存結構
本文以圖解的形式詳細介紹了圖資料庫JanusGraph的內部資料儲存結構,包括Vertex/Property/Edge等資訊如何儲存於HBase資料表中,以及各自的資料格式定義。
在前面的文章中,我們介紹了圖資料庫的基本概念,並對Titan做了簡單的介紹。開源Titan專案已經停止更新,JanusGraph是Titan專案的演進產品。到目前為止,JanusGraph與Titan在核心機制上相差不大。JanusGraph/Titan有如下關鍵設計:
-
支援大規模圖資料儲存,Titan圖資料庫是建立在分散式叢集上,資料儲存容量和叢集節點數量成正比;
-
支援彈性和線性擴充套件,高可用,高容錯;
-
支援Gremlin圖查詢語言;
-
支援利用Hadoop計算框架對圖資料進行分析;
-
支援外部索引:ElasticSearch、Solr、Lucene;
-
支援多儲存引擎:Cassandra、HBase、Berkeley DB和InMemory模式;
-
基於Apache License 2.0;
本文內容以HBase作為Storage Backend,詳細介紹JanusGraph的內部資料儲存結構。
JanusGraph基本概念
在介紹關係資料的資料儲存結構之前,我們先來看一下JanusGraph的基本概念。
如同大多數圖資料庫一樣,JanusGraph採用屬性圖進行建模。基於屬性圖的模型,JanusGraph有如下概念:
-
Vertex Label:節點的型別,用於表示現實世界中的實體型別,比如”人”,“車”。在JanusGraph中,每一個節點有且只有一個Vertex Label。當不顯式指定Vertex Label時,採用預設的Vertex Label。
-
Vertex:節點/頂點,用於表示現實世界中的實體物件。
-
Edge Label:邊的型別,用於表示現實世界中的關係型別,比如“通話關係”,“轉賬關係”,“微博關注關係”等;
-
Edge: 邊,用於表示一個個具體的聯絡。JanusGraph的邊都是單向邊。如果需要雙向邊,則通過兩條相反方向的單向邊組成。JanusGraph不存在無向邊。
-
Property Key
-
Property:屬性,用於表示一個個具體的附加資訊,採用Key-Value結構。Key就是Property Key,Value就是具體的值。
屬性圖舉例:
張三與李四是同事關係,他們從2017年開始成為同事,用屬性圖表達:
圖切割
作為一種分散式圖資料庫,JanusGraph需要將資料切分儲存到多臺機器上。
典型的圖切割方法有兩種: 一種是按點切割,另一種是按邊切割。
-
按點切割(Vertex-Cut)
按Vertex切割時,切割線通過圖的Vertex,而不是Edge。每一條Edge邊只儲存一次,並且每一條Edge只出現在一臺機器上,鄰居多的Vertex會被分發到不同的機器上。
-
按邊切割(Edge-Cut)
按Edge切割時,切割線只穿過連線vertex的edge,此時,每一個vertex只儲存一次。切斷的Edge會儲存到多臺機器上。
JanusGraph採用的分片方式是按Edge切割,而且是對於每一條邊,都會被切斷。切斷後,該邊會在起始Vertex上和目的Vertex上各儲存一次。通過這種方式,JanusGraph不管是通過起始Vertex,還是從目的Vertex,都能快速找到對端Vertex。
備註:
對於Super Node的儲存模式不同,這裡暫不展開討論。
JanusGraph允許將Edge Label定義為單向感知的edge. 對於單向感知的edge,只會從起始vertex查詢此edge,不會從目的vertex反向查詢。因此,這種情況下,目的節點不儲存該vertex,僅在起始節點儲存。
儲存方式
還是以上面的例子為例,按邊切割後,變成如下結果:
在JanusGraph中,是點為中心,按切邊的方式儲存資料的。節點的ID作為HBase的Rowkey,節點上的每一個屬性和每一條邊,作為該Rowkey的一個個獨立的Cell。即每一個屬性、每一條邊,都是一個個獨立的KCV結構(Key-Column-Value)。
如下圖所示:
備註: 簡化起見,此處暫不考慮節點中如何記錄其所屬的Vertex Label。
詳細儲存格式
Vertex儲存格式
-
Vertex ID以Rowkey的形式儲存在HBase中。Vertex ID總共64個bit。
-
Vertex ID由partition id、count、ID padding三部分組成。
-
最高位5個bit是partition id. partition是JanusGraph抽象出的一個概念。當Storage Backend是HBase時,JanusGraph會根據partition數量,自動計算並配置各個HBase Region的split key,從而將各個partition均勻對映到HBase的多個Region中。然後通過均勻分配partition id最終實現資料均勻打散到Storage Backend的多臺機器中。
-
中間的count部分是流水號,其中最高位位元固定為0.
-
最後幾個bit是ID padding, 表示Vertex的型別。具體的位數長度根據不同的Vertex型別而不同。最常用的普通Vertex,其值為’000’。
各個Vertex型別字尾劃分如下(拷貝自原始碼):
Property儲存格式
一個Property Key所關聯的屬性值有可能有一個,也有可能有多個,因此,JanusGraph使用Cardinality來描述Property Key的這種特點。Cardinality
有SINGLE
,LIST
和SET
三種類型:
-
SINGLE表示一個Property Key只對應一個Value,這是最常用的場景。
-
LIST表示一個Property Key可以對應多個Value,多個Value可以有重複值。
-
SET表示一個Property Key可以對應多個Value,多個Value不可以有重複值。
JanusGraph的Property,在不同的Cardinality下,資料儲存結構略有不同。整體原則是:根據Cardinality的不同,列名本身能夠確定唯一的一個屬性。
- Cardinality為SINGLE時的儲存結構
HBase的列名只儲存Property Key的ID及方向。具體的Property Value值以及Property ID(JanusGraph為每一個Property分配的唯一ID),都存放在Cell的Value中。另外,如果該Property還有額外的Remaining properties,也會放在Value中。Remaining properties一般不使用,僅在一些特殊場景下,用於為該Property記錄更多的附加資訊(比如儲存元資料Edge Labe的定義等)。
PropertyKeyID及方向整個結構的詳細結構在後文中描述;屬性的值
佔用一個或多個欄位,具體格式在後文描述;屬性的ID
及Remaining屬性
採用相同的格式,具體格式在後文描述。
- Candinality為LIST時的儲存結構
各個部分與Cardinality為SINGLE時的結構相似,區別在於屬性的ID被放在了列名中,而不是放在Value中。
- Candinality為SET儲存結構
各個部分與Cardinality為SINGLE時的結構相似,區別在於屬性的值被放在了列名中,而不是放在Value中。
- 屬性的Property Key ID和方向儲存結構
每一個Property Key本身也有一個唯一的ID。為標識各個屬性是屬於哪一種Property Key,JanusGraph記錄了各個屬性對應的Property Key的ID。JanusGraph在儲存時,會將Property Key ID以及DirectionID(分系統型別/隱藏/常規不隱藏三種屬性)組裝在一起,使用多個位元組來表示。
- 屬性的ID和屬性值儲存結構
屬性的值根據Property Key中定義的資料型別不同,有不同的序列化器。比如,String有StringSerializer, integer有IntegerSerializer. 其中IntegerSerializer與屬性ID的格式化方式相同。
屬性的ID作為一種無符號整數,JanusGraph將其格式化為多個位元組。每一個位元組的最高位位元用於表示是最後一個位元組。0
表示不是最後一個位元組,1
表示是最後一個位元組。
邊儲存格式
JanusGraph的Edge Label也有一個MULTIPLICITY
概念。MULTIPLICITY
有MULTI
等不同的取值,其中以MULTI
取值最為常用。
-
MULTIPLICITY為MULTI時的儲存結構
邊的儲存格式與屬性類似,Property Key和Edge Label被抽象成了Relation Type,並採用相同的資料結構。SortKey是一種特殊的屬性。JanusGraph允許在定義Edge Label時指定其中的一個或多個屬性為Sort Key。對於邊的Sort Key屬性,JanusGraph在儲存時會將其儲存在Relation Type ID的後面,其他所有欄位的前面。通過這種方式,可以保證一個節點的多條同一個型別的邊,會按Sort Key屬性排序儲存。這對於一個節點有大量邊時,對查詢效能提升有幫助。
-
MULTIPLICITY不是MULTI並且此方向有多條邊時的儲存結構
-
MULTIPLICITY不是MULTI並且此方向僅有一條邊時的儲存結構
以上系作者根據自己對原始碼的理解整理而成,難免有不準確或者不全面的地方。歡迎指正,交流。