本文介绍使用docker搭建redis-stack全过程,通过SpringBoot2集成RediSearch对数据的全文检索功能。首先是通过docker安装最新的redis-stack,配置redis相关参数,运行redis-stack,启动Redis加载RediSearch模块,验证RediSearch模块。添加jredisearch依赖,通过jredisearch连接RediSearch进行创建索引、添加数据、更新数据、删除数据和检索数据。

环境说明

jdk 1.8.0_311

redis 7.2.4

springboot 2.7.18

redisson 3.18.0

- jedis 3.7.0

jedis 4.4.8

jredisearch 2.2.0

jredisearch2.2.0默认使用的jedis是3.7.0的版本;但jedis版本和redis版本有具体的映射关系,3.7.0和redis7.x并不匹配,具体版本信息。查看 https://gitee.com/mirrors/jedis




jedis5.x版本,连接redis7的 redisearch,使用密码无法连接,具体原因不太清楚。有大佬了解,请在评论区留言。


搭建RediSearch

通过docker搭建RediSeach,下载redis-stack最新镜像。 Redis-Stack官方链接

# docker-compose配置 [docker-compose-redis-stack.yml]


services:

 redis:

  # redis-stack镜像,生产环境推荐使用redis-stack-server

   image: redis/redis-stack

   container_name: redis

   environment:

    # 添加密码

     REDIS_ARGS: --requirepass redis

   ports:

 # redis端口

       - 6379:6379

 # redis insight端口

       - 8001:8001

   volumes:

 # rdb数据文件存放路径

       - ./data:/data

   restart: on-failure

   logging:

       driver: 'json-file'

       options:

           max-size: '30m'

           max-file: '1'  

 

- redis7配置文件中requirepass的说明, 设置密码后 redisson3.18.0无法正常连接redis,不设置密码,通过protected-mode yes配置,只允许本机访问。


使用jedis4.4.8版本的JedisPool连接redisearch,可以设置密码;不再使用redisson3.18.0连接,redis不设置密码裸奔,还是有点风险。


redis-stack和redis-stack-server的区别

redis/redis-stack包含 redis-stack-server和 redis insight。此容器最适合本地开发,因为您可以使用嵌入式 redis insight 来可视化数据。

redis/redis-stack-server仅提供 redis-stack-server。此容器最适合生产部署。

redis-stack-server中包含RedisJSON和RediSearch模块。


运行redis-stack

# -f filename  指定文件

# up 启动

# -d 后台运行

docker-compose -f docker-compose-redis-stack.yml up -d

 

验证redis

通过docker redis-cli访问redis

docker exec -it redis redis-cli




验证RediSearch

在redis-cli中 输入命令 module list,可以查看到安装了RediSearch和RedisJSON模块




在redis-cli中 输入命令 FT._LIST,可以查看索引列表




SpringBoot2集成RediSearch

操作RediSearch的库有:


Redisson


redis-om-spring


jredisearch


Redisson 3.21.x版本才支持RediSearch,同时Redisson版本对SpringBoot版本的支持不佳,只能放弃使用Redisson。Redisson版本支持


redisson-spring-data

module name Spring Boot

version

redisson-spring-data-16 1.3.y

redisson-spring-data-17 1.4.y

redisson-spring-data-18 1.5.y

redisson-spring-data-2x 2.x.y

redisson-spring-data-3x 3.x.y

redis-om-spring 要求jdk11,当前项目使用的是jdk8,无法使用,如果使用jdk11,可以使用这个库。

RedisOM是Redis官方推出的ORM框架,是对Spring Data Redis的扩展。由于Redis目前已经支持原生JSON对象的存储,之前使用RedisTemplate直接用字符串来存储JOSN对象的方式明显不够优雅。通过RedisOM我们不仅能够以对象的形式来操作Redis中的数据,而且可以实现搜索功能!


<dependency>

<groupId>com.redis.om</groupId>

<artifactId>redis-om-spring</artifactId>

<version>0.5.0</version>

</dependency>

 

JRediSearch,是基于jedis封装的RediSearch库,对jdk和springboot版本没有要求。

JRediSearch 是 RediSearch 的一个 Java 客户端库。JRediSearch 包含一个抽象化 RediSearch Redis 模块的 API 的 Java 库,并在 Redis 内部实现了强大的 in-memory 搜索引擎。


当前想选择了JRediSearch 2.2.0版本进行操作RediSearch, JRediSearch依赖了Jedis 3.7.0, 这里需要单独引用一下jedis。


# 添加依赖


<dependency>

   <groupId>com.redislabs</groupId>

   <artifactId>jredisearch</artifactId>

   <version>2.2.0</version>

# 排除包内引用的jedis, 包内引用的是3.7.0,但是无法连接redis7的redisearch,在下面单独引用 jedis 4.4.8

   <exclusions>

       <exclusion>

           <artifactId>jedis</artifactId>

           <groupId>redis.clients</groupId>

       </exclusion>

   </exclusions>

</dependency>


<dependency>

   <groupId>redis.clients</groupId>

   <artifactId>jedis</artifactId>

   <version>4.4.8</version>

</dependency>


 

spring-boot-starter-data-redis中排除一下lettuce,因为新的springboot使用lettuce替代了jedis进行连接redis, 使用jredisearch时,会造成 JedisPool 无法找到。

<dependency>

   <groupId>org.springframework.boot</groupId>

   <artifactId>spring-boot-starter-data-redis</artifactId>

   <exclusions>

       <exclusion>

           <groupId>io.lettuce</groupId>

           <artifactId>lettuce-core</artifactId>

       </exclusion>

   </exclusions>

</dependency>

 

操作RediSearch

示例代码

重写jredisarch中Client类,类中使用的BinaryClient类,在jedis4.4.8中不存在此类,需要更换为Collection,其他代码跟原始代码Client相同:


public class JedisClient implements io.redisearch.Client {

   private Connection sendCommand(Jedis conn, ProtocolCommand provider, String... args) {

       Connection client = conn.getClient();

       client.sendCommand(provider, args);

       return client;

   }


   private Connection sendCommand(Jedis conn, ProtocolCommand provider, byte[]... args) {

       Connection client = conn.getClient();

       client.sendCommand(provider, args);

       return client;

   }

}


 

使用JedisPool初始化客户端:



import io.redisearch.Document;

import io.redisearch.SearchResult;

import io.redisearch.Query;

import io.redisearch.Schema;




// 初始化jedis连接池  

JedisPool pool = new JedisPool(new GenericObjectPoolConfig<>(),

              "127.0.0.1",

              6379,

              2000,

              "redis",

              0

      );

// 索引名称    

String indexName = "article";

// 初始化客户端

JedisClient client = new JedisClient(indexName,pool);

 

为索引定义并创建一个 schema:



// 中文语言

String LANGUAGE_NAME = "chinese";

Schema sc = new Schema()

               .addTextField("title", 5.0)

               .addTextField("body", 1.0)

               .addNumericField("price");




// IndexDefinition requires RediSearch 2.0+

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

     .setPrefixes(new String[] {"item:", "product:"})

     // 设置语言,不设置,中文无法查找到

     setLanguage(LANGUAGE_NAME );


client.createIndex(sc, Client.IndexOptions.defaultOptions().setDefinition(def));

 

为索引添加文档:


Map<String, Object> fields = new HashMap<>();

fields.put("title", "hello world");

fields.put("state", "NY");

fields.put("body", "lorem ipsum");

fields.put("price", 1337);


// Prior to RediSearch 2.0+ the addDocument has to be called

client.addDocument("item", fields);


// document方式

client.addDocument(new Document("item", fields));

 

查找索引:


// 中文语言

String LANGUAGE_NAME = "chinese";

// Creating a complex query

Query q = new Query("hello world")

    // 设置语言,不设置,中文无法查找到

    .setLanguage(LANGUAGE_NAME)

                   // 设置分页

                   .limit(0,5);


// actual search

SearchResult res = client.search(q);


// aggregation query

AggregationBuilder r = new AggregationBuilder("hello")

 .apply("@price/1000", "k")

 .groupBy("@state", Reducers.avg("@k").as("avgprice"))

 .filter("@avgprice>=2")

 .sortBy(10, SortedField.asc("@state"));

 

AggregationResult res = client.aggregate(r);