本文首發於 Nebula Graph 公衆號 NebulaGraphCommunity,Follow 看大廠圖數據庫技術實踐。正則表達式
1 背景
Nebula 2.0 中已經支持了基於外部全文搜索引擎的文本查詢功能。在介紹這個功能前,咱們先簡單回顧一下 Nebula Graph 的架構設計和存儲模型,更易於下邊章節的描述。數據庫
1.1 Nebula Graph 架構簡介
如圖所示,Storage Service 共有三層,最底層是 Store Engine,它是一個單機版 local store engine,提供了對本地數據的get
/put
/scan
/delete
操做,相關的接口放在 KVStore / KVEngine.h 文件裏面,用戶徹底能夠根據本身的需求定製開發相關 local store plugin,目前 Nebula 提供了基於 RocksDB 實現的 Store Engine。安全
在 local store engine 之上,即是咱們的 Consensus 層,實現了 Multi Group Raft,每個 Partition 都對應了一組 Raft Group,這裏的 Partition 即是咱們的數據分片。目前 Nebula 的分片策略採用了靜態 Hash
的方式,具體按照什麼方式進行 Hash,在下一個章節 schema 裏會說起。用戶在建立 SPACE 時需指定 Partition 數,Partition 數量一旦設置便不可更改,通常來說,Partition 數目要能知足業務未來的擴容需求。網絡
在 Consensus 層上面也就是 Storage Service 的最上層,即是咱們的Storage Interfaces,這一層定義了一系列和圖相關的 API。 這些 API 請求會在這一層被翻譯成一組針對相應 Partition 的 KV 操做。正是這一層的存在,使得咱們的存儲服務變成了真正的圖存儲,不然,Storage Service 只是一個 KV 存儲罷了。而 Nebula 沒把 KV 做爲一個服務單獨提出,其最主要的緣由即是圖查詢過程當中會涉及到大量計算,這些計算每每須要使用圖的 Schema,而 KV 層是沒有數據 Schema 概念,這樣設計會比較容易實現計算下推。數據結構
1.2 Nebula Graph 存儲介紹
Nebula Graph 在 2.0 中,對存儲結構進行了改進,其包含點、邊和索引的存儲結構,接下來咱們將簡單回顧一下 2.0 的存儲結構。經過存儲結構的解釋,你們基本也能夠簡單瞭解 Nebula Graph 的數據和索引掃描原理。架構
1.2.1 Nebula 數據存儲結構
Nebula 數據的存儲包含「點」和「邊」的存儲,「點」 和 「邊」 的存儲均是基於 KV 模型存儲,這裏咱們主要介紹其 Key 的存儲結構,其結構以下所示app
Type
: 1 個字節,用來表示 key 的類型,當前的類型有 vertex、edge、index、system 等。PartID
: 3 個字節,用來表示數據分片 partition,此字段主要用於 partition 從新分佈(balance)時方便根據前綴掃描整個 partition 數據VertexID
: n 個字節, 出邊裏面用來表示源點的 ID, 入邊裏面表示目標點的 ID。Edge Type
: 4 個字節, 用來表示這條邊的類型,若是大於 0 表示出邊,小於 0 表示入邊。Rank
: 8 個字節,用來處理同一種類型的邊存在多條的狀況。用戶能夠根據本身的需求進行設置,這個字段可存放交易時間、交易流水號、或某個排序權重。PlaceHolder
: 1 個字節,對用戶不可見,將來實現分佈式作事務的時候使用。TagID
:4 個字節,用來表示 tag 的類型。
1.2.1.1 點的存儲結構
Type (1 byte) | PartID (3 bytes) | VertexID (n bytes) | TagID (4 bytes) |
---|
1.2.1.2 邊的存儲結構
Type (1 byte) | PartID (3 bytes) | VertexID (n bytes) | EdgeType (4 bytes) | Rank (8 bytes) | VertexID (n bytes) | PlaceHolder (1 byte) |
---|
1.2.2 Nebula 索引存儲結構
- props binary (n bytes):tag 或 edge 中的 props 屬性值。若是屬性爲 NULL,則會填充 0xFF。
- nullable bitset (2 bytes):標識 prop 屬性值是否爲 NULL,共有 2 bytes(16 bit),由此可知,一個 index 最多能夠包含 16 個字段。
1.2.2.1 tag index 存儲結構
Type (1 byte) | PartID (3 bytes) | IndexID (4 bytes) | props binary (n bytes) | nullable bitset (2 bytes) | VertexID (n bytes) |
---|
1.2.2.2 edge index 存儲結構
Type (1 byte) | PartID (3 bytes) | IndexID (4 bytes) | props binary (n bytes) | nullable bitset (2 bytes) | VertexID (n bytes) | Rank (8 bytes) | VertexID (n bytes) |
---|
1.3 借用第三方全文搜索引擎的緣由
由以上的存儲結構推理能夠看出,若是咱們想要對某個 prop 字段進行文本的模糊查詢,都須要進行一個 full table scan
或 full index scan
,而後逐行過濾,由此看來,查詢性能將會大幅降低,數據量大的狀況下,頗有可能還沒掃描完畢就出現內存溢出的狀況。另外,若是將 Nebula 索引的存儲模型設計爲適合文本搜索的倒排索引模型,那將背離 Nebula 索引初始的設計原則。通過一番調研和討論,所謂術業有專攻,文本搜索的工做仍是交給外部的第三方全文搜索引擎來作,在保證查詢性能的基礎上,同時也下降了 Nebula 內核的開發成本。異步
2 目標
2.1 功能
2.0 版本咱們只對 LOOKUP
支持了文本搜索功能。也就是說基於 Nebula 的內部索引,藉助第三方全文搜索引擎來完成 LOOKUP
的文本搜索功能。對於第三方全文引擎來講,目前只使用了一些基本的數據導入、查詢等功能。若是是要作一些複雜的、純文本的查詢計算的話,Nebula 目前的功能還有待完善和改進,期待廣大的社區用戶提出寶貴的建議。目前所支持的文本搜索表達式以下:分佈式
- 模糊查詢
- 前綴查詢
- 通配符查詢
- 正則表達式查詢
2.2 性能
這裏所說的性能,指數據同步性能和查詢性能。工具
- 數據同步性能:既然咱們使用了第三方的全文搜索引擎,那不可避免的是須要在第三方全文搜索引擎中也保存一份數據。通過驗證,第三方全文搜索引擎的導入性能要低於 Nebula 自身的數據導入性能,爲了避免影響 Nebula 自身的數據導入性能,咱們經過異步數據同步的方案來進行第三方全文搜索引擎的數據導入工做。具體的數據同步邏輯咱們將在如下章節中詳細介紹。
- 數據查詢性能:剛剛咱們提到了,若是不借助第三方全文搜索引擎,Nebula 的文本搜索將是一場噩夢。目前
LOOKUP
中經過第三方全文引擎支持了文本搜索,不可避免的性能會慢於 Nebula 原生的索引掃描,有時甚至第三方全文引擎自身的查詢都會很慢,此時咱們須要有一個時效機制來保證查詢性能。即LIMIT
和TIMEOUT
,將在下列章節中詳細介紹。
3 名詞解釋
名稱 | 說明 |
---|---|
Tag | 用於點上的屬性結構,一個 vertex 能夠附加多個 tag,以 tagId 標示。 |
Edge | 相似於 tag,edge 是用於邊上的屬性結構,以 edgetype 標示。 |
Property | tag 或 edge 上的屬性值,其數據類型由 tag 或 edge 的結構肯定。 |
Partition | Nebula Graph 的最小邏輯存儲單元,一個 Storage Engine 可包含多個 partition。Partition 分爲 leader 和 follower 的角色,raftex 保證了 leader 和 follower 之間的數據一致性。 |
Graph space | 每一個 graph space 是一個獨立的業務 graph 單元,每一個 graph space 有其獨立的 tag 和 edge 集合。一個 Nebula Graph 集羣中可包含多個 graph space。 |
Index | 下文中出現的 index 指 Nebula Graph 中點和邊上的屬性索引。其數據類型依賴於 tag 或 edge。 |
TagIndex | 基於 tag 建立的索引,一個 tag 能夠建立多個索引。因暫不支持複合索引,所以一個索引只能夠基於一個 tag。 |
EdgeIndex | 基於 edge 建立的索引。一樣,一個 edge 能夠建立多個索引,但一個索引只能夠基於一個 edge。 |
Scan Policy | index 的掃描策略,每每一條查詢語句能夠有多種索引的掃描方式,但具體使用哪一種掃描方式須要 scan policy 來決定。 |
Optimizer | 對查詢條件進行優化,例如對 WHERE 子句的表達式樹進行子表達式節點的排序、分裂、合併等。其目的是獲取更高的查詢效率。 |
4 實現邏輯
目前咱們兼容的第三方全文搜索引擎是 ElasticSearch,此章節中主要圍繞 ElasticSearch 來進行描述。
4.1 存儲結構
4.1.1 DocID
partId(10 bytes) | schemaId(10 bytes) | encoded_columnName(32 bytes) | encoded_val(max 344 bytes) |
---|
- partId:對應於 Nebula 的 partition ID,當前的 2.0 版本中尚未用到,主要用於從此的查詢下推和 es routing 機制。
- schemaId:對應於 Nebula 的 tagId 或 edgetype。
- encoded_columnName:對應於 tag 或 edge 中的 column name,此處作了一個 md5 的編碼,用以免 ES DocID 中不兼容的字符。
- encoded_val 之因此最大爲 344 個 byte,是由於 prop value 作了一個 base64 的編碼,用於解決 prop 中存在某些 docId 不支持的可見字符的問題。實際的 val 大小被限制在 256 byte。這裏爲何會將長度限制在 256?設計之初,主要的目的是完成 LOOKUP 中的文本搜索功能。基於 Nebula 自身的 index,其長度也有限制,相似傳統關係數據庫 MySQL 同樣,其索引的字段長度建議在 256 個字符以內。所以將第三次搜索引擎的長度也限制在 256 以內。此處並無支持長文本的全文搜索。
- ES 的 docId 最長爲 512 byte,目前有大約 100 個 byte 的保留字節。
4.1.2 Doc Fields
- schema_id:對應於 Nebula 的 tagId 或 edgetype。
- column_id:nebula tag 或 edge 中 column 的編碼。
- value:對應於 Nebula 原生索引中的屬性值。
4.2 數據同步邏輯
Leader & Listener
上邊的章節中簡單介紹了數據異步同步的邏輯,此邏輯將在本章節中詳細介紹。介紹以前,先讓咱們認識一下 Nebula 的 Leader 和 Listener。
- Leader:Nebula 自己是一個可水平擴展的分佈式系統,其分佈式協議是 raft。一個分區(Partition)在分佈式系統中能夠有多種角色,例如 Leader、Follower、Learner 等。當有新數據寫入時,會由 Leader 發起 WAL 的同步事件,將 WAL 同步給 Follower 和 Learner。當有網絡異常、磁盤異常等狀況發生時,其 partition 角色也會隨之改變。由此保證了分佈式數據庫的數據安全。不管是 Leader、Follower,仍是 Learner,都是在 nebula-storaged 進程中控制,其系統參數由配置參數
nebula-stoage.conf
決定。 - Listener:不一樣於 Leader、Follower 和 Learner,Listener 由一個單獨的進程控制,其配置參數由
nebula-stoage-listener.conf
決定。Listener 做爲一個監聽者,會被動的接收來自於 Leader 的 WAL,並定時的將 WAL 進行解析,並調用第三方全文引擎的數據插入 API 將數據同步到第三方全文搜索引擎中。對於 ElasticSearch,Nebula 支持PUT
和BULK
接口。
接下來咱們介紹一下數據同步邏輯:
- 經過 Client 或 Console 插入 vertex 或 edge
- graph 層經過 Vertex ID 計算出相關 partition
- graph 層經過 storageClient 將
INSERT
請求發送到相關 Partition 的 Leader - Leader 解析
INSERT
請求,並將 WAL 同步到 Listener 中 - Listener 會定時處理新同步來的 WAL,並解析 WAL,獲取 tag 或 edge 中字段類型爲 string 的屬性值。
- 將 tag 或 edge 的元數據和屬性值組裝成 ElasticSearch 兼容的數據結構
- 經過 ElasticSearch 的
PUT
或BULK
接口寫入到 ElasticSearch 中。 - 若是寫入失敗,則回到第 5 步,繼續重試失敗的 WAL,直到寫入成功。
- 寫入成功後,記錄成功的 Log ID 和 Term ID,作爲下次 WAL 同步的起始值。
- 回到第 5 步的定時器,處理新的 WAL。
在以上步驟中,若是由於 ElasticSearch 集羣掛掉,或 Listener 進程掛掉,則中止 WAL 同步。當系統恢復後,會接着上次成功的 Log ID 繼續進行數據同步。在這裏有一個建議,須要 DBA 經過外部監控工具實時監控 ES 的運行狀態,若是 ES 長期處於無效狀態,會致使 Listener 的 log 日誌暴漲,而且沒法作正常的查詢操做。
4.3 查詢邏輯
由上圖可知,其文本搜索的關鍵步驟是 「Send Fulltext Scan Request」 → "Fulltext Cluster" → "Collect Constant Values" → "IndexScan Optimizer"。
- Send Fulltext Scan Request: 根據查詢條件、schema ID、Column ID 生成全文索引的查詢請求(即封裝成 ES 的 CURL 命令)
- Fulltext Cluster:發送查詢請求到 ES,並獲取 ES 的查詢結果。
- Collect Constant Values:將返回的查詢結果做爲常量值,生成 Nebula 內部的查詢表達式。例如原始的查詢請求是查詢 C1 字段中以「A」開頭的屬性值,若是返回的結果中包含 「A1」 和 "A2"兩條結果,那麼在這一步,將會解析爲 neubla 的表達式
C1 == "A1" OR C1 == "A2"
。 - IndexScan Optimizer:根據新生成的表達式,基於 RBO 找出最優的 Nebula 內部 Index,並生成最優的執行計劃。
- 在"Fulltext Cluster"這一步中,可能會有查詢性能慢,或海量數據返回的狀況,這裏咱們提供了
LIMIT
和TIMEOUT
機制,實時中斷 ES 端的查詢。
5 演示
5.1 部署外部ES集羣
對於 ES 集羣的部署,這裏再也不詳細介紹,相信你們都很熟悉了。這裏須要說明的是,當 ES 集羣啓動成功後,咱們須要對 ES 集羣建立一個通用的 template,其結構以下:
{ "template": "nebula*", "settings": { "index": { "number_of_shards": 3, "number_of_replicas": 1 } }, "mappings": { "properties" : { "tag_id" : { "type" : "long" }, "column_id" : { "type" : "text" }, "value" :{ "type" : "keyword"} } } }
5.2 部署 Nebula Listener
- 根據實際環境,修改配置參數
nebula-storaged-listener.conf
- 啓動 Listener:
./bin/nebula-storaged --flagfile ${listener_config_path}/nebula-storaged-listener.conf
5.3 註冊 ElasticSearch 的客戶端鏈接信息
nebula> SIGN IN TEXT SERVICE (127.0.0.1:9200); nebula> SHOW TEXT SEARCH CLIENTS; +-------------+------+ | Host | Port | +-------------+------+ | "127.0.0.1" | 9200 | +-------------+------+ | "127.0.0.1" | 9200 | +-------------+------+ | "127.0.0.1" | 9200 | +-------------+------+
5.4 建立 Nebula Space
CREATE SPACE basketballplayer (partition_num=3,replica_factor=1, vid_type=fixed_string(30)); USE basketballplayer;
5.5 添加 Listener
nebula> ADD LISTENER ELASTICSEARCH 192.168.8.5:46780,192.168.8.6:46780; nebula> SHOW LISTENER; +--------+-----------------+-----------------------+----------+ | PartId | Type | Host | Status | +--------+-----------------+-----------------------+----------+ | 1 | "ELASTICSEARCH" | "[192.168.8.5:46780]" | "ONLINE" | +--------+-----------------+-----------------------+----------+ | 2 | "ELASTICSEARCH" | "[192.168.8.5:46780]" | "ONLINE" | +--------+-----------------+-----------------------+----------+ | 3 | "ELASTICSEARCH" | "[192.168.8.5:46780]" | "ONLINE" | +--------+-----------------+-----------------------+----------+
5.6 建立 Tag、Edge、Nebula Index
此時建議字段 「name」 的長度應該小於 256,若是業務容許,建議 player 中字段 name 的類型定義爲 fixed_string 類型,其長度小於 256。
nebula> CREATE TAG player(name string, age int); nebula> CREATE TAG INDEX name ON player(name(20));
5.7 插入數據
nebula> INSERT VERTEX player(name, age) VALUES \ "Russell Westbrook": ("Russell Westbrook", 30), \ "Chris Paul": ("Chris Paul", 33),\ "Boris Diaw": ("Boris Diaw", 36),\ "David West": ("David West", 38),\ "Danny Green": ("Danny Green", 31),\ "Tim Duncan": ("Tim Duncan", 42),\ "James Harden": ("James Harden", 29),\ "Tony Parker": ("Tony Parker", 36),\ "Aron Baynes": ("Aron Baynes", 32),\ "Ben Simmons": ("Ben Simmons", 22),\ "Blake Griffin": ("Blake Griffin", 30);
5.8 查詢
nebula> LOOKUP ON player WHERE PREFIX(player.name, "B"); +-----------------+ | _vid | +-----------------+ | "Boris Diaw" | +-----------------+ | "Ben Simmons" | +-----------------+ | "Blake Griffin" | +-----------------+
6 問題跟蹤與解決技巧
對於系統環境的搭建過程當中,可能某個步驟錯誤致使功能沒法正常運行,在以前的用戶反饋中,我總結了三類可能發生的錯誤,對分析和解決問題的技巧概況以下
- Listener 沒法啓動,或啓動後不能正常工做
- 檢查 Listener 配置文件,確保 Listener 的
IP:Port
不和已有的 nebula-storaged 衝突 - 檢查 Listener 配置文件,確保 Meta 的
IP:Port
正確,這個要和 nebula-storaged 中保持一致 - 檢查 Listener 配置文件,確保 pids 目錄和 logs 目錄獨立,不要和 nebula-storaged 衝突
- 當啓動成功後,由於配置錯誤,修改了配置,再重啓後仍然沒法正常工做,此時須要清理 meta 的相關元數據。對此提供了操做命令,請參考 nebula 的幫助手冊:文檔連接。
- 檢查 Listener 配置文件,確保 Listener 的
- 數據沒法同步到 ES 集羣
- 檢查 Listener 是否從 Leader 端接受到了 WAL,能夠查看
nebula-storaged-listener.conf
配置文件中–listener_path
的目錄下是否有文件。 - 打開 vlog(
UPDATE CONFIGS storage:v=3
),並關注 log 中 CURL 命令是否執行成功,若是有錯誤,多是 ES 配置或 ES 版本兼容性錯誤
- 檢查 Listener 是否從 Leader 端接受到了 WAL,能夠查看
- ES 集羣中有數據,可是沒法查詢出正確的結果
- 一樣打開 vlog (
UPDATE CONFIGS graph:v=3
),關注 graph 的 log,檢查 CURL 命令是什麼緣由執行失敗 - 查詢時,只能識別小寫字符,不能識別大寫字符。多是 ES 的 template 建立錯誤。請對照 nebula 幫助手冊進行建立:文檔連接。
- 一樣打開 vlog (
7 TODO
- 針對特定的 tag 或 edge 創建全文索引
- 全文索引的重構(REBUILD)
交流圖數據庫技術?加入 Nebula 交流羣請先填寫下你的 Nebula 名片,Nebula 小助手會拉你進羣~~
想要和其餘大廠交流圖數據庫技術嗎?NUC 2021 大會等你來交流:NUC 2021 報名傳送門