RediSearch+SpringBoot全文検索を実現


一、RediSearch紹介:


Redis上では検索エンジンが実装されているが、他のRedis検索ライブラリとは異なり、Sorted Setsのような内部データ構造は使用されない.逆インデックスストレージは特殊な圧縮データ型であり、高速インデックスと検索速度を実現し、メモリ消費量を削減します.これにより、正確なフレーズマッチングやテキストクエリのデジタルフィルタリングなど、より高度な機能も有効になります.これは、従来のRedis検索方法では実現できないか、実現できないものです.

二、RediSearchオープンソースアドレス:


公式住所:https://oss.redislabs.com/redisearch/
オープンソースアドレス:https://github.com/RediSearch/RediSearch

三、インストールコマンド:


https://hub.docker.com/r/redislabs/redisearch/
$ docker run -p 63796379 redislabs / redisearch:latest

四、Springboot集積RediSearch:


クライアント: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;
    }


}