摘要:在本文中,我們将通過資料流快速學習 Nebula Graph,以使用者在用戶端輸入一條 nGQL 語句
SHOW SPACES
為例,使用 GDB 追蹤語句輸入時 Nebula Graph 是怎麼調用和運作的。
首發于 Nebula Graph 部落格:
https://nebula-graph.com.cn/posts/how-to-read-nebula-graph-source-code/

導讀
對于一些剛開始接觸
Nebula Graph開源庫的小夥伴來說,剛開始可能和我一樣,想要提高自己,看看大神們的代碼然後試着能夠做點什麼,或許能夠修複一個看起來并不是那麼困難的 Bug。但是面對如此多的代碼,我裂開了,不知道如何下手。最後硬着頭皮,再看了一遍又一遍代碼,跑了一個又一個用例之後終于有點眉目了。
下面就分享下個人學習 Nebula Graph 開源代碼的過程,也希望剛接觸 Nebula Graph 的小夥伴能夠少走彎路,快速入門。另外 Nebula Graph 本身也用到了一些開源庫,詳情可以見附錄。
在本文中,我們将通過資料流快速學習 Nebula Graph,以使用者在用戶端輸入一條 nGQL 語句
SHOW SPACES
整體架構
一個完整的 Nebula Graph 包含三個服務,即 Query Service,Storage Service 和 Meta Service。每個服務都有其各自的可執行二進制檔案。
Query Service 主要負責
- 用戶端連接配接的管理
- 解析來自用戶端的 nGQL 語句為抽象文法樹 AST,并将抽象樹 AST 解析成一系列執行動作。
- 對執行動作進行優化
- 執行優化後的執行計劃
Storage Service 主要負責
- 資料的分布式存儲
Meta Service 主要負責
- 圖 schema 的增删查改
- 叢集的管理
- 使用者鑒權
這次,我們主要對 Query Service 進行分析
目錄結構
剛開始,可以拿到一個 source 包,解壓,可以先看看代碼的層級關系,不同的包主要功能是幹什麼的 下面隻列出 src 目錄:
|--src
|--client // 用戶端代碼
|--common // 提供一些常用的基礎元件
|--console
|--daemons
|--dataman
|--graph // 包含了Query Service的大部分代碼
|--interface // 主要是一些 meta、storage 和 graph 的通訊接口定義
|--jni
|--kvstore
|--meta // 中繼資料管理相關
|--parser // 主要負責詞法和文法分析
|--storage // 存儲層相關
|--tools
|--webservice
代碼跟蹤
通過 scripts 目錄下的腳本啟動 metad 和 storaged 這兩個服務:
啟動後通過
nebula.service status all
檢視目前的服務狀态
然後 gdb 運作 bin 目錄下的
nebula-graphd
二進制程式
gdb> set args --flagfile /home/mingquan.ji/1.0/nebula-install/etc/nebula-graphd.conf //設定函數入參
gdb> set follow-fork-mode child // 由于是守護程序,是以在 fork 子程序後 gdb 繼續跟蹤子程序
gdb> b main // 在 mian 入口打斷點
在 gdb 中輸入
run
開始運作
nebula-graphd
程式,然後通過
next
可以一步一步運作,直到遇到
gServer->serve(); // Blocking wait until shut down via gServer->stop()
,此時
nebula-graphd
的所有線程阻塞,等待用戶端連接配接,這時需要找到用戶端發起請求後由哪個函數處理。
由于 Nebula Graph 使用 FBThrift 來定義生成不同服務的通訊代碼,在
src/interface/graph.thrift
檔案中可以看到 GraphService 接口的定義如下:
service GraphService {
AuthResponse authenticate(1: string username, 2: string password)
oneway void signout(1: i64 sessionId)
ExecutionResponse execute(1: i64 sessionId, 2: string stmt)
}
在
gServer->serve()
之前有
auto interface = std::make_shared<GraphService>();
status = interface->init(ioThreadPool);
gServer->setInterface(std::move(interface));
gServer->setAddress(localIP, FLAGS_port);
可以知道是由
GraphService
對象來處理用戶端的連接配接和請求,是以可以在
GraphService.cpp:
`future_execute` 處打斷點,以便跟蹤後續處理流程。
此時重新打開一個終端進入 nebula 安裝目錄,通過
./nebule -u=root -p=nebula
來連接配接 nebula 服務,再在用戶端輸入
SHOW SPACES
,此時用戶端沒有反應,是因為服務端還在阻塞調試中,回到服務端輸入 continue,如下所示:
經過
session
驗證後,進入
executionEngine->execute()
中,
step
進入函數内部
auto plan = new ExecutionPlan(std::move(ectx));
plan->execute();
繼續
step
進入
ExecutionPlan
的
execute
函數内部,然後執行到
auto result = GQLParser().parse(rctx->query());
parse
這塊主要使用
flex & bison
,用于詞法分析和文法解析構造對象到抽象文法樹,其詞法檔案是 src/parser/scanner.lex,文法檔案是 src/parser/parser.yy,其詞法分析類似于正規表達式,文法分析舉例如下:
go_sentence
: KW_GO step_clause from_clause over_clause where_clause yield_clause {
auto go = new GoSentence();
go->setStepClause($2);
go->setFromClause($3);
go->setOverClause($4);
go->setWhereClause($5);
if ($6 == nullptr) {
auto *cols = new YieldColumns();
for (auto e : $4->edges()) {
if (e->isOverAll()) {
continue;
}
auto *edge = new std::string(*e->edge());
auto *expr = new EdgeDstIdExpression(edge);
auto *col = new YieldColumn(expr);
cols->addColumn(col);
}
$6 = new YieldClause(cols);
}
go->setYieldClause($6);
$$ = go;
}
其在比對到對應到 go 語句時,就構造對應的節點,然後由 bison 處理,最後生成一個抽象的文法樹。
詞法文法分析後開始執行子產品,繼續
gdb
,進入
excute
函數,一直
step
直到進入
ShowExecutor::execute
函數。
next
直到
showSpaces()
,
step
進入此函數
auto future = ectx()->getMetaClient()->listSpaces();
auto *runner = ectx()->rctx()->runner();
'''
'''
std::move(future).via(runner).thenValue(cb).thenError(error);
此時 Query Service 通過 metaClient 和 Meta Service 通信拿到
spaces
資料,之後通過回調函數
cb
回傳拿到的資料,至此 nGQL 語句
SHOW SPACES;
已經執行完畢,而其他複雜的語句也可以以此類推。
- 如果是正在運作的服務,可以先查出該服務的程序 ID,然後通過 gdb attach PID 來調試該程序;
- 如果不想啟動服務端和用戶端進行調試,在 src 目錄下的每個檔案夾下都有一個 test 目錄,裡面都是對對應子產品或者功能進行的單元測試,可以直接編譯對應的單元子產品,然後跟蹤運作。方法如下:
- 通過對應目錄下的 CMakeLists.txt 檔案找到對應的子產品名
- 在 build 目錄下 make 子產品名,在 build/bin/test 目錄下生成對應的二進制程式
- gdb 跟蹤調試該程式
附錄
閱讀 Nebula Graph 源碼需要了解的一些庫:
- flex & bison :詞法分析和文法分析工具,将用戶端輸入的 nGQL 語句解析為抽象文法樹
- FBThrift :Facebook 開源的 RPC 架構,定義并生成了 Meta 層、Storage 層和 Graph 層的通訊過程代碼
- folly :Facebook 開源的 C++14 元件庫,提供了類似 Boost 和 std 庫的功能,在性能上更加優化
- Gtest :Google 開源的 C++ 單元測試架構
其中資料庫資料可以參考:
喜歡這篇文章?來來來,給我們的
GitHub點個 star 表鼓勵啦~~ 🙇♂️🙇♀️ [手動跪謝]
交流圖資料庫技術?交個朋友,Nebula Graph 官方小助手微信:
NebulaGraphbot拉你進交流群~~
作者有話說:Hi,我是明泉,是圖資料 Nebula Graph 研發工程師,主要工作和資料庫查詢引擎相關,希望本次的經驗分享能給大家帶來幫助,如有不當之處也希望能幫忙糾正,謝謝~