摘要:在本文中,我們將通過數(shù)據(jù)流快速學(xué)習(xí) Nebula Graph,以用戶在客戶端輸入一條 nGQL 語句
SHOW SPACES為例,使用 GDB 追蹤語句輸入時 Nebula Graph 是怎么調(diào)用和運行的。
首發(fā)于 Nebula Graph 博客:https://nebula-graph.com.cn/posts/how-to-read-nebula-graph-source-code/

導(dǎo)讀
對于一些剛開始接觸 Nebula Graph 開源庫的小伙伴來說,剛開始可能和我一樣,想要提高自己,看看大神們的代碼然后試著能夠做點什么,或許能夠修復(fù)一個看起來并不是那么困難的 Bug。但是面對如此多的代碼,我裂開了,不知道如何下手。最后硬著頭皮,再看了一遍又一遍代碼,跑了一個又一個用例之后終于有點眉目了。
下面就分享下個人學(xué)習(xí) Nebula Graph 開源代碼的過程,也希望剛接觸 Nebula Graph 的小伙伴能夠少走彎路,快速入門。另外 Nebula Graph 本身也用到了一些開源庫,詳情可以見附錄。
在本文中,我們將通過數(shù)據(jù)流快速學(xué)習(xí) Nebula Graph,以用戶在客戶端輸入一條 nGQL 語句 SHOW SPACES 為例,使用 GDB 追蹤語句輸入時 Nebula Graph 是怎么調(diào)用和運行的。
整體架構(gòu)

一個完整的 Nebula Graph 包含三個服務(wù),即 Query Service,Storage Service 和 Meta Service。每個服務(wù)都有其各自的可執(zhí)行二進制文件。
Query Service 主要負(fù)責(zé)
- 客戶端連接的管理
- 解析來自客戶端的 nGQL 語句為抽象語法樹 AST,并將抽象樹 AST 解析成一系列執(zhí)行動作。
- 對執(zhí)行動作進行優(yōu)化
- 執(zhí)行優(yōu)化后的執(zhí)行計劃
Storage Service 主要負(fù)責(zé)
- 數(shù)據(jù)的分布式存儲
Meta Service 主要負(fù)責(zé)
- 圖 schema 的增刪查改
- 集群的管理
- 用戶鑒權(quán)
這次,我們主要對 Query Service 進行分析
目錄結(jié)構(gòu)
剛開始,可以拿到一個 source 包,解壓,可以先看看代碼的層級關(guān)系,不同的包主要功能是干什么的 下面只列出 src 目錄:
|--src
|--client // 客戶端代碼
|--common // 提供一些常用的基礎(chǔ)組件
|--console
|--daemons
|--dataman
|--graph // 包含了Query Service的大部分代碼
|--interface // 主要是一些 meta、storage 和 graph 的通訊接口定義
|--jni
|--kvstore
|--meta // 元數(shù)據(jù)管理相關(guān)
|--parser // 主要負(fù)責(zé)詞法和語法分析
|--storage // 存儲層相關(guān)
|--tools
|--webservice
代碼跟蹤
通過 scripts 目錄下的腳本啟動 metad 和 storaged 這兩個服務(wù):

啟動后通過 nebula.service status all 查看當(dāng)前的服務(wù)狀態(tài)

然后 gdb 運行 bin 目錄下的 nebula-graphd 二進制程序
gdb> set args --flagfile /home/mingquan.ji/1.0/nebula-install/etc/nebula-graphd.conf //設(shè)置函數(shù)入?yún)?gdb> set follow-fork-mode child // 由于是守護進程,所以在 fork 子進程后 gdb 繼續(xù)跟蹤子進程
gdb> b main // 在 mian 入口打斷點
在 gdb 中輸入 run 開始運行 nebula-graphd 程序,然后通過 next 可以一步一步運行,直到遇到 gServer->serve(); // Blocking wait until shut down via gServer->stop(),此時 nebula-graphd 的所有線程阻塞,等待客戶端連接,這時需要找到客戶端發(fā)起請求后由哪個函數(shù)處理。
由于 Nebula Graph 使用 FBThrift 來定義生成不同服務(wù)的通訊代碼,在 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 處打斷點,以便跟蹤后續(xù)處理流程。
此時重新打開一個終端進入 nebula 安裝目錄,通過 ./nebule -u=root -p=nebula 來連接 nebula 服務(wù),再在客戶端輸入 SHOW SPACES ,此時客戶端沒有反應(yīng),是因為服務(wù)端還在阻塞調(diào)試中,回到服務(wù)端輸入 continue,如下所示:

經(jīng)過 session 驗證后,進入 executionEngine->execute() 中,step 進入函數(shù)內(nèi)部
auto plan = new ExecutionPlan(std::move(ectx));
plan->execute();
繼續(xù) step 進入ExecutionPlan 的 execute 函數(shù)內(nèi)部,然后執(zhí)行到
auto result = GQLParser().parse(rctx->query());
parse 這塊主要使用 flex & bison,用于詞法分析和語法解析構(gòu)造對象到抽象語法樹,其詞法文件是 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;
}
其在匹配到對應(yīng)到 go 語句時,就構(gòu)造對應(yīng)的節(jié)點,然后由 bison 處理,最后生成一個抽象的語法樹。
詞法語法分析后開始執(zhí)行模塊,繼續(xù) gdb,進入 excute 函數(shù),一直 step 直到進入ShowExecutor::execute 函數(shù)。

繼續(xù) next 直到 showSpaces(),step 進入此函數(shù)
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 數(shù)據(jù),之后通過回調(diào)函數(shù) cb 回傳拿到的數(shù)據(jù),至此 nGQL 語句 SHOW SPACES; 已經(jīng)執(zhí)行完畢,而其他復(fù)雜的語句也可以以此類推。
- 如果是正在運行的服務(wù),可以先查出該服務(wù)的進程 ID,然后通過 gdb attach PID 來調(diào)試該進程;
- 如果不想啟動服務(wù)端和客戶端進行調(diào)試,在 src 目錄下的每個文件夾下都有一個 test 目錄,里面都是對對應(yīng)模塊或者功能進行的單元測試,可以直接編譯對應(yīng)的單元模塊,然后跟蹤運行。方法如下:
- 通過對應(yīng)目錄下的 CMakeLists.txt 文件找到對應(yīng)的模塊名
- 在 build 目錄下 make 模塊名,在 build/bin/test 目錄下生成對應(yīng)的二進制程序
- gdb 跟蹤調(diào)試該程序
附錄
閱讀 Nebula Graph 源碼需要了解的一些庫:
- flex & bison:詞法分析和語法分析工具,將客戶端輸入的 nGQL 語句解析為抽象語法樹
- FBThrift:Facebook 開源的 RPC 框架,定義并生成了 Meta 層、Storage 層和 Graph 層的通訊過程代碼
- folly:Facebook 開源的 C++14 組件庫,提供了類似 Boost 和 std 庫的功能,在性能上更加優(yōu)化
- Gtest:Google 開源的 C++ 單元測試框架
其中數(shù)據(jù)庫資料可以參考:
喜歡這篇文章?來來來,給我們的 GitHub 點個 star 表鼓勵啦~~ ???♂????♀? [手動跪謝]
交流圖數(shù)據(jù)庫技術(shù)?交個朋友,Nebula Graph 官方小助手微信:NebulaGraphbot 拉你進交流群~~
作者有話說:Hi,我是明泉,是圖數(shù)據(jù) Nebula Graph 研發(fā)工程師,主要工作和數(shù)據(jù)庫查詢引擎相關(guān),希望本次的經(jīng)驗分享能給大家?guī)韼椭?,如有不?dāng)之處也希望能幫忙糾正,謝謝~