開源之夏項目分享:圖數據庫 Nebula Graph 支持 JDBC 協議

本文首發於 Nebula Graph Community 公衆號

nebula-dashboard

開源之夏

開源軟件供應鏈點亮計劃 - 暑期 2021(下簡稱:開源之夏)是由中國科學院軟件研究所與 openEuler 社區共同舉辦的一項面向高校學生的暑期活動,旨在鼓勵在校學生積極參與開源軟件的開發維護,促進優秀開源軟件社區的蓬勃發展。中科院聯合包括 Nebula Graph 在內的國內各大開源社區,針對重要開源軟件的開發與維護提供項目,並向全球高校學生開放報名。學生在自由選擇項目後,與社區導師溝通實現方案並撰寫項目計劃書。被選中的學生將在社區導師指導下,按計劃完成開發工作,並將成果貢獻給社區。根據項目的難易程度和完成情況,參與者將獲得由主辦方發放的 6,000 - 12,000 不等的項目獎金。

活動官網:https://summer.iscas.ac.cn/

本期分享來自 Nebula Graph 社區鄭東陽同學(圖數據庫 Nebula Graph 支持 JDBC 協議)的項目經驗。

項目信息

項目名稱:圖數據庫 Nebula Graph 支持 JDBC 協議

項目詳情

讓 Nebula Graph 可以對接 JDBC 協議,實現 Nebula JDBC driver,實現 JDBC 的相關接口。要求:用戶可直接使用 JDBC 驅動操作 Nebula 服務,項目 repo 有自動運行的單元測試。

Nebula Graph 簡介

一個可靠的分佈式、線性擴容、性能高效的圖數據庫;世界上唯一能夠容納千億個頂點和萬億條邊,並提供毫秒級查詢延時的圖數據庫解決方案。Nebula Graph 特點

  • 開源:致力於與社區合作, 普及及促進圖數據庫的發展;
  • 安全:具有基於角色的權限控制,授權才能訪問;
  • 擴展性:支持 Spark、Hadoop、GraphX、Plato 等等多種周邊生態工具;
  • 高性能:Nebula Graph 在維持高吞吐量的同時依舊能做到低時延的讀寫;
  • 擴容:基於 shared-nothing 分佈式架 Nebula Graph 支持線性擴容;
  • 兼容 openCypher:逐步兼容 openCypher9 ,Cypher 用戶可輕鬆上手 Nebula Graph;
  • 高可用:支持多種方式恢復異常數據,保證在局部失敗的情況下服務的高可用性;
  • 穩定發行版:經過一線互聯網大廠,諸如京東、美團、小紅書在生產環境上的業務考驗。

Nebula Graph 具有活躍的社區與及時的技術支持,這是官網:https://nebula-graph.com.cn 和 GitHub 倉庫:https://github.com/vesoft-inc/nebula,歡迎關注及使用 Nebula Graph,一起成爲 Nebula Graph 的 Contributor,爲圖數據庫的發展貢獻力量!!!

項目落地

方案描述

前期瞭解 Nebula Graph 相關功能,掌握其基本使用;調研 JDBC 的驅動開發,閱讀 JDBC 規範文檔,瞭解一些需要實現的接口;中期參考 Neo4j 的 neo4j-jdbc:https://github.com/neo4j-contrib/neo4j-jdbc 實現,克隆 nebula-java:https://github.com/vesoft-inc/nebula-java 項目,學習源碼,瞭解項目代碼的主要邏輯和代碼風格;後期利用已有的輪子 nebula-java:https://github.com/vesoft-inc/nebula-java 實現與數據庫的通信,編寫代碼爲 Nebula Graph 實現 JDBC 的相關接口, 編寫單元測試。

實現描述

這個項目實現的思路很清晰:implements JDBC 規範中的一系列接口(主要位於 java.sql 包中),實現接口中的方法。JDBC 規範中所有的類加起來需要實現的方法有好幾百個。JDBC 主要面向的數據庫是傳統的關係型數據庫(RDB),而 Nebula Graph 作爲新一代的圖數據庫,比起久經發展的關係型數據庫來說沒有它那麼完備的功能特性,但是又比關係型數據庫多出許多新的特點,所以 JDBC 規範中的方法對於 Nebula Graph 而言既有多餘(不需要真正實現)也有不足。(需要實現但是沒有在相關接口中定義) 在具體的實現中,定義出一些抽象類直接 implements 規範中的主要接口,再定義出具體的實現類實現接口中一些重要的方法,這樣一來實現類中的方法在閱讀時不會顯得很雜很亂。對於接口中需要實現的方法:

for( method :  接口的方法 ){
    if(method BELONG_TO  不需要具體實現的方法){
        // 比如 Statement::getGeneratedKeys()
        在該抽象類中 Override,方法體中拋出一個SQLFeatureNotSupportedException;
    }else if(method BELONG_TO 需要實現但是不是核心方法){
        // 比如 Statement::isClosed()
        在該抽象類中 Override;
    }else if(method BELONG_TO 需要實現且是核心方法){
        // 比如 Statement::execute(String nGql)
        在具體實現類中 Override
    }else if(method BELONG_TO 在接口中沒有定義但是需要實現){
        // 比如 NebulaResult::getNode getEdge getPath (點,邊,路徑是圖數據庫特有概念)
        在具體實現類中實現
    }
}

項目中主要的一些 implements 和 extends 關係如下:(藍色實線是類之間的 extends 關係,綠色實線是接口之間的 implements 關係,綠色虛線是抽象類與接口之間的 implements 關係)。

nebula-dashboard

工作流程及類中主要方法分析:

// 用戶首先通過 NebulaDriver 註冊驅動,其中有 NebulaPool 屬性,用於獲取 Session 與數據庫通信
// NebulaDriver 中提供兩個構造函數,無參構造函數配置默認的 NebulaPool,接收一個 Properties 類型參數的構造函數可以自定義 NebulaPool 配置
​
public NebulaDriver() throws SQLException {
    this.setDefaultPoolProperties();
    this.initNebulaPool();
    // 將自身註冊到 DriverManager
    DriverManager.registerDriver(this);
}
​
public NebulaDriver(Properties poolProperties) throws SQLException {
    this.poolProperties = poolProperties;
    this.initNebulaPool();
    // 將自身註冊到 DriverManager
    DriverManager.registerDriver(this);
}
​
// 註冊驅動後用戶可以 DriverManager::getConnection(String url) 獲取連接。在 NebulaConnection 的構造函數中會通過 NebulaDriver 中的 NebulaPool 獲取 Session 接着連接訪問在 url 中指定的圖空間
​
// 獲取到 Connection 後用戶可以用 Connection::createStatement 和 Connection::prepareStatement 拿到 Statement 或者 PreparedStatement 對象,調用其中的 execute 方法向數據庫發送命令,數據庫執行此命令後的結果會封裝在 NebulaResult 中,再調用其中各種獲取數據的方法可以得到不同數據類型的數據
​
// 目前 NebulaResult 中實現的獲取數據方法有以下這些,Nebula Graph 中不同的數據類型都有對應實現
public String getString();
public int getInt();
public long getLong();
public boolean getBoolean();
public double getDouble();
public java.sql.Date getDate();
public java.sql.Time getTime();
public DateTimeWrapper getDateTime();
public Node getNode();
public Relationship getEdge();
public PathWrapper getPath();
public List getList();
public Set getSet();
public Map getMap();

項目進度

已完成工作

nebula-dashboard

完成以下實現:

遇到的問題及解決方案

如何與數據庫通信的問題

項目前期過程中不知道如何與數據庫通信,在研究友商 Neo4j 的 neo4j-jdbc 實現後利用 Http 框架通過 Nebula Graph 的 API (粗糙地)實現了與數據庫的通信;完成後與導師聯繫詢問該想法是否可行,導師告訴我可以用已有的輪子 nebula-java,通過 rpc 與 Nebula Graph 通信。

數據統計採用了計算複合指標的方法,計算得出各家企業在企業規模、社會影響、發展潛力和社會責任四個維度上的得分,加權平均後確定排名。

關於獲取 Connection 的問題

NebulaPoolConfig 類中的一些參數是可配置的,我的想法是以在連接字符串中指定的形式進行配置,如:"jdbc:nebula://ip:port/graphSpace?maxConnsSize=10&reconnect=true"。

諮詢導師後導師建議可以讓用戶獲取連接的時候,支持兩種接口,一種是用默認配置,一種是讓用戶指定配置,如:

// default configuration
DriverManager.getConnection(url, username, password);
​
// customized configuration
DriverManager.getConnection(url, config);

關於 PreparedStatement 的問題

關係型數據庫支持查詢語句預編譯的功能,PreparedStatement 可以向 DBMS 發送 SQL 讓其預編譯然後再傳參數,提高了性能且能防止 SQL 注入攻擊;目前 Nebula Graph 暫無此功能, 所以在本地解析 nGQL 中的佔位符再將參數填充進去,本質上與 Statement 相同。

nebula-java 版本問題

一開始在項目中引入的依賴的 2.0.0 版本,在一次查詢中發現其路徑返回結果與控制檯返回結果不一致,諮詢導師後發現這是這個版本中的 bug,改用最新的 2.0.0-SNAPSHOT 版本。

updateCount 問題

JDBC 接口中一些方法要求返回值是收到此方法影響的數據量 updateCount,但目前服務端沒有 updateCount 統計返回給用戶。假如用戶一條插入語句裏面同時插入多個點或者多條邊,這裏面可能有部分成功,但服務端只會返回告訴用戶失敗了,但是其實用戶可能能查到部分數據。這個 updateCount 按照 0 返回,然後在接口添加註釋說明不支持。

NebulaPool 初始化問題

一開始我是在初始化 NebulaConnection 時初始化 NebulaPool 再獲取 Session,而且搞混了對於 NebulaPool 的配置和對於 Session 的配置。這樣的話用戶每次獲取 Connection 時都會重新初始化 NebulaPool,是不合理的,我提交代碼到 Gitlab 導師 review 後指出了我的錯誤,建議我將 NebulaPool 的 初始化和關閉移到 NebulaDriver 中,再提高默認配置和自定義配置兩種方式初始化 NebulaPool。

後續工作安排

  • 完成接口中應該實現但未實現的方法 ;
  • 完善代碼註釋 ;
  • 完成單元測試 ;
  • 編寫使用說明 .

致謝

這次活動促進了開源軟件的發展和優秀開源軟件社區建設,增加開源項目的活躍度,推進開源生態的發展;感謝 @開源之夏主辦方 爲這次活動提供的平臺與機會。

感謝導師 @laura.ding 在這過程中認真地 review 我 PR 的代碼,予以我細緻的指導讓我知曉自己的不足;感謝 @Nebula Graph 運營小姐姐寄給我的社區周邊,LUCKY!

本文爲鄭東陽同學的原創文章。

深圳 Meetup 活動進行中,本週六你如果想來和 Nebula 技術團隊來個面對面交流的話,記得戳下面鏈接報名喲~

nMeetup 報名鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章