摘要:在本文中,咱們將經過數據流快速學習 Nebula Graph,以用戶在客戶端輸入一條 nGQL 語句
SHOW SPACES
爲例,使用 GDB 追蹤語句輸入時 Nebula Graph 是怎麼調用和運行的。
首發於 Nebula Graph 博客: https://nebula-graph.com.cn/p...
對於一些剛開始接觸 Nebula Graph 開源庫的小夥伴來講,剛開始可能和我同樣,想要提升本身,看看大神們的代碼而後試着可以作點什麼,或許可以修復一個看起來並非那麼困難的 Bug。可是面對如此多的代碼,我裂開了,不知道如何下手。最後硬着頭皮,再看了一遍又一遍代碼,跑了一個又一個用例以後終於有點眉目了。html
下面就分享下我的學習 Nebula Graph 開源代碼的過程,也但願剛接觸 Nebula Graph 的小夥伴可以少走彎路,快速入門。另外 Nebula Graph 自己也用到了一些開源庫,詳情能夠見附錄。git
在本文中,咱們將經過數據流快速學習 Nebula Graph,以用戶在客戶端輸入一條 nGQL 語句 SHOW SPACES
爲例,使用 GDB 追蹤語句輸入時 Nebula Graph 是怎麼調用和運行的。github
一個完整的 Nebula Graph 包含三個服務,即 Query Service,Storage Service 和 Meta Service。每一個服務都有其各自的可執行二進制文件。web
Query Service 主要負責正則表達式
Storage Service 主要負責數據庫
Meta Service 主要負責微信
此次,咱們主要對 Query Service 進行分析markdown
剛開始,能夠拿到一個 source 包,解壓,能夠先看看代碼的層級關係,不一樣的包主要功能是幹什麼的 下面只列出 src 目錄:session
|--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;
已經執行完畢,而其餘複雜的語句也能夠以此類推。
若是不想啓動服務端和客戶端進行調試,在 src 目錄下的每一個文件夾下都有一個 test 目錄,裏面都是對對應模塊或者功能進行的單元測試,能夠直接編譯對應的單元模塊,而後跟蹤運行。方法以下:
閱讀 Nebula Graph 源碼須要瞭解的一些庫:
其中數據庫資料能夠參考:
喜歡這篇文章?來來來,給咱們的 GitHub 點個 star 表鼓勵啦~~ 🙇♂️🙇♀️ [手動跪謝]
交流圖數據庫技術?交個朋友,Nebula Graph 官方小助手微信:NebulaGraphbot 拉你進交流羣~~
做者有話說:Hi,我是明泉,是圖數據 Nebula Graph 研發工程師,主要工做和數據庫查詢引擎相關,但願本次的經驗分享能給你們帶來幫助,若有不當之處也但願能幫忙糾正,謝謝~