開源之夏
開源軟件供應鏈點亮計劃 - 暑期 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 關係)。
工作流程及類中主要方法分析:
// 用戶首先通過 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 Graph 並掌握其基本使用;
- 閱讀 JDBC:https://download.oracle.com/otn-pub/jcp/jdbc-4_2-mrel2-spec/jdbc4.2-fr-spec.pdf?AuthParam=1628844546_d5f078af230e42dcbe0ba3d183af7495 規範文檔,明確實現要求;
- 學習 nebula-java:https://github.com/vesoft-inc/nebula-java 源碼;
完成以下實現:
遇到的問題及解決方案
如何與數據庫通信的問題:
項目前期過程中不知道如何與數據庫通信,在研究友商 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 技術團隊來個面對面交流的話,記得戳下面鏈接報名喲~