Redis 也支持全文搜索?這也太強了
在 2021 年我就了解到 RediSearch 這個項目,并已經把它用于我的開源項目 newbee-mall-pro 中。
就我的使用體驗來說,簡單場景下,用來平替 Elasticsearch 的使用場景已經足夠。像是 Elasticsearch 中常用中文分詞插件可以用 RediSearch 替代,但是拼音轉中文插件在 RediSearch 中還沒有功能替代,只能通過個人手段處理。
在 newbee-mall-pro 項目中,拼音搜索我是通過先將中文轉拼音后作為拼音字段存入 Redis 中,再通過 RediSearch 查詢拼音字段來實現的。
RediSearch 對于我來說相比 Elasticsearch 的最大優點就是 內存占用非常低,查詢性能也足夠高??。
在我的低配 2 核 4g 內存的服務器上,通過官方提供的 Redis Stack 鏡像部署 Redis 以及自帶模塊 RediSearch 后,內存占用才不到 100m。
相比部署一個 Elasticsearch 起碼需要 1g 內存來說,我更愿意部署 RediSearch。本文大綱如下,
圖片
RediSearch 簡介
RediSearch 是一個 Redis 模塊,為 Redis 提供查詢、二級索引和全文搜索功能。
要使用 RediSearch 的功能,我們需要要先聲明一個 index(類似于 Elasticsearch 的索引)。然后就可以使用 RediSearch 的查詢語言來查詢該索引下的數據。
RediSearch 內部使用壓縮的倒排索引,所以可以用較低的內存占用來實現索引的快速構建。
目前 RediSearch 最新版支持的查詢功能也比較豐富了,除了基本的文本分詞還支持聚合統計、停用詞、同義詞、拼寫檢查、結果排序、標簽查詢、向量相似度查詢以及中文分詞等。
對比 Elasticsearch
基本硬件
數據源
RediSearch 配置
Elasticsearch 配置
版本
索引構建測試
在官方提供的索引構建測試中,RediSearch 用 221 秒的速度超過了 Elasticsearch 的 349 秒,領先 58%,
查詢性能測試
通過數據集導入索引數據后,官方使用運行在專用負載生成器服務器上的 32 個客戶端啟動了兩個詞的搜索查詢。
如下圖所示,RediSearch 的吞吐量達到了 12.5K ops/sec,而 Elasticsearch 的吞吐量只有了 3.1K ops/sec,快了 4 倍。此外 RediSearch 的延遲稍好一些,平均為 8 毫秒,而 Elasticsearch 為 10 毫秒。(ops/sec 每秒操作數)
由此可見,RediSearch 在性能上對比 RediSearch 有比較大的優勢。
目前 RediSearch 已經更新到 2.0+ 版本,根據官方對于 RediSearch 2.0 版本介紹,與 RediSearch 1.6 相比,吞吐量和延遲相關的指標都提高了 2.4 倍。
RediSearch 安裝
對于目前最新的 RediSearch 2.0 版本來說,官方推薦直接使用 redis-stack-server 鏡像進行進行部署,也比較簡單,
docker run -d --name redis-stack-server -p 6379:6379 redis/redis-stack-server:latest
設置登錄
// 設置登錄
docker run -e REDIS_ARGS="--requirepass redis-stack" redis/redis-stack:latest
通過 redis-cli 連接查看 RediSearch 是否安裝了 search 模塊,
redis-cli -h localhost
module list
> MODULE list
...
3) 1) "name"
2) "search"
3) "ver"
4) "20809"
5) "path"
6) "/opt/redis-stack/lib/redisearch.so"
7) "args"
8) 1) "MAXSEARCHRESULTS"
2) "10000"
3) "MAXAGGREGATERESULTS"
4) "10000"
...
索引操作
FT.CREATE 創建索引命令
> FT.CREATE idx:goods on hash prefix 1 "goods:" language chinese schema goodsName text sortable
"OK"
- FT.CREATE:創建索引命令
- idx:goods:索引名稱
- on hash:索引關聯的數據類型,這里指定索引基于 hash 類型的源數據構建
- prefix 1 "goods:":表示索引關聯的 hash 類型源數據前綴是 goods:
- language chinese:表示支持中文語言分詞
- schema goodsName text sortable:表示字段定義,goodsName 表示元數據屬性名,text 表示字段類型 sortable 表示該字段可以用于排序
添加索引時,直接使用 hset 命令添加一個 key 前綴是 "goods:" 的源數據。如下,
hset goods:1001 goodsName 小米手機
hset goods:1002 goodsName 華為手機
FT.SEARCH 查詢索引
> FT.SEARCH idx:goods1 "手機"
1) "2"
2) "goods:1001"
3) 1) "goodsName"
2) "\xe5\xb0\x8f\xe7\xb1\xb3\xe6\x89\x8b\xe6\x9c\xba"
4) "goods:1002"
5) 1) "goodsName"
2) "\xe5\x8d\x8e\xe4\xb8\xba\xe6\x89\x8b\xe6\x9c\xba"
FT.INFO 查詢指定名稱索引信息
> FT.INFO idx:goods
1) "index_name"
2) "idx:goods1"
3) "index_options"
4) (empty list or set)
5) "index_definition"
6) 1) "key_type"
2) "HASH"
3) "prefixes"
4) 1) "goods:"
5) "default_language"
6) "chinese"
7) "default_score"
8) "1"
7) "attributes"
8) 1) 1) "identifier"
2) "goodsName"
3) "attribute"
4) "goodsName"
5) "type"
6) "TEXT"
7) "WEIGHT"
8) "1"
9) "SORTABLE"
...
- FT.INFO 查詢指定名稱的索引信息
FT.DROPINDEX 刪除索引名稱
> FT.DROPINDEX idx:goods1
"OK"
- FT.DROPINDEX 刪除指定名稱索引,不會刪除 hash 類型的源數據
如果需要刪除索引數據,直接使用 del 命令刪除索引關聯的源數據即可。
Java 使用 RediSearch
對于 Java 項目直接選用 Jedis4.0 以上版本就可以使用 RediSearch 提供的搜索功能,Jedis 在 4.0 以上版本自動支持 RediSearch,編寫 Jedis 連接 RedisSearch 測試用例,用 RedisSearch 命令創建如下,
Jedis 創建 RediSearch 客戶端
@Bean
public UnifiedJedis unifiedJedis(GenericObjectPoolConfig jedisPoolConfig) {
UnifiedJedis client;
if (StringUtils.isNotEmpty(password)) {
client = new JedisPooled(jedisPoolConfig, host, port, timeout, password, database);
} else {
client = new JedisPooled(jedisPoolConfig, host, port, timeout, null, database);
}
return client;
}
Jedis 創建索引
Schema schema = new Schema()
.addSortableTextField("goodsName", 1.0)
.addSortableTagField("tag", "|");
IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.HASH)
.setPrefixes("idx:goods")
.setLanguage("chinese"); # 設置支持中文分詞
client.ftCreate(idxName,
IndexOptions.defaultOptions().setDefinition(rule),
schema);
Jedis 添加索引源數據
public boolean addGoodsIndex(String keyPrefix, Goods goods) {
Map<String, String> hash = MyBeanUtil.toMap(goods);
hash.put("_language", "chinese");
client.hset("idx:goods" + goods.getGoodsId(), MyBeanUtil.toMap(goods));
return true;
}
Jedis 中文查詢
public SearchResult search(String goodsIdxName, SearchObjVO searchObjVO, Page<SearchPageGoodsVO> page) {
// 查詢關鍵字
String keyword = searchObjVO.getKeyword();
String queryKey = String.format("@goodsName:(%s)", keyword);
Query q = new Query(queryKey);
String sort = searchObjVO.getSidx();
String order = searchObjVO.getOrder();
// 查詢是否排序
if (StringUtils.isNotBlank(sort)) {
q.setSortBy(sort, Constants.SORT_ASC.equals(order));
}
// 設置中文分詞查詢
q.setLanguage("chinese");
// 設置分頁
q.limit((int) page.offset(), (int) page.getSize());
// 返回查詢結果
return client.ftSearch(goodsIdxName, q);
}
最后聊兩句
RediSearch 是這幾年新出的一個全文搜索引擎,借助于 Redis 的成功,RediSearch 一出場就獲得了較高的關注度。
目前來看,我個人使用 RediSearch 作為 newbee-mall-pro 項目的全文搜索引擎已經夠用了,它有易于安裝、索引占用內存低、查詢速度快等許多優點。不過在對 Redis 集群的支持上,RediSearch 目前只針對 Redis 企業版有解決方案,開源版還沒有,這一點需要告訴大家。
如果想要在生產環境大規模使用,我還是不太建議的。
最后本文使用的 Jedis 操作 RediSearch 相關代碼,都在 newbee-mall-pro 項目的 JedisSearchTest 類有體現。
newbee-mall-pro:https://github.com/wayn111/newbee-mall-