在Redis之上实现了一个搜索引擎,但与其他Redis搜索库不同的是,它不使用诸如Sorted Sets之类的内部数据结构。
反向索引存储为特殊的压缩数据类型,可实现快速索引和搜索速度,并减少内存占用。
这还启用了更高级的功能,例如精确的词组匹配和文本查询的数字过滤,这是传统Redis搜索方法无法实现或无法实现的。
官方地址:https://oss.redislabs.com/redisearch/
开源地址:https://github.com/RediSearch/RediSearch
https://hub.docker.com/r/redislabs/redisearch/
$ docker run -p 6379:6379 redislabs / redisearch:latest
客户端:https://github.com/RediSearch/JRediSearch
依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.citydo</groupId>
<artifactId>redisearchspringboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redisearchspringboot</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.redislabs</groupId>
<artifactId>jredisearch</artifactId>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>com.hankcs</groupId>
<artifactId>hanlp</artifactId>
<version>portable-1.7.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
工具类:
package com.citydo.redisearchspringboot;
import com.hankcs.hanlp.seg.common.Term;
import com.hankcs.hanlp.suggest.Suggester;
import com.hankcs.hanlp.tokenizer.NLPTokenizer;
import io.redisearch.AggregationResult;
import io.redisearch.Query;
import io.redisearch.Schema;
import io.redisearch.SearchResult;
import io.redisearch.aggregation.AggregationBuilder;
import io.redisearch.aggregation.SortedField;
import io.redisearch.aggregation.reducers.Reducers;
import io.redisearch.client.Client;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class RedisSearchUtils {
private Client client = null;
/**
* 配置redis搜索
*/
private RedisSearchUtils() {
if (client == null) {
String indexName = "test";
String host = "localhost";
int port = 6379;
String password = "124";
int timeout = 3000;
int poolSize = 0;
if (!StringUtils.isEmpty(password)) {
// redis 设置了密码
client = new Client(indexName, host, port, timeout, poolSize, password);
}else {
// redis 未设置密码
client = new Client(indexName, host, port);
}
}
}
/**
* 创建索引
*/
public void createIndex(String title, String body, String price){
Schema sc = new Schema()
.addTextField(title, 5.0)
.addTextField(body, 1.0)
.addNumericField(price);
client.createIndex(sc, Client.IndexOptions.defaultOptions());
}
/**
* 将文档添加到索引
* fields.put("title", "hello world");
* fields.put("state", "NY");
* fields.put("body", "lorem ipsum");
* fields.put("price", 1337);
* @param fields
*/
public void addDocument(String docId, Map<String, Object> fields){
client.addDocument(docId, fields);
}
/**
* 创建复杂查询
* @param queryString
* @param price
* @return
*/
public SearchResult search(String queryString, String price){
Query query = new Query(queryString)
.addFilter(new Query.NumericFilter(price, 0, 1000))
.limit(0,Integer.MAX_VALUE);
return client.search(query);
}
/**
* 聚合查询
* @param query
* @param price
* @param state
* @param avgprice
* @param k
* @return
*/
public AggregationResult aggregate(String query, String price, String state, String avgprice, String k){
AggregationBuilder builder = new AggregationBuilder(query)
.apply("@".concat(price).concat("/1000"), k)
.groupBy("@".concat(state), Reducers.avg("@".concat(k)).as(avgprice))
.filter("@".concat(avgprice).concat(">=2"))
.sortBy(Integer.MAX_VALUE, SortedField.asc("@".concat(state)));
return client.aggregate(builder);
}
/**
* 分词查询 先分词 在合并
*/
public List<SearchResult> searchParticiple(String queryString, String price){
List<SearchResult> result = new ArrayList<>();
//分词
List<Term> termList = NLPTokenizer.segment(queryString);
termList.stream().forEach(e->{
Query query = new Query(e.word)
.addFilter(new Query.NumericFilter(price, 0, 1000))
.limit(0,Integer.MAX_VALUE);
SearchResult search = client.search(query);
result.add(search);
});
return result;
}
/**
* 出推荐查询
* @param queryString
* @param price
* @return
*/
public List<SearchResult> searchSuggest(String queryString, String price){
List<SearchResult> result = new ArrayList<>();
//推荐
List<String> suggest = new Suggester().suggest(queryString, Integer.MAX_VALUE);
suggest.stream().forEach(e->{
Query query = new Query(e)
.addFilter(new Query.NumericFilter(price, 0, 1000))
.limit(0,Integer.MAX_VALUE);
SearchResult search = client.search(query);
result.add(search);
});
return result;
}
}