摘要:在本文中,我們將通過資料流快速學習 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
為例,使用 GDB 追蹤語句輸入時 Nebula Graph 是怎麼呼叫和執行的。
整體架構
一個完整的 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 研發工程師,主要工作和資料庫查詢引擎相關,希望本次的經驗分享能給大家帶來幫助,如有不當之處也希望能幫忙糾正,謝謝~