序言

各位觀眾老爺們好,很榮幸能邀請到我司虛擬化平台組的研發同學 Vici 做一些在產品中使用 redisearch 的心得分享(加戲)。

「搜索」是很多產品中無法繞開的一個功能模塊,smartx 的虛擬化管理平台也提供了對主機,存儲等對象的查詢功能。近期的研發工作中,我們引入了 redisearch 來優化查詢效果。

希望通過閱讀本文,能讓研發同學更加深入的了解 redisearch 模塊一種高效經濟的文本檢索方案。

全文大約 7000 字,閱讀需要 10-15分鐘。

0. 為什麼是 Redisearch

在引入全文檢索之前,我們使用 mongoDB 內置的 正則匹配搜索文本。為了提升性能(正則搜索大部分情況下無法使用索引。)和引入語義搜索功能 (如搜索虛擬機 描述文檔的內容), 正則匹配的方式已經遠遠不夠了。我們優先考慮了 mongodb 自帶的 text 索引和 search 關鍵字,考慮到當前客戶環境的mongodb 版本較低(2.6),升級會引入額外成本。同時 text索引會給客戶環境帶來額外的內存壓力。所以我們希望能找到一種獨立的全文檢索方案。

業界常用 elasticsearch 和 lucene 方案都對 JVM 的運行時內存有著最低限額,筆者在選型測試時優先測試了 elasticearch ,其運行時大小推薦 2G 以上的內存空間,並且需要額外的磁碟空間做持久化存儲。

相對照的,我們還測試了 redisearch 的內存佔用情況和中文分詞功能的可用性。

如下結構的測試數據 比較貼近我們的業務搜索場景。

相同數據量的情況下(4-30W),redis 可以比 Elasticearch 節省 約75%的內存佔用。

而且,ES 的一些對嵌套文檔索引和查詢的高級功能也不是需求的業務特性

中文的分片語件(friso)可以滿足基本需求,模塊化的設計也給我們後續的優化開發提供了餘地。

1. Redis Modules

Redis Modules 是 redis 4.0 引入的一種擴展機制,用戶可以通過實現 redis module 提供的 C api 介面為 redis 服務添加定製化功能。 redisLab 也希望籍此來規範 redis 社區的 ecosystem 實現。

redis module 本身的版本獨立於redis,並且以編譯成動態載入庫 .so 文件的方式 release, 不同版本的 redis 可以 load 同一版本 module.so 文件。

redis 提供了兩種載入方式。可以通過 在 conf 文件中 加入 loadmodule /path/to/mymodule.so ,也可以在 redis-cli 中 使用命令 MODULE LOAD /path/to/panda.so 動態載入,MODULE UNLOAD 卸載。

2. redisearch

Redis 社區兩位核心開發@dvirsky和@mnunberg 在 2016 年啟動了 redisearch 項目,旨在為 redis 提供全文索引相關的搜索功能(沒錯, 很像elasticsearch )。 截至2019年1月, redisearch 的最新發布版本是 v1.4.2 。

特性

  • 基於文檔的全文索引。
  • 高性能增量索引。
  • 支持文檔評分,文檔欄位(field) 權重機制。
  • 支持布爾複雜查詢。
  • 支持自動補全 (未測試)。
  • 基於 snowball 的詞幹分析,多語言支持。使用 friso 支持中文分詞。
  • utf-8 字符集支持。
  • redis 數據持久化支持。
  • 自定義評分機制。

相比 elasticsearch,redisearch 在這些「主營業務」上其實沒有什麼優勢,不過麻雀雖小卻也五臟俱全。內存存儲,輕量,文檔的實時 index & search 特性才是我們選擇它的原因。

有些遺憾的是,redisearch 分散式支持功能只在其企業版中可用。

在rediserch 的 的文檔說明中,說明了其主要功能的實現都沒有使用 redis的原生數據結構。

以全文檢索使用到的倒排索引為例,對於常見的一些vm數據:

{"_id": "1a2c3d343e", "name": "我的測試vm", ...},
{"_id": "1a2c3d343d", "name": "vm-全文檢索", },
{"_id": "1a2c3d343f", "name": "測試vm", }

進行分詞後的倒排索引簡單結構類似:

結構如下:

{text: "我", ids: ["1a2c3d343e",]},
{text: "vm", ids: ["1a2c3d343e","1a2c3d343d","1a2c3d343f",]},
{text: "全文", ids: ["1a2c3d343d",]},
{text: "檢索", ids: ["1a2c3d343d",]},
{text: "測試", ids: ["1a2c3d343e","1a2c3d343f",]}

在 redis 的 hashmap 基礎上就可以很容易實現倒排索引的結構。redisearch 倒排索引除了實現了基礎功能外,還引入了內存管理等優化功能。如果有興趣可以閱讀源碼中的 src/inverted_index.c 部分。

快速上手

Redislab 社區推薦測試時使用 docker 快速啟動驗證環境。

docker run -p 6666:6379 redislabs/redisearch:latest

這裡我們同時需要 一個 redis-cli 來進行功能驗證:

linux or Darwin:

cd /tmp
wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make
cp src/redis-cli /usr/local/bin/
chmod 755 /usr/local/bin/redis-cli

進入交互環境

redis-cli

或者硬核一點, 使用 linux 自帶的 nc 命令 與 redisearch 交互:

nc -v 127.0.0.1 6379

檢查 modules 是否成功載入

127.0.0.1:6666> MODULE list
1) 1) "name"
2) "ft"
3) "ver"
4) (integer) 10405

如果返回數組中存在 "ft" , 則表明 redisearch 已經成功載入。

創建索引 index

Redisearch 的索引概念 與elasticsearch 的 index 類似,表示某一類文檔資源單元。

這裡我們定義了一個 SMARTX_VM 索引,其中存儲的文檔 包含 了 name 和 desc 兩個 類型為 TEXT 的field。

127.0.0.1:6666> FT.CREATE SMARTX_VM SCHEMA name TEXT WEIGHT 5.0 desc TEXT

查看索引信息

127.0.0.1:6666> FT.INFO SMARTX_VM

FT.INFO可以查詢 index 的metadata. 包括 索引名,欄位信息 fields info,已索引文檔數, 內存用量等。

向索引添加文檔

向之前創建的索引 SMARTX_VM 存儲文檔:

{
"id": vm-2019030211110001,
"name": "測試虛擬機-01",
"desc": "我在北京故宮也能吃炸雞"
}
127.0.0.1:6666> FT.ADD SMARTX_VM vm-2019030211110001 1.0 LANGUAGE "chinese" FIELDS
name "測試虛擬機-01" desc "我在北京故宮也能吃炸雞"

LANGUAGE "chinese" 參數 表示 使用 中文分詞器 處理文本。默認為英文.

文檔檢索

這裡直接搜索 「故宮炸雞」 是檢索不到的,因為沒有指定合理的文本分詞器

127.0.0.1:6666> FT.SEARCH SMARTX_VM "故宮炸雞"
1) (integer) 0

檢索時指定語言類型

127.0.0.1:6666> FT.SEARCH SMARTX_VM "故宮炸雞" LANGUAGE "chinese"
1) (integer) 1
2) "vm-2019030211110001"
3) 1) "name"
2) "xe6xb5x8bxe8xafx95xe8x99x9axe6x8bx9fxe6x9cxba-01"
3) "desc"
...

可以看到已經返回了我們想要的結果。

進階

存儲結構

redisearch的 索引文檔存儲復用了 redis 的 HASH table 類型。

通過redis 的 HGETALL ,HGET 命令可以查詢文檔內容。 比如查詢上文示例中添加的文檔

127.0.0.1:6666> HGETALL vm-2019030211110001

需要注意的是 redisarch 模塊並沒有做文檔的資源隔離,當存在多個索引,不同索引的文檔 id 出現重複時,相同 id 的文檔將會合併。合理使用這一特性 可以節省內存空間。如果業務層需要嚴格的資源隔離,則可以使用 redis 的 多個 db,或者為 不同索引的 id 添加 prefix。

接入姿勢

在 smartx 虛擬化管理平台,我們將 redisearch 置於 我們的 mongodb 業務資料庫之後, 並且實現了一個 mongo 操作日誌監聽工具 goose,將 mongodb集群中業務方需要檢索的文檔欄位實時同步到 redisearch 中。

用戶的檢索查詢請求會首先從redisearch 獲取到匹配文檔的id,再從mongoDB 中查詢。流程圖如下:

在mongodb的副本集(replicaSet) 部署中,各個副本節點與主節點使用 oplog 機制同步, oplog 會記錄mongo客戶端的操作歷史,副本節點可以監聽主節點的oolog 並重放就可以完成 同步數據的效果。

oplog的 prototype:

type OpLog struct {
Id // mongo doc 的 objectId
Operation // 操作數: 增,改,刪
Namespace // db.collection
Timestamp // 操作時間戳
Doc // 文檔內容
}

github 社區的開源同步工具 mongo-connector,就是通過監聽 oplog的方式從mongo中做持久化數據同步,然後再根據disparcher 介面將數據分發到 相應的數據組件: elasticsearch,mongo,etc...

Mongo-connector 雖然提供了完備的mongo同步框架,但同步到 redisearch 相關的 dispatcher 仍然需要自行實現。同時其數據 filter 模塊也不支持 field 粒度的查詢。

出於上述考慮,為了能夠更加精細化的做數據篩選同步和兼容產品後端服務,筆者實現了一個可以定製篩選條件的 oplog 同步中間件: goose(鵝)。

產品中的其他服務可以向 goose 註冊篩選條件(以toml文件的方式):

# sync VOLUME => "ELF_VOLUME"
[[sync-rule]]
db = "resources"
collection = "resource"
index = "VOLUME" # also an unique id of rule
id = "_id"
filter = { super_type = "KVM_VOLUME_SUPER", status = "created"}
fields = ["name", "description"]

形如上述配置,goose會從 mongo 的 resouce 中篩選一些滿足 supertype = "KVMVOLUME_SUPER" & status = "created" 的文檔,將文檔中的 name 和 decription 同步到 redisearch 的 VOLUME 集合中,完成從監聽 --> 同步 --> 建立全文索引的過程。

相關數據的處理和流向關係用下圖表示:

高可用

由於 redisearch 的開源版本不支持redis的集群模式部署, 該查詢服務的高可用部分交由goose 和產品集群的 服務註冊和代理服務合作。

我們在每個 mongo節點都部署了針對單節點的 數據同步和redisearch服務 並暴露本地6666埠, 如果goose服務健康, goose 會把當前節點註冊到集群的 反向代理和負載均衡 服務中。其他服務可以訪問集群任意節點的11900埠進行 全文檢索查詢服務。

開發

在實際開發中,筆者通讀了 redisearch python 和 golang 的客戶端實現,python-cli 作為社區推薦的客戶端實現,目前覆蓋封裝了了1.4 版本之前的大部分功能, 可以滿足開發使用要求。但是 golang 版本比較滯後,很多關鍵命令(如 ALTER, SUG* 等)沒有封裝,連接池復用等常用客戶端特性均未實現,如有golang 使用需求,建議使用 redigo 項目自行實現。

license

RedisLab 在 2018 年 7月 將其大部分項目的 license 更換到了 Apache License with Commons Clause。

在 2019年 2月份,又替換為 Redis Source Available License Agreement。

為避免商業糾紛,我們也在第一時間聯繫了Redis社區,了解 license 被修改後相關產品的使用邊界。

根據反饋結果,可以確認 redis 依然可以作為一種基礎設施內存資料庫存在於產品中。但是將redis作為產品的賣點,或封裝後直接售賣 是不被允許的。

寫在最後

Redisearch 是一個高效,功能完備的內存存儲的高性能全文檢索組件, 十分適合應用在數據量適中, 內存和存儲空間有限的環境。藉助數據同步手段,我們可以很方便的將redisearch 結合到現有的數據存儲中, 進而向產品提供 全文檢索, 自動補全等服務優化功能。

相關的數據同步組件部分會儘快提供與公司業務層解耦的開源版本回饋社區,敬請關注。

了解更多

1.2019 redis license agreement

2.redisearch doc

作者介紹

Vici,SmartX 研發工程師。SmartX 擁有國內最頂尖的分散式存儲和超融合架構研發團隊,是國內超融合領域的技術領導者。

推薦閱讀:

相关文章