解析 Nebula Graph 子圖設計及實踐
前言
在先前的 Query Engine 原始碼解析中,我們介紹了 2.0 中 [Query Engine]和 1.0 的主要變化和大體的結構:
大家可以大概瞭解到使用者透過客戶端傳送一條查詢語句,Query Engine 是如何解析語句、把語句構建為抽象語法樹,在抽象語法樹進行校驗、生成執行計劃的過程。本文會透過 2.0 中新增的子圖演算法模組繼續講解 Query Engine 背後所做的內容,並著重介紹執行計劃生成的過程,以便加強你對原始碼更好地理解。
子圖的定義
子圖是指節點集合和邊集合分別是某一圖的節點集的子集和邊集的子集的圖。直觀地理解,就是從使用者指定的起點開始出發沿著指定的邊一步步擴充,直到達到使用者所設定的步數為止,然後返回在擴充過程中遇到的所有點集和邊集。
子圖的語法
GET SUBGRAPH [ STEPS] FROM {, ...} [IN , ...]
[OUT , ...] [BOTH , ...]
-
step_count:指定從起始點開始的跳數,返回從 0 到
step_count
跳的子圖。必須是非負整數。預設值為 1 -
vid:指定起始點 ID
-
edge_type:指定邊型別。可以用
IN
、OUT
和BOTH
來指定起始點上該邊型別的方向。預設為BOTH
子圖的實現
當 Query Engine 接收到 GET SUBGRAPH
命令後,Parser 模組(由 flex 和 bison 實現)會根據已經寫好的規則(parser.yy
中 get_subgraph_sentence
規則)把所需要的內容從查詢語句中提取出來,生成一個抽象語法樹,如下所示:
然後進入 Validate 階段,此時對生成的抽象語法樹進行校驗,目的是為了驗證使用者的輸入是否合法,當校驗透過後,會把語法樹中的內容提取出來,生成一個執行計劃。
那麼這個執行計劃是如何生成的呢?對同一功能不同的資料庫廠商可能會生成不同的執行計劃,但是原理都是相同的。那就是要看自身的運算元有哪些和查詢層和儲存層是如何進行互動的。因為我們的每一條查詢語句到最後都是要從儲存層取資料的。在 Nebula Graph 中 Query Engine 和儲存層是透過 RPC 方式(fbthrift)進行互動的(介面定義在 common 倉中的 interface 目錄下)。這裡有兩個非常關鍵的介面 getNeighbors 和 getProps 需要了解一下。
getNeighbors 其中 fbthrift 的定義格式如下:
struct GetNeighborsRequest {
1: common.GraphSpaceID space_id,
2: list column_names,
3: map>
(cpp.template = "std::unordered_map") parts,
4: TraverseSpec traverse_spec
}
該結構中每個變數的詳細定義可以參考 github.com/vesoft-inc/nebula-common/blob/master/src/common/interface/storage.thrift,裡面有詳細的註釋。
其主要功能就是 Query Engine 根據定義好的結構傳入起始點和要擴充的邊型別資訊,然後儲存層會找到起始點,然後把該點的屬性和以該點的出邊的邊屬性找出來組裝成一個表格返回給 Query Engine,其中返回的表格的格式參考 github.com/vesoft-inc/nebula-common/blob/master/src/common/interface/storage.thrift 中 GetNeighborsResponse 的定義,然後在 Query Engine 中我們就可以透過這個表格提取到我們想要的內容。
例如在 basketba l l 資料集中,當起始點為 Tim Duncan、Manu Ginobili 沿著 like
邊雙向擴充。想要獲得 $^.[player.name]()
、like._dst
、$$.[player.name]()
和 like.likeness
這四個屬性。其返回的資料大致如下所示:
表格1
因為是雙向擴充第四列的 + like
代表出邊,第五列的 - like
代表入邊。
在 Nebula Graph 的儲存層中邊是和起始點在一起存放的,所以透過 getNeighbor 介面就可以獲得起點和出邊的所有屬性資訊,但是如果想要在擴充過程中拿到目的點的屬性資訊則需要使用 getProps 介面,當然如果我只想透過 fetch 語句拿到某個點或者邊的屬性也需要呼叫這個介面。你可以自行了解 github.com/vesoft-inc/nebula-common/blob/master/src/common/interface/storage.thrift下 getPropRequest 的定義,加深理解。
執行計劃
有了上面的介面定義我們就可以開始執行計劃了,首先需要的運算元有 start、getNeighbor、subgraph、loop、datacollect。
-
start 運算元:相當於執行計劃中葉子節點,不做任何事情。目的是告訴排程器,之後沒有可以依賴的運算元,或者可以理解為遞迴演算法中的終止條件。
-
loop 運算元:相當於 C 語言中的 while 語法,該運算元有三個成員 depend、condition 和 loopBody,depend 在多語句和
PIPE
中會使用當前暫且不表,condition 相當於終止條件。loopBody 相當於 while 中的迴圈體。 -
subgraph 運算元:負責把 getNeighbor 運算元結果中的
_dst
(目的點)屬性提出來然後過濾掉已經訪問過的目的點(避免重複從儲存層拿資料),然後把它們當作 getNeighbor 運算元下一次擴充時的輸入。 -
datacollect 運算元:負責在最後把擴充過程中獲得的點和邊屬性收集起來組裝為 vertex 和 edge 型別。
其中各個運算元的詳細資訊,可參考原始碼 github.com/vesoft-inc/nebula-graph/tree/master/src/executor 。
下面透過圖1 舉例,我們是如何構建子圖的
圖1
擴充一步的情況
當從 A 點開始沿著 like
邊只獲取一步的所有點和邊的資訊,則很容易。只需要 getNeighbor 和 dataCollect 這兩個運算元就可以了。執行計劃如下圖所示 :
擴充多步的情況
一步場景其實是多步的場景的特殊情況。所以可以將一步的場景合入到多步場景中。當從 A 點開始,沿著 like 邊擴充三步的話,根據現有的運算元,可以在 getNeighbor 擴充後把目的點提取出來,然後將這些目的點當作起點重新呼叫 getNeighbor 介面,這個迴圈兩次就可以了(loop 運算元的終止條件設定為當前步數),因此執行計劃如下圖所示 :
輸入和輸出
一般情況下,每個運算元的輸入就是所依賴運算元的輸出,這時候根據執行計劃的依賴關係就可以直觀地確定每個運算元的輸入和輸出。但是在某些情況下,比如:子圖,在多步場景中每一次 getNeighbor 運算元的輸入都應該是上一次擴充邊的目的點,也就是 subgraph 運算元的輸出,因此 subgraph 運算元的輸出應該就是 getNeighbor 運算元的輸入。這時就和上圖的執行計劃依賴不一致,這時就需要自行設定每個運算元的輸入和輸出。在 Query Engine 2.0 中我們已經介紹了每個運算元的輸入和輸出是存放在雜湊表中的,其中 value 是 vector 型別。如下表 ResultMap 所示:
-
起始點存放在 ResultMap[“StartVid”] 中
-
getNeighbor 運算元的輸入是 ResultMap[“StartVid”], 輸出存放在 ResultMap[“GN_1”]
-
subgraph 運算元的輸入是 ResultMap[“GN_1”], 輸出存放在 ResultMap[“StartVid”]
-
loop 運算元不產生資料,當作邏輯迴圈使用,因此不需要設定輸入輸出
-
dataCollect 運算元的輸入是 ResultMap[“GN_1”], 輸出存放在 ResultMap[“DATACOLLECT_2”]
這時 getNeighbor 運算元會把每一次的結果放在 ResultMap[“GN_1”] 中的 vector 中的末尾,然後 subgraph 運算元從 ResultMap[“GN_1”] 中的 vector 中的末尾取值,經過計算再把下一次要擴充的起始點存放在 ResultMap[“StartVid”] 中。
當擴充第一步後,ResultMap 的結果如下:
為了方便顯示,GetNeighbor 的結果只寫了 _dst
的屬性,實際上會帶上邊上所有的屬性和起始點的所有屬性,類似於表格 1。
subgraph 運算元接收"GN_1"的輸入,提取 _dst
屬性,然後將結果放入"StartVid"中。當擴充第二步後,ResultMap 的結果如下:
當擴充第三步後,ResultMap 的結果如下:
最後 dataCollect 運算元從 ResultMap[“GN_1”] 中取出擴充過程中遇到的所有點集和邊集,組裝成最終的結果返回給使用者。
例項
下面執行一個子圖的例項看看在 Nebula Graph 中執行計劃的具體結構,開啟 nebula-console, 切換 space 到 basketball, 輸入 EXPLAIN format="dot" GET SUBGRAPH 2 STEPS FROM 'Tim Duncan' IN like, serve
,這時候 nebula-console 會生成 dot 格式的資料,然後開啟 [Graphviz Online]這個網站,將生成的 dot 資料貼上上去,就可以看到如下結構:
其中 Start_0 運算元是 loop 運算元中 depend 的依賴,由於沒有多語句或 PIPE 語句,因此不做任何處理。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2001/viewspace-2797076/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- GraphX 在圖資料庫 Nebula Graph 的圖計算實踐資料庫
- Nebula Graph 在眾安保險的圖實踐
- Nebula Graph 的 Ansible 實踐
- 分散式圖資料庫 Nebula Graph 的 Index 實踐分散式資料庫Index
- 使用 MyBatis 操作 Nebula Graph 的實踐MyBatis
- 高效能圖計算系統 Plato 在 Nebula Graph 中的實踐
- Geospatial Data 在 Nebula Graph 中的實踐
- 基於 Nebula Graph 構建百億關係知識圖譜實踐
- Nebula Graph 在網易遊戲業務中的實踐遊戲
- 圖計算 on nLive:Nebula 的圖計算實踐
- K6 在 Nebula Graph 上的壓測實踐
- Jepsen 測試框架在圖資料庫 Nebula Graph 中的實踐框架資料庫
- 圖資料庫 Nebula Graph TTL 特性資料庫
- Neo4j 匯入 Nebula Graph 的實踐總結
- 智聯招聘基於 Nebula Graph 的推薦實踐分享
- 圖資料庫 Nebula Graph 的程式碼變更測試覆蓋率實踐資料庫
- Nebula Graph 在微眾銀行資料治理業務的實踐
- 圖資料庫 Nebula Graph 的資料模型和系統架構設計資料庫模型架構
- 一文了解 Nebula Graph DBaaS 服務——Nebula Graph Cloud ServiceCloud
- 圖資料庫 Nebula Graph 的安裝部署資料庫
- 基於 Nebula Graph 構建圖學習能力
- 圖資料庫實操:用 Nebula Graph 破解成語版 Wordle 謎底資料庫
- Nebula Graph|資訊圖譜在攜程酒店的應用
- 圖資料庫|Nebula Graph v3.1.0 效能報告資料庫
- 圖資料庫|[Nebula Graph v3.1.0 效能報告資料庫
- 從 Neo4j 匯入 Nebula Graph 實踐見 SPark 資料匯入原理Spark
- Nebula Operator 雲上實踐
- 一文讀懂圖資料庫 Nebula Graph 訪問控制實現原理資料庫
- 圖資料庫 Nebula Graph v.1.0.0-beta 已上線資料庫
- 初識分散式圖資料庫 Nebula Graph 2.0 Query Engine分散式資料庫
- 圖資料庫 Nebula Graph 在 Boss 直聘的應用資料庫
- Nebula Graph 在大規模資料量級下的實踐和定製化開發
- 如何實現十億級離線 CSV 匯入 Nebula Graph
- 使用 Docker 構建 Nebula Graph 原始碼Docker原始碼
- Nebula Graph 在企查查的應用
- 圖資料庫|基於 Nebula Graph 的 BetweennessCentrality 演算法資料庫演算法
- RESTful API 設計思路及實踐RESTAPI
- Nebula Graph 特性講解——RocksDB 統計資訊的收集和展示