Redis 全文搜索是依赖于 Redis 官方提供的 RediSearch 来实现的。RediSearch 提供了一种简单快速的方法对 hash 或者 json 类型数据的任何字段建立二级索引,然后就可以对被索引的 hash 或者 json 类型数据字段进行搜索和聚合操作。
这里我们把被索引的 hash 或者 json 类型数据叫做源数据。
本文大纲如下,
图片
使用体验
简单场景下,用 RediSearch 来平替 Elasticsearch 的使用场景已经足够。像是 Elasticsearch 中常用的查询语法 AND 、OR 、IN 、NOT IN 、> 、< 、= 、like 等,在 RediSearch 中都是支持的。
此外 RediSearch 还支持聚合统计、停用词、文本标记和转义、同义词、标签、排序、向量查询、中文分词等。
就我个人来说,个人项目使用 RediSearch 作为全文搜索引擎已经够用了,它有占用内存低、索引建立快、查询数据性能足够高等优点。
后续发展
就目前官方对 RediSearch 的支持更新来看,
图片
RediSearch 官方更新频率图
最近一次提交记录在 12 月 17 号。
可以看到 RediSearch 的更新频率还是比较高的,而且是官方支持做的模块,不用担心后续无人维护。
虽然 Redis 天生支持分布式集群,但是 RediSearch 对 Redis 集群的支持还不完善,引用官方说明,
图片
官方针对 RediSearch 的集群支持问题,提供了一个 RediSearch 集群版本,但是这个版本只能在 Redis 企业版或者 Redis Cloud 上能使用,开源版还没有,这一点需要告诉大家。
遇到 bug
首先在使用 RediSearch 的过程中,遇到了 bug 并发现 bug 来源于 RediSearch,不要慌,也不要抱怨难用, 毕竟是开源项目,
图片
官方issue列表
大家可以看到 issue 列表中有很多 bug 没有解决。
不过本着开源共进的精神,希望大家发现了 bug 后,第一时间在 RediSearch 官方 github 上提个 issue,方便官方发现并解决问题。
RediSearch Github 仓库地址:https://github.com/RediSearch/RediSearch
下面我给大家用 newbee-mall-pro 项目作为样本,给大家介绍下如何创建一个索引并关联源数据。
newbee-mall-pro 项目地址:https://github.com/wayn111/newbee-mall-pro
添加源数据
在 newbee-mall-pro 项目中,已经将商品数据以 hash 类型存入了 Redis 中,
其中,我们对于 key 名称的定义规则是按照 newbee_mall:goods: + 商品ID。
这里我们的 key 名称定义规则很重要,RediSearch 创建索引会基于 key 名称前缀来生成。
hash 类型的 value 包含属性如下,
- goodsId : 商品 ID,唯一属性,由数据库商品表主键生成
- goodsName : 商品名称
- goodsIntro : 商品简介
- goodsCategoryId : 商品分类 ID,唯一属性,由数据库商品分类表主键生成
- goodsSellStatus : 商品上架状态,0 代表下架,1 代表上架
- sellingPrice : 商品售价
- originalPrice : 商品原价
- tag : 商品标签
在 newbee-mall-pro 中,添加源数据的方法已经写好了,代码逻辑在 JedisSearch.addGoodsListIndex() 方法里,
public boolean addGoodsListIndex(String keyPrefix, List<Goods> list) {
int chunk = 200;
List<List<Goods>> partition = ListUtil.partition(list, chunk);
AbstractPipeline pipelined = client.pipelined();
for (List<Goods> goodsList : partition) {
for (Goods goods : goodsList) {
RsGoodsDTO target = new RsGoodsDTO();
MyBeanUtil.copyProperties(goods, target);
Map<String, String> hash = MyBeanUtil.toMap(target);
// 支持中文
hash.put("_language", Constants.GOODS_IDX_LANGUAGE);
pipelined.hset(keyPrefix + goods.getGoodsId(), hash);
}
}
pipelined.sync();
return true;
}
上诉代码中,其实就是把 list 商品列表以 hash 类型的数据结构写进 Redis 中,并且为了加快写入速度,使用了 Redis 提供的管道操作。
需要注意的就是 hash 类型中新增了一个 _language 字段,用于指定 RediSearch 对于源数据关联的索引,要使用中文分词查询。
建立索引
RediSearch 通过提供一种简单且自动的方式在 Redis hash 类型数据结构上创建二级索引,并且内部极大地简化了这一过程。(最终会出现更多数据结构)
二级索引示意图
如果我们要使用 RediSearch 查询商品 hash 结构里的 goodsName 字段,那么必须要对该字段建立索引。
Jedis 新建索引
所以这里,我给大家介绍下在 newbee-mall-pro 项目中,是如何建立索引的,代码逻辑在 GoodsServiceImpl.syncRs() 方法中,
// 定义索引结构
public boolean syncRs() {
jedisSearch.dropIndex("idx:goods");
Schema schema = new Schema()
.addSortableTextField("goodsName", 1.0)
.addSortableTextField("goodsIntro", 0.5)
.addSortableNumericField("goodsId")
.addSortableNumericField("goodsCategoryId")
.addSortableNumericField("goodsSellStatus")
.addSortableNumericField("sellingPrice")
.addSortableNumericField("originalPrice")
.addSortableTagField("tag", "|");
jedisSearch.createIndex(Constants.GOODS_IDX_NAME, Constants.GOODS_IDX_PREFIX, schema);
}
上述代码中,我们对商品 hash 结构里的下方字段都建立了索引。
- goodsName:文本类型,可排序,设置权重为 1.0
- goodsIntro:文本类型,可排序,设置权重为 0.5
- goodsId:数字类型,可排序
- goodsCategoryId:数字类型,可排序
- goodsSellStatus:数字类型,可排序
- sellingPrice:数字类型,可排序
- originalPrice:数字类型,可排序
- tag:标签类型,可排序,设置分隔符为字符串 |
在 RediSerach 中可以添加的字段类型有 text、numberic、tag 等,可以设置是否排序。
并且还可以设置权重系数,表示该字段已加权。这对于在搜索操作期间为特定字段分配不同的重要性级别非常有用,通常就是在条件筛选完成后的打分排序阶段用于提升或者降低排名。
Redis 中的新建索引语法
当我们把上面的 Jedis 新建索引的代码转换为 Redis 中的语法后,如下
> FT.CREATE idx:goods ON hash PREFIX 1 "newbeemall:goods:" \
SCHEMA goodsName TEXT SORTABLE WEIGHT 1.0 \
SCHEMA goodsIntro TEXT SORTABLE WEIGHT 0.5 \
goodsId NUMERIC SORTABLE \
goodsCategoryId NUMERIC SORTABLE \
goodsSellStatus NUMERIC SORTABLE \
sellingPrice NUMERIC SORTABLE \
originalPrice NUMERIC SORTABLE \
tag TAG SORTABLE SEPARATOR "|"
现在我给大家详细介绍下这条命令:
- FT.CREATE:RediSearch 中索引创建语法。
- idx:goods:指定索引名称,索引名称将在所有键名称中使用,因此请保持简短。
- ON hash:指定索引关联的结构类型。需要注意的是,在 RediSearch 2.0 中仅支持哈希结构,随着 RediSearch 更新,后续有望支持更多数据结构。
- PREFIX 1 "newbeemall:goods:":指定索引的关联源数据的 key 前缀,可以指定多个前缀。
- SCHEMA ...:字段定义,用于定义字段名称、类型、是否排序、权重等。可以定义多个字段。
如果你想了解更多关于 ft.search 的语法以及字段定义相关的只是,可以打开官方文档,
https://redis.io/docs/interact/search-and-query/basic-constructs/schema-definition
Redis 中查询索引定义
在 RediSearch 中要查询已经存在的索引详情也是很简单的,官方提供了 ft.info 索引名称 的语法,用来打印索引详情。
> FT.INFO idx:goods