IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> RedisJson和RedisSearch探究(三) -> 正文阅读

[大数据]RedisJson和RedisSearch探究(三)

这一篇,讨论RedisSearch如何通过java进行操作。

RedisSearch是一个搜索的工具,在搜索的时候,它会先将要搜索的内容进行分词处理,创建索引的时候也会分词。对于英文来说,分词比较简单,基本上空格和标点符号就可以,但是中文分词相对复杂一些,因为中文不能通过空格进行简单的分词。

现在有各种不同的中文分词器,比如jieba,IK已经RedisSearch使用的分词器:friso。

friso在gitee上可以找到:https://gitee.com/lionsoul/friso

friso的具体使用,可以参考gitee中的介绍。

我在使用之前,先将friso和jieba进行了简单的分词效果对比,发现其分词效果比起jieba还是差一点,此处并无对作者不敬之意。并且friso目前也只是出于维护状态,版本已经五年没有更新,friso的主要作者在维护一个新的分词器,有兴趣可以移步观摩一下:

https://gitee.com/lionsoul/jcseg#jcseg%E6%98%AF%E4%BB%80%E4%B9%88

1. 自定义Friso词库

因为我要做中文分词,friso默认的字典并不适合,所以需要自定义字典。

RedisSearch将friso直接打包,如果想自定义,只能通过更改friso的初始化配置来实现。

可以先看下默认的配置:

127.0.0.1:6379> FT.CONFIG get *
?1) 1) EXTLOAD
??? 2) (nil)
?2) 1) SAFEMODE
??? 2) true
?3) 1) CONCURRENT_WRITE_MODE
??? 2) false
?4) 1) NOGC
??? 2) false
?5) 1) MINPREFIX
??? 2) 2
?6) 1) FORKGC_SLEEP_BEFORE_EXIT
??? 2) 0
?7) 1) MAXDOCTABLESIZE
??? 2) 1000000
?8) 1) MAXSEARCHRESULTS
??? 2) 1000000
?9) 1) MAXAGGREGATERESULTS
??? 2) unlimited
10) 1) MAXEXPANSIONS
??? 2) 200
11) 1) MAXPREFIXEXPANSIONS
??? 2) 200
12) 1) TIMEOUT
??? 2) 500
13) 1) INDEX_THREADS
??? 2) 8
14) 1) SEARCH_THREADS
??? 2) 20
15) 1) FRISOINI
??? 2) nil
16) 1) ON_TIMEOUT
??? 2) return
17) 1) GCSCANSIZE
??? 2) 100
18) 1) MIN_PHONETIC_TERM_LEN
??? 2) 3
19) 1) GC_POLICY
??? 2) fork
20) 1) FORK_GC_RUN_INTERVAL
??? 2) 30
21) 1) FORK_GC_CLEAN_THRESHOLD
??? 2) 100
22) 1) FORK_GC_RETRY_INTERVAL
??? 2) 5
23) 1) _MAX_RESULTS_TO_UNSORTED_MODE
??? 2) 1000
24) 1) UNION_ITERATOR_HEAP
??? 2) 20
25) 1) CURSOR_MAX_IDLE
??? 2) 300000
26) 1) NO_MEM_POOLS
??? 2) false
27) 1) PARTIAL_INDEXED_DOCS
??? 2) false
28) 1) UPGRADE_INDEX
??? 2) Upgrade config for upgrading
29) 1) _NUMERIC_COMPRESS
??? 2) false
30) 1) _PRINT_PROFILE_CLOCK
??? 2) true
31) 1) RAW_DOCID_ENCODING
??? 2) false
32) 1) _NUMERIC_RANGES_PARENTS
??? 2) 0

第15个配置项就是friso的配置,默认是空的。

如果想更改可以,我目前找到两种方法,但是是试了第一种:

1.1 第一种:

在redis启动时,增加参数配置,如下:

redis-server --loadmodule /usr/lib/redis/modules/redisearch.so FRISOINI /home/friso.ini

命令可以放到Dockerfile中,并在file中cp friso的初始化文件和字典:

FROM??? redislabs/redisearch:latest
MAINTAINER????? qzh "qiaozh2006@126.com"

WORKDIR /opt/

ADD friso.ini /home/
ADD friso_dict /home/


EXPOSE? 6379
ENTRYPOINT ["redis-server", "--loadmodule", "/usr/lib/redis/modules/redisearch.so","FRISOINI", "/home/friso.ini"]

friso.ini文件可以从gitee上获取,只需要更改其中的字典路径即可

friso.lex_dir = /home/vendors/dict/UTF-8/

friso_dict 文件夹内容结构为:

friso_dict

????????-vendors

????????????????--Makefile.am

????????????????--dict

??????????????????????? ---Makefile.am

??????????????????????? ---GBK

??????????????????????? ---UTF-8

??????????????????????????????? ----friso.lex.ini

??????????????????????????????? ----lex-placename.lex

lex-placename.lex即为自定义字典,主要包括的是地名,在文件friso.lex.ini中将自定义字典加入:

__LEX_CJK_WORDS__ :[
??? lex-main.lex;
??? lex-admin.lex;
??? lex-chars.lex;
??? lex-cn-mz.lex;
??? lex-cn-place.lex;
??? lex-company.lex;
??? lex-festival.lex;
??? lex-flname.lex;
??? lex-food.lex;
??? lex-lang.lex;
??? lex-nation.lex;
??? lex-net.lex;
??? lex-org.lex;
??? lex-touris.lex;
??? lex-placename.lex;
# add more here
]

1.2 第二种:

可以通过redis配置文件的方式,加载模块和相关配置。但是这种方式只是网上查的可以,并没有试过,以后有空再试下。

自定义字典配置好后,打包,运行,再看下RedisSearch中friso的配置

15) 1) FRISOINI
??? 2) /home/friso.ini

2. Java操作实例

2.1 环境配置

仍然是用jedis,相关配置一样,可以在第一篇文章中找到:RedisJson和RedisSearch探究(一)_六狗回来的博客-CSDN博客

2.2 POJO定义

定义province:

package com.redisStream.pojo.address;

import lombok.Getter;
import lombok.Setter;

import java.util.List;

@Getter
@Setter
public class Province {

    private String provinceName;
    private String provincePinyin;
    private List<City> cityList ;
}

定义city:

package com.redisStream.pojo.address;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;

import java.util.List;

@Getter
@Setter
public class City {

    private String cityName;
    private List<County> countyList;
    private String cityPinyin;
    //"geoinfo":-122.064228,37.377658
    private String geoinfo;

}

定义county :

package com.redisStream.pojo.address;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;

import java.util.List;

@Getter
@Setter
public class County {

    private String countyName;
    private String countyPinyin;
    private List<String> attributes;

}

2.3 创建索引

package com.redisStream.utils;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.search.FieldName;
import redis.clients.jedis.search.IndexDefinition;
import redis.clients.jedis.search.IndexOptions;
import redis.clients.jedis.search.Schema;

import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.util.Map;

@Component
public class RedisSearchUtils {

    private static final Logger log = LoggerFactory.getLogger(RedisSearchUtils.class);

    @Autowired
    private UnifiedJedis jedis;

    private String prefix = "$.";

    @PostConstruct
    private void init(){
        createIndex("place-index","place:", new String[]{"provinceName","cityList[*].cityName","cityList[*].geoinfo","cityList[*].countyList[*].countyName"});

    }

    public boolean createIndex(String indexName, String key, String... fields){

        try {

            try{
                Map<String, Object> map = jedis.ftInfo(indexName);
                log.info("index configuration:{}",map);
                jedis.ftDropIndex(indexName);
            } catch (Exception e){
                log.error("the index does not exist", e);
            }

            Schema schema = new Schema();

            float weight = 1.0f;
            for(String field : fields) {
                String attribute;
                if (StringUtils.isNoneBlank(field)) {

                    if (field.indexOf(".") == -1) {
                        attribute = field;
                    } else {
                        String[] fieldSplit = field.split("\\.");
                        attribute = fieldSplit[fieldSplit.length - 1];
                    }

                    if (attribute.toLowerCase().startsWith("geo")) {
                        Schema.Field field1 = new Schema.Field(FieldName.of(prefix + field).as(attribute), Schema.FieldType.GEO);
                        //schema.addGeoField(prefix + field);
                        schema.addField(field1);
                        continue;
                    } else {

                        Schema.TextField textField = new Schema.TextField(FieldName.of(prefix + field).as(attribute), weight, false, false, false, null);
                        schema.addField(textField);
                        weight *= 3;
                        continue;
                    }
                }
            }


            IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON).setLanguage("chinese")
                    .setPrefixes(new String[]{key});


            jedis.ftCreate(indexName,
                    IndexOptions.defaultOptions().setDefinition(rule),
                    schema);
            return true;
        } catch (Exception e){
            log.error("create redis search index failed", e);
            return false;
        }


    }


}

2.4 定义Controller

2.4.1 添加数据

package com.redisStream.controller;

import com.alibaba.fastjson.JSON;
import com.redisStream.pojo.address.Province;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.json.Path2;
import redis.clients.jedis.search.Document;
import redis.clients.jedis.search.Query;
import redis.clients.jedis.search.SearchResult;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
public class PlaceController {

    private static final Logger log = LoggerFactory.getLogger(PlaceController.class);

    @Autowired
    private UnifiedJedis jedis;

    private String key_prefix = "place:";

    @PostMapping("/addProvince")
    public String addProvince(@RequestBody Province newKeyInfo) throws Exception{

        jedis.jsonSet(key_prefix + newKeyInfo.getProvinceName(), JSON.toJSONString(newKeyInfo));
        return JSON.toJSONString(jedis.jsonGet(key_prefix + newKeyInfo.getProvinceName()));

    }

测试一下,发送请求增加数据:

POST http://localhost:8081/addforProvince

{
?? ?"provinceName": "河北省",
?? ?"provincePinyin": "hebeisheng",
?? ?"cityList": [{
?? ??? ?"cityName": "张家口市",
?? ??? ?"cityPinyin": "zhangjiakoushi",
?? ??? ?"geoinfo": "115.408848,40.970239",
?? ??? ?"countyList": [{
?? ??? ??? ?"countyName": "崇礼县",
?? ??? ??? ?"countyPinyin": "chonglixian",
?? ??? ??? ?"attributes": ["滑雪场","高山"]
?? ??? ?}]
?? ?}]

}

返回值:

{"cityList":[{"cityName":"张家口市","cityPinyin":"zhangjiakoushi","countyList":[{"attributes":["滑雪场","高山"],"countyName":"崇礼县","countyPinyin":"chonglixian"}],"geoinfo":"115.408848,40.970239"}],"provinceName":"河北省","provincePinyin":"hebeisheng"}

2.4.2 搜索某个省

    @PostMapping("/queryforProvince")
    public Map<String, String> queryProvince(@RequestBody String keyword) throws Exception {

        Query q = new Query("@provinceName:" + keyword);

        SearchResult result = jedis.ftSearch(indexName,q);
        List<Document> docs = result.getDocuments();

        Map<String, String> map = new HashMap<>();

        for(Document doc : docs){
            doc.getProperties().forEach(a -> map.put(doc.getId(), a.toString()));
        }

        return map;

    }

?发送请求:?POST http://localhost:8081/queryforProvince

河北省

发送之后的response为空。

为什么是空?我试了好几种方式,最后发现,在创建index的时候,将geo去掉就可以了。

不能同时创建geo 类型的index么?查了官网,也没有找到相关说明。这个地方应该我觉得可能是jsonpath搞错了,但是翻看了JSONPath - XPath for JSON,试了几次,也没有别的办法。

没有办法,只好将index拆开两部分。

??????? createIndex("place-index","place:", new String[]{"provinceGeoInfo", "provinceName","cityList.cityName","cityList.countyList.countyName"});

??????? createIndex("place-geo-index","place:", new String[]{"cityList[*].geoinfo"});

哪位知道怎么弄,请不吝赐教,回复一下。

index更改之后,再试一下,就可以了。

createIndex("place-index","place:", new String[]{"provinceName","cityList[*].cityName","cityList[*].countyList[*].countyName"});
createIndex("place-geo-index","place:", new String[]{"provinceName","cityList[*].geoinfo"});

2.4.3 搜索某个市

   @PostMapping("/queryforCity")
    public Map<String, String> queryCity(@RequestBody String keyword) throws Exception {
     
        Query q = new Query("@cityName:" + keyword);  
        SearchResult result = jedis.ftSearch(indexName,q);
        List<Document> docs = result.getDocuments();

        Map<String, String> map = new HashMap<>();

        for(Document doc : docs){
            doc.getProperties().forEach(a -> map.put(doc.getId(), a.toString()));
        }
        return map;

    }

和获取省份是一样的,只是生成Query的时候,指定的field不同,实验一下:

POST http://localhost:8081/queryforCity

{张家口市}

?2.4.4 全文搜索

    @PostMapping("/queryforAddrall")
    public Map<String, String> queryAddrALl(@RequestBody String keyword) throws Exception {

        Query q = new Query(keyword);

        SearchResult result = jedis.ftSearch("place-index",q);
        List<Document> docs = result.getDocuments();

        Map<String, String> map = new HashMap<>();

        for(Document doc : docs){
            doc.getProperties().forEach(a -> map.put(doc.getId(), a.toString()));
        }

        return map;

    }

这是时候无论,搜索河北省还是张家口市,这个接口都可以搜到:

?2.4.5 地理位置过滤

@PostMapping("/queryforgeo")
    public Map<String, String> queryGeo(@RequestBody GEOQueryBody body) throws Exception {

        Query q = new Query(body.getName());
        if(StringUtils.isNoneBlank(body.getGeoinfo())) {
            String[] geo = body.getGeoinfo().split(",");
            q.addFilter(new Query.GeoFilter("geoinfo", Double.parseDouble(geo[0].trim()), Double.parseDouble(geo[1].trim()), Double.parseDouble(geo[2].trim()), Query.GeoFilter.KILOMETERS));
        }
        SearchResult result = jedis.ftSearch("place-geo-index",q);
        List<Document> docs = result.getDocuments();

        Map<String, String> map = new HashMap<>();

        for(Document doc : docs){
            doc.getProperties().forEach(a -> map.put(doc.getId(), a.toString()));
        }

        return map;

    }

GEOQueryBody也很简单:

public class GEOQueryBody {

    private String name;
    private String geoinfo;
}

需要注意的是,GEOFilter的第一个参数property,只要写字段的名字就可以了,即写成:

q.addFilter(new Query.GeoFilter("geoinfo", Double.parseDouble(geo[0].trim()), Double.parseDouble(geo[1].trim()), Double.parseDouble(geo[2].trim()), Query.GeoFilter.KILOMETERS));

定义index的时候,没有使用as来取别名,那么要写成jsonpath路径,比如:

q.addFilter(new Query.GeoFilter("$.geoinfo", Double.parseDouble(geo[0]), Double.parseDouble(geo[1]), Double.parseDouble(geo[2]), Query.GeoFilter.KILOMETERS));

发请求试一下:

?如果把经纬度更换了,比如变为:

{
??? "name":"河北省",
??? "geoinfo":"125.408848,40.970239,20"
}

搜索结果就为空。

2.4.6 添加城市信息

    @PostMapping("/addforCity")
    public String addCity(@RequestBody Province newKeyInfo) throws Exception{

        Path2 path = new Path2("$." + ".cityList");
        jedis.jsonArrAppend(key_prefix + newKeyInfo.getProvinceName(), path, JSON.toJSONString(newKeyInfo.getCityList().get(0)));
        return JSON.toJSONString(jedis.jsonGet(key_prefix + newKeyInfo.getProvinceName()));
    }

?添加一下:

{
?? ?"provinceName": "河北省",
?? ?"geoinfoprovince": "125.1111,33.2222",
?? ?"provincePinyin": "hebeisheng",
?? ?"cityList": [{
?? ??? ?"cityName": "石家庄市",
?? ??? ?"cityPinyin": "shijiazhuangshi",
?? ??? ?"geoinfo": "125.408848,41.970239",
?? ??? ?"countyList": [{
?? ??? ??? ?"countyName": "正定县",
?? ??? ??? ?"countyPinyin": "chonglixian",
?? ??? ??? ?"attributes": ["正定","历史名城"]
?? ??? ?}]
?? ?}]

}

?返回结果为:

{"cityList":[{"cityName":"张家口市","cityPinyin":"zhangjiakoushi","countyList":[{"attributes":["滑雪场","高山"],"countyName":"崇礼县","countyPinyin":"chonglixian"}],"geoinfo":"115.408848,40.970239"},{"cityName":"石家庄市","cityPinyin":"shijiazhuangshi","countyList":[{"attributes":["正定","历史名城"],"countyName":"正定县","countyPinyin":"chonglixian"}],"geoinfo":"125.408848,41.970239"}],"geoinfoprovince":"125.1111,33.2222","provinceName":"河北省","provincePinyin":"hebeisheng"}

2. 应用限制

虽然有的还没有摸透,但是基本上能找到解决方法,但是我在尝试对jsonarray进行索引,并添加了多个list内容的时候的时候,怎么也无法搜索,也就是以上的例子,如果我再加一个石家庄市,然后,再进行相同的搜索,就会返回空结果。折腾了半天,后来在官网发现了这句话:

JSON arrays can only be indexed in a TAG field.

也就是说,如果是text类型,是不支持创建jsonarray类型的。

心塞啊,我希望自己看错了。

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-01-30 18:59:54  更:2022-01-30 19:02:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:06:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码