新手閱讀 Nebula Graph 源碼的姿式

摘要:在本文中,咱們將經過數據流快速學習 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 主要負責正則表達式

  • 客戶端鏈接的管理
  • 解析來自客戶端的 nGQL 語句爲抽象語法樹 AST,並將抽象樹 AST 解析成一系列執行動做。
  • 對執行動做進行優化
  • 執行優化後的執行計劃

Storage Service 主要負責數據庫

  • 數據的分佈式存儲

Meta Service 主要負責微信

  • 圖 schema 的增刪查改
  • 集羣的管理
  • 用戶鑑權

此次,咱們主要對 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 進入ExecutionPlanexecute 函數內部,而後執行到

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 目錄,裏面都是對對應模塊或者功能進行的單元測試,能夠直接編譯對應的單元模塊,而後跟蹤運行。方法以下:

    1. 經過對應目錄下的 CMakeLists.txt 文件找到對應的模塊名
    2. 在 build 目錄下 make 模塊名,在 build/bin/test 目錄下生成對應的二進制程序
    3. gdb 跟蹤調試該程序

附錄

閱讀 Nebula Graph 源碼須要瞭解的一些庫:

  1. flex & bison:詞法分析和語法分析工具,將客戶端輸入的 nGQL 語句解析爲抽象語法樹
  2. FBThrift:Facebook 開源的 RPC 框架,定義並生成了 Meta 層、Storage 層和 Graph 層的通信過程代碼
  3. folly:Facebook 開源的 C++14 組件庫,提供了相似 Boost 和 std 庫的功能,在性能上更加優化
  4. Gtest:Google 開源的 C++ 單元測試框架

其中數據庫資料能夠參考:

  1. 數據庫基本介紹
  2. SQL調優
  3. Nebula 架構剖析系列(零)圖數據庫的總體架構設計

喜歡這篇文章?來來來,給咱們的 GitHub 點個 star 表鼓勵啦~~ 🙇‍♂️🙇‍♀️ [手動跪謝]

交流圖數據庫技術?交個朋友,Nebula Graph 官方小助手微信:NebulaGraphbot 拉你進交流羣~~

做者有話說:Hi,我是明泉,是圖數據 Nebula Graph 研發工程師,主要工做和數據庫查詢引擎相關,但願本次的經驗分享能給你們帶來幫助,若有不當之處也但願能幫忙糾正,謝謝~
相關文章
相關標籤/搜索