前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >可能是全网第一个使用RediSearch实战的项目

可能是全网第一个使用RediSearch实战的项目

原创
作者头像
wayn
修改2022-03-29 23:43:45
2.3K0
修改2022-03-29 23:43:45
举报
文章被收录于专栏:wayn的程序开发

实战项目地址newbeemall,集成RediSearch,代码开源已上传,支持的话可以点个star😁

RediSearch 是基于 Redis 开发的支持二级索引、查询引擎和全文搜索的应用程序。在2.0的版本中,简单看下官网测试报告:

索引构建

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

indixing-results.png
indixing-results.png
查询性能

数据集建立索引后,我们使用运行在专用负载生成器服务器上的 32 个客户端启动了两个词的搜索查询。如下图所示,RediSearch 的吞吐量达到了 12.5K ops/sec,而 Elasticsearch 的吞吐量达到了 3.1K ops/sec,快了 4 倍。此外,RediSearch 的延迟稍好一些,平均为 8 毫秒,而 Elasticsearch 为 10 毫秒。

(ops/sec每秒操作数)

querying-results.png
querying-results.png

由此可见,新的RediSearch在性能上对比RediSearch较有优势,此外对中文项目来说对于中文的支持必不可少,RediSearch也在官网文档特意列出了支持中文,基于frisoC语言开发的中文分词项目。

image.png
image.png

一、RediSearch安装

Docker安装最新版

代码语言:txt
复制
docker run -p 6379:6379 redislabs/redisearch:latest

通过redis-cli连接查看RediSearch是否安装成功

代码语言:txt
复制
1、redis-cli -h localhost 

2、module list

82.157.141.70:16789> MODULE LIST 



    1) 1) "name"

       2) "search" # 查看是否包含search模块

       3) "ver"

       4) (integer) 20210

    2) 1) "name"

       2) "ReJSON" # 查看是否包含ReJSON模块

       3) "ver"

       4) (integer) 20007

二、客户端集成

对于Java项目直接选用Jedis4.0版本就可以,Jedis在4.0版本自动支持RediSearch,编写Jedis连接RedisSearch测试用例,用RedisSearch命令创建如下:

代码语言:txt
复制
FT.CREATE idx:goods on hash prefix 1 "goods:" language chinese schema goodsName text sortable

// FT.CREATE 创建索引命令

// idx:goods 索引名称

// on hash 索引数据基于hash类型源数据构建

// prefix 1 "goods:" 表示要创建索引的源数据前缀匹配规则

// language chinese 表示支持中文语言分词

// schema 表示字段定义,goodsName元数据属性名 text字段类型 sortable自持排序



FT.INFO idx:goods 

// FT.INFO 查询指定名称索引信息



FT.DROPINDEX idx:goods 

// FT.DROPINDEX 删除指定名称索引,不会删除源数据



添加索引时,使用hset命令添加索引源数据

删除索引时,使用del命令删除索引源数据
  1. Jedis创建RediSearch客户端
代码语言:txt
复制
@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;

}
  1. Jedis创建索引
代码语言:txt
复制
@Test

public void createIndex() {

    System.out.println("begin");

    Schema schema = new Schema()

            .addSortableTextField("goodsName", 1.0)

            .addSortableTextField("goodsIntro", 0.5)

            .addSortableTagField("tag", "|");

    jedisSearch.createIndex(idxName, "goods", schema);

    System.out.println("end");

}



/\*\*

 \* 创建索引

 \*

 \* @param idxName 索引名称

 \* @param prefix  要索引的数据前缀

 \* @param schema  索引字段配置

 \*/

public void createIndex(String idxName, String prefix, Schema schema) {

    IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.HASH)

            .setPrefixes(prefix)

            .setLanguage(Constants.GOODS\_IDX\_LANGUAGE); # 设置支持中文分词

    client.ftCreate(idxName,

            IndexOptions.defaultOptions().setDefinition(rule),

            schema);

}
  1. Jedis添加索引源数据
代码语言:txt
复制
/\*\*

 \* 添加索引数据

 \*

 \* @param keyPrefix 要索引的数据前缀

 \* @param goods     商品信息

 \* @return boolean

 \*/

public boolean addGoodsIndex(String keyPrefix, Goods goods) {

    Map<String, String> hash = MyBeanUtil.toMap(goods);

    hash.put("\_language", Constants.GOODS\_IDX\_LANGUAGE);

    client.hset(keyPrefix + goods.getGoodsId(), MyBeanUtil.toMap(goods));

    return true;

}
  1. Jedis中文查询
代码语言:txt
复制
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(Constants.GOODS\_IDX\_LANGUAGE);

    // 查询分页

    q.limit((int) page.offset(), (int) page.getSize());

    // 返回查询结果

    return client.ftSearch(goodsIdxName, q);

}

三、项目实战

  1. 引入Jedis4.0
代码语言:txt
复制
<jedis.version>4.2.0</jedis.version>

<!-- jedis -->

<dependency>

    <groupId>redis.clients</groupId>

    <artifactId>jedis</artifactId>

    <version>${jedis.version}</version>

</dependency>
  1. newbeemall项目后台商品管理中添加同步按钮
image.png
image.png

编写商品全量同步按钮,为了加快同步速度,通过多线程同步

代码语言:txt
复制
// 同步商品到RediSearch

public boolean syncRs() {

    jedisSearch.dropIndex(Constants.GOODS\_IDX\_NAME);

    Schema schema = new Schema()

            .addSortableTextField("goodsName", 1.0)

            .addSortableTextField("goodsIntro", 0.5)

            .addSortableNumericField("goodsId")

            .addSortableNumericField("sellingPrice")

            .addSortableNumericField("originalPrice")

            .addSortableTagField("tag", "|");

    jedisSearch.createIndex(Constants.GOODS\_IDX\_NAME, "goods:", schema);

    List<Goods> list = this.list();

    jedisSearch.deleteGoodsList(Constants.GOODS\_IDX\_PREFIX);

    return jedisSearch.addGoodsListIndex(Constants.GOODS\_IDX\_PREFIX, list);

}

/\*\*

 \* 同步商品索引

 \*

 \* @param keyPrefix 要索引的数据前缀

 \* @return boolean

 \*/

public boolean addGoodsListIndex(String keyPrefix, List<Goods> list) {

    int chunk = 200;

    int size = list.size();

    int ceil = (int) Math.ceil(size / (double) chunk);

    // 多线程同步

    List<CompletableFuture<Void>> futures = new ArrayList<>(4);

    for (int i = 0; i < ceil; i++) {

        int toIndex = (i + 1) \* chunk;

        if (toIndex > size) {

            toIndex = i \* chunk + size % chunk;

        }

        List<Goods> subList = list.subList(i \* chunk, toIndex);

        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.supplyAsync(() -> subList).thenAccept(goodsList -> {

            for (Goods goods : goodsList) {

                Map<String, String> hash = MyBeanUtil.toMap(goods);

                hash.put("\_language", Constants.GOODS\_IDX\_LANGUAGE);

                client.hset(keyPrefix + goods.getGoodsId(), MyBeanUtil.toMap(goods));

            }

        });

        futures.add(voidCompletableFuture);

    }

    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

    return true;

}
  1. 修改商品页面搜索接口
代码语言:txt
复制
@GetMapping("/search")

public String rsRearch(SearchObjVO searchObjVO, HttpServletRequest request) {

    Page<SearchPageGoodsVO> page = getPage(request, Constants.GOODS\_SEARCH\_PAGE\_LIMIT);

    ...

    // RediSearch中文搜索

    SearchResult query = jedisSearch.search(Constants.GOODS\_IDX\_NAME, searchObjVO, page);

    ...

    return "mall/search";

}
  1. 查看搜索结果中包含"小米"、"手机"两个单独分词
image.png
image.png

四、总结

通过以上实战项目,使用RediSearch是可以满足基本中文分词需求

image.png
image.png

高级用法聚合查询、结果高亮、停用词、扩展API、拼写更正、自动补全等可以在官网了解。

最后贴一下实战项目地址newbeemall,集成RediSearch,代码开源已上传

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 索引构建
  • 查询性能
  • 一、RediSearch安装
  • 二、客户端集成
  • 三、项目实战
  • 四、总结
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档