Redis 也支持全文搜索?這也太強(qiáng)了
在 2021 年我就了解到 RediSearch 這個(gè)項(xiàng)目,并已經(jīng)把它用于我的開源項(xiàng)目 newbee-mall-pro 中。
就我的使用體驗(yàn)來說,簡(jiǎn)單場(chǎng)景下,用來平替 Elasticsearch 的使用場(chǎng)景已經(jīng)足夠。像是 Elasticsearch 中常用中文分詞插件可以用 RediSearch 替代,但是拼音轉(zhuǎn)中文插件在 RediSearch 中還沒有功能替代,只能通過個(gè)人手段處理。
在 newbee-mall-pro 項(xiàng)目中,拼音搜索我是通過先將中文轉(zhuǎn)拼音后作為拼音字段存入 Redis 中,再通過 RediSearch 查詢拼音字段來實(shí)現(xiàn)的。
RediSearch 對(duì)于我來說相比 Elasticsearch 的最大優(yōu)點(diǎn)就是 內(nèi)存占用非常低,查詢性能也足夠高??。
在我的低配 2 核 4g 內(nèi)存的服務(wù)器上,通過官方提供的 Redis Stack 鏡像部署 Redis 以及自帶模塊 RediSearch 后,內(nèi)存占用才不到 100m。
相比部署一個(gè) Elasticsearch 起碼需要 1g 內(nèi)存來說,我更愿意部署 RediSearch。本文大綱如下,
圖片
RediSearch 簡(jiǎn)介
RediSearch 是一個(gè) Redis 模塊,為 Redis 提供查詢、二級(jí)索引和全文搜索功能。
要使用 RediSearch 的功能,我們需要要先聲明一個(gè) index(類似于 Elasticsearch 的索引)。然后就可以使用 RediSearch 的查詢語(yǔ)言來查詢?cè)撍饕碌臄?shù)據(jù)。
RediSearch 內(nèi)部使用壓縮的倒排索引,所以可以用較低的內(nèi)存占用來實(shí)現(xiàn)索引的快速構(gòu)建。
目前 RediSearch 最新版支持的查詢功能也比較豐富了,除了基本的文本分詞還支持聚合統(tǒng)計(jì)、停用詞、同義詞、拼寫檢查、結(jié)果排序、標(biāo)簽查詢、向量相似度查詢以及中文分詞等。
對(duì)比 Elasticsearch
基本硬件
數(shù)據(jù)源
RediSearch 配置
Elasticsearch 配置
版本
索引構(gòu)建測(cè)試
在官方提供的索引構(gòu)建測(cè)試中,RediSearch 用 221 秒的速度超過了 Elasticsearch 的 349 秒,領(lǐng)先 58%,
查詢性能測(cè)試
通過數(shù)據(jù)集導(dǎo)入索引數(shù)據(jù)后,官方使用運(yùn)行在專用負(fù)載生成器服務(wù)器上的 32 個(gè)客戶端啟動(dòng)了兩個(gè)詞的搜索查詢。
如下圖所示,RediSearch 的吞吐量達(dá)到了 12.5K ops/sec,而 Elasticsearch 的吞吐量只有了 3.1K ops/sec,快了 4 倍。此外 RediSearch 的延遲稍好一些,平均為 8 毫秒,而 Elasticsearch 為 10 毫秒。(ops/sec 每秒操作數(shù))
由此可見,RediSearch 在性能上對(duì)比 RediSearch 有比較大的優(yōu)勢(shì)。
目前 RediSearch 已經(jīng)更新到 2.0+ 版本,根據(jù)官方對(duì)于 RediSearch 2.0 版本介紹,與 RediSearch 1.6 相比,吞吐量和延遲相關(guān)的指標(biāo)都提高了 2.4 倍。
RediSearch 安裝
對(duì)于目前最新的 RediSearch 2.0 版本來說,官方推薦直接使用 redis-stack-server 鏡像進(jìn)行進(jìn)行部署,也比較簡(jiǎn)單,
docker run -d --name redis-stack-server -p 6379:6379 redis/redis-stack-server:latest
設(shè)置登錄
// 設(shè)置登錄
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 創(chuàng)建索引命令
> FT.CREATE idx:goods on hash prefix 1 "goods:" language chinese schema goodsName text sortable
"OK"
- FT.CREATE:創(chuàng)建索引命令
- idx:goods:索引名稱
- on hash:索引關(guān)聯(lián)的數(shù)據(jù)類型,這里指定索引基于 hash 類型的源數(shù)據(jù)構(gòu)建
- prefix 1 "goods:":表示索引關(guān)聯(lián)的 hash 類型源數(shù)據(jù)前綴是 goods:
- language chinese:表示支持中文語(yǔ)言分詞
- schema goodsName text sortable:表示字段定義,goodsName 表示元數(shù)據(jù)屬性名,text 表示字段類型 sortable 表示該字段可以用于排序
添加索引時(shí),直接使用 hset 命令添加一個(gè) key 前綴是 "goods:" 的源數(shù)據(jù)。如下,
hset goods:1001 goodsName 小米手機(jī)
hset goods:1002 goodsName 華為手機(jī)
FT.SEARCH 查詢索引
> FT.SEARCH idx:goods1 "手機(jī)"
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 刪除指定名稱索引,不會(huì)刪除 hash 類型的源數(shù)據(jù)
如果需要?jiǎng)h除索引數(shù)據(jù),直接使用 del 命令刪除索引關(guān)聯(lián)的源數(shù)據(jù)即可。
Java 使用 RediSearch
對(duì)于 Java 項(xiàng)目直接選用 Jedis4.0 以上版本就可以使用 RediSearch 提供的搜索功能,Jedis 在 4.0 以上版本自動(dòng)支持 RediSearch,編寫 Jedis 連接 RedisSearch 測(cè)試用例,用 RedisSearch 命令創(chuàng)建如下,
Jedis 創(chuàng)建 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 創(chuàng)建索引
Schema schema = new Schema()
.addSortableTextField("goodsName", 1.0)
.addSortableTagField("tag", "|");
IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.HASH)
.setPrefixes("idx:goods")
.setLanguage("chinese"); # 設(shè)置支持中文分詞
client.ftCreate(idxName,
IndexOptions.defaultOptions().setDefinition(rule),
schema);
Jedis 添加索引源數(shù)據(jù)
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) {
// 查詢關(guān)鍵字
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));
}
// 設(shè)置中文分詞查詢
q.setLanguage("chinese");
// 設(shè)置分頁(yè)
q.limit((int) page.offset(), (int) page.getSize());
// 返回查詢結(jié)果
return client.ftSearch(goodsIdxName, q);
}
最后聊兩句
RediSearch 是這幾年新出的一個(gè)全文搜索引擎,借助于 Redis 的成功,RediSearch 一出場(chǎng)就獲得了較高的關(guān)注度。
目前來看,我個(gè)人使用 RediSearch 作為 newbee-mall-pro 項(xiàng)目的全文搜索引擎已經(jīng)夠用了,它有易于安裝、索引占用內(nèi)存低、查詢速度快等許多優(yōu)點(diǎn)。不過在對(duì) Redis 集群的支持上,RediSearch 目前只針對(duì) Redis 企業(yè)版有解決方案,開源版還沒有,這一點(diǎn)需要告訴大家。
如果想要在生產(chǎn)環(huán)境大規(guī)模使用,我還是不太建議的。
最后本文使用的 Jedis 操作 RediSearch 相關(guān)代碼,都在 newbee-mall-pro 項(xiàng)目的 JedisSearchTest 類有體現(xiàn)。
newbee-mall-pro:https://github.com/wayn111/newbee-mall-