Redis之RediSearch全文搜索详解

在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。

本文大纲如下

图片[1]-Redis之RediSearch全文搜索详解-不念博客

RediSearch 简介

RediSearch 是一个 Redis 模块,为 Redis 提供查询、二级索引和全文搜索功能。

要使用 RediSearch 的功能,我们需要要先声明一个 index(类似于 Elasticsearch 的索引)。然后就可以使用 RediSearch 的查询语言来查询该索引下的数据。

RediSearch 内部使用压缩的倒排索引,所以可以用较低的内存占用来实现索引的快速构建。

目前 RediSearch 最新版支持的查询功能也比较丰富了,除了基本的文本分词还支持聚合统计、停用词、同义词、拼写检查、结果排序、标签查询、向量相似度查询以及中文分词等。

对比 Elasticsearch

基本硬件

图片[2]-Redis之RediSearch全文搜索详解-不念博客

数据源

图片[3]-Redis之RediSearch全文搜索详解-不念博客

RediSearch 配置

图片[4]-Redis之RediSearch全文搜索详解-不念博客

Elasticsearch 配置

图片[5]-Redis之RediSearch全文搜索详解-不念博客

版本

图片[6]-Redis之RediSearch全文搜索详解-不念博客

索引构建测试

在官方提供的索引构建测试中,RediSearch 用 221 秒的速度超过了 Elasticsearch 的 349 秒,领先 58%,

图片[7]-Redis之RediSearch全文搜索详解-不念博客

查询性能测试

通过数据集导入索引数据后,官方使用运行在专用负载生成器服务器上的 32 个客户端启动了两个词的搜索查询。

如下图所示,RediSearch 的吞吐量达到了 12.5K ops/sec,而 Elasticsearch 的吞吐量只有了 3.1K ops/sec,快了 4 倍。此外 RediSearch 的延迟稍好一些,平均为 8 毫秒,而 Elasticsearch 为 10 毫秒。(ops/sec 每秒操作数)

图片[8]-Redis之RediSearch全文搜索详解-不念博客

由此可见,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 类有体现。

© 版权声明
THE END