美文网首页设计方案我的微服务
Spring Boot整合Elasticsearch

Spring Boot整合Elasticsearch

作者: GIT提交不上 | 来源:发表于2020-07-14 23:47 被阅读0次

    一、Elasticsearch简介

       Elasticsearch是一个分布式、高扩展、高实时的搜索与数据分析引擎。Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful Web接口。

    百度百科-Elasticsearch

    1.1 Elasticsearch核心概念

    • 索引index:存储document文档数据的结构,类似于关系型数据库中的数据库
    • 类型type:用于存储document的逻辑结构,相对于index来说,type是index的下级,类似于关系型数据库中的
    • 文档document:json格式,类似于关系型数据库中的行数据

      Elasticsearch使用的是一种称为倒排索引的结构,采用Lucene倒排索作为底层。这种结构适用于快速的全文搜索, 一个索引由文档中所有不重复的列表构成,对于每一个词,都有一个包含它的文档列表。一个Elasticsearch索引是由多个Lucene索引组成。

    〈二〉ElasticSearch的认识:索引、类型、文档

    二、Elasticsearch环境搭建

      本地需要配置JDK环境(使用jdk1.8及以上版本)和node环境(用于启动Elasticsearch Head)。除此之外需要安装配置(本文基于Elasticsearch7.6.1版本进行讲解):

    • Elasticsearch
    • Elasticsearch Head
    • Ik
    • Kibana

    华为云镜像下载-Elasticsearch
    Github下载-Elasticsearch Head
    华为云镜像下载-Logstash(本文不需要
    Github下载-Ik
    华为云镜像下载-Kibana

      所有下载均下载7.6.1版本即可。

    2.1 Elasticsearch跨域请求配置

      在Elasticsearch安装目录的\config\elasticsearch.yml文件中配置跨域请求,以便Elasticsearch Head访问。

    http.cors.enabled: true 
    http.cors.allow-origin: "*"
    

    2.2 Ik分词器配置

      在Elasticsearch安装目录的\config\plugins下新建ik目录,将下载好的ik压缩包解压即可。

    2.3 Kibana汉化

      在Kibana安装目录的\config\kibana.yml文件中配置默认语言为中文。

    i18n.locale: "zh-CN"
    

    2.4 启动Elasticsearch

      双击Elasticsearch安装目录下的\bin\elasticsearch.bat文件,浏览器访问:

    http://localhost:9200/

    图2-1 Elasticsearch启动成功界面.png

    2.5 启动Elasticsearch Head

      在Elasticsearch Head目录下,命令行输入npm run start启动Elasticsearch Head,浏览器访问:

    http://localhost:9100/

    图2-2 Elasticsearch Head启动成功界面.png

    2.6 启动Kibana

      双击Kibana安装目录下的\bin\kibana.bat文件,浏览器访问:

    http://localhost:5601/app/kibana#/dev_tools/console

    图2-3 Kibana启动成功界面.png

    三、Kibana操作ES

      Kibana操作ES参考以下参考链接即可。

    • text类型会分词,keyword类型不会分词

    从零学Elasticsearch系列——使用kibana实现ES基本的操作
    ES7学习笔记(八)数据的增删改

    四、Spring Boot整合Elasticsearch

      引入Elasticsearch和HighLevelClient的Maven依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>7.6.1</version>
    </dependency>
    

      指定Elasticsearch的版本为7.6.1。

    <properties>
        <elasticsearch.version>7.6.1</elasticsearch.version>
    </properties>
    

      RestHighLevelClient配置如下所示:

    @Configuration
    public class ESConfig {
        @Bean
        public RestHighLevelClient restHighLevelClient() {
            RestClientBuilder builder = RestClient.builder(new HttpHost("127.0.0.1", 9200, "http"));
            return new RestHighLevelClient(builder);
        }
    }
    

      Elasticsearch全局配置:

    spring.elasticsearch.rest.uris=http://127.0.0.1:9200
    

    五、商铺搜索建议、GEO位置搜索

      本文实现源码如下,欢迎Star和Fork。

    https://github.com/just-right/elasticsearch

      参考链接如下所示:

    B站狂神说-ElasticSearch教程
    巨坑:ElasticSearch 无法解析序列化的 GeoPoint 字段
    URL在线转码
    参考链接一:ES7学习笔记(十二)高亮 和 搜索建议
    参考链接二:ES7学习笔记(十三)GEO位置搜索

    5.1 创建店铺索引

      使用Kibana创建店铺(Shop)索引,字段包括shopName(店铺名)、address(店铺地址)、tags(店铺标签)、location地址位置字段和suggest搜索建议字段,本文将tags数组作为搜索建议字段的值

    PUT /shop
    {
        "settings":{
            "analysis":{
                "analyzer":{
                    "default":{
                        "type":"ik_max_word"
                    }
                }
            }
        },
        "mappings":{
            "dynamic_date_formats": [
                 "MM/dd/yyyy",
                 "yyyy/MM/dd HH:mm:ss",
                 "yyyy-MM-dd",
                 "yyyy-MM-dd HH:mm:ss"
             ],
            "properties":{
                "shopName":{
                    "type":"text"
                },
                "address":{
                    "type":"text"
                },
                "tags":{
                    "type":"text"
                },
                "suggest":{
                    "type":"completion"
                },
                 "location":{
                    "type":"geo_point"
                }
            }
        }
    }
    

    5.2 新增店铺文档

      使用PostMan进行测试,调用新增文档接口,新增文档:

    http://127.0.0.1:8080/es/document/create/shop

    图5-1 新增文档一.png 图5-2 新增文档二.png

      后端处理新增文档请求,使用Gson解析数据,使用Json解析会序列化失败,原因参考:https://agentd.cn/archives/es-geopoint
      后端实现核心代码如下所示:

    @PostMapping(value = "/es/document/create/shop")
    public String createShopDocument(@RequestBody Shop shop) throws IOException {
        IndexRequest indexRequest = new IndexRequest("shop");
        indexRequest.timeout(TimeValue.timeValueSeconds(1));
        shop.setSuggest(new Completion(shop.getTags()));
        indexRequest.source(new Gson().toJson(shop), XContentType.JSON);
        IndexResponse indexResponse = highLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        return indexResponse.toString();
    }
    

    5.3 基于搜索建议和GEO搜索店铺

      使用PostMan进行测试,调用店铺文档搜索接口,使用店铺名搜索,同时传入当前地理位置的经纬度和距离len进行距离过滤。

    http://127.0.0.1:8080/es/document/search/shop

    图5-3 搜索店铺成功.png 图5-4 搜索店铺失败.png

      后端实现核心代码如下所示:

    @PostMapping(value = "/es/document/search/shop")
    public String shopSearch(@RequestBody ShopSearchInfo shopSearchInfo) throws IOException {
        SearchRequest searchRequest = new SearchRequest("shop");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        /**
         * 搜索建议
         */
        CompletionSuggestionBuilder suggestionBuilder = SuggestBuilders
                .completionSuggestion(Shop.SUGGEST)
                .prefix(shopSearchInfo.getShopName());
        SuggestBuilder suggestBuilder = new SuggestBuilder();
        //自定义搜索名
        suggestBuilder.addSuggestion("shopSearch", suggestionBuilder);
        /**
         * GEO位置搜索
         */
        GeoPoint geoPoint = new GeoPoint(shopSearchInfo.getLatitude(), shopSearchInfo.getLongitude());
        //geo距离查询
        QueryBuilder queryBuilder = QueryBuilders.geoDistanceQuery(Shop.LOCATION)
                .distance(shopSearchInfo.getLen(), DistanceUnit.KILOMETERS)
                .point(geoPoint);
        sourceBuilder.suggest(suggestBuilder);
        sourceBuilder.query(queryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse response = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        List<String> geoResList = new ArrayList<>();
        SearchHit[] searchHits = response.getHits().getHits();
        if (searchHits == null || searchHits.length <= 0) {
            return "未搜索到相关信息!";
        }
        for (SearchHit hit : searchHits) {
            geoResList.add(hit.getId());
        }
        CompletionSuggestion suggestion = response.getSuggest().getSuggestion("shopSearch");
        List<CompletionSuggestion.Entry.Option> optionList = suggestion.getOptions();
        if (optionList == null || optionList.size() <= 0) {
            return "未搜索到相关信息!";
        }
        List<String> suggestResList = new ArrayList<>();
        for (CompletionSuggestion.Entry.Option option : optionList) {
            suggestResList.add(option.getHit().getId());
        }
        /**
         * 寻找相同元素
         */
        geoResList.retainAll(suggestResList);
        if (geoResList == null || geoResList.size() <= 0) {
            return "未搜索到相关信息!";
        }
        List<SearchResultInfo> resList = new ArrayList<>();
        for (String shopID : geoResList) {
            GetRequest request = new GetRequest("shop", shopID);
            GetResponse searchResponse = highLevelClient.get(request, RequestOptions.DEFAULT);
            Map<String, Object> resultMap = searchResponse.getSourceAsMap();
            if (resultMap == null || resultMap.size() <= 0) {
                continue;
            }
            String shopName = (String) resultMap.get(Shop.SHOPNAME);
            Map<String, Double> geoInfo = (Map<String, Double>) resultMap.get(Shop.LOCATION);
            double _lon1 = geoInfo.get("lon");
            double _lat1 = geoInfo.get("lat");
            double _lon2 = shopSearchInfo.getLongitude();
            double _lat2 = shopSearchInfo.getLatitude();
            double len = this.getDistance(_lat1, _lon1, _lat2, _lon2);
            SearchResultInfo resultInfo = new SearchResultInfo();
            resultInfo.setShopID(shopID).setShopName(shopName).setLen(len);
            resList.add(resultInfo);
        }
        /**
         * 按照距离排序 - 倒序
         */
        resList = resList.stream().distinct().sorted(Comparator
                .comparing(SearchResultInfo::getLen)).collect(Collectors.toList());
        return JSONObject.toJSONString(resList);
    }
    
    public double getDistance(double _lat1, double _lon1, double _lat2, double _lon2) {
        double lat1 = (Math.PI / 180) * _lat1;
        double lat2 = (Math.PI / 180) * _lat2;
        double lon1 = (Math.PI / 180) * _lon1;
        double lon2 = (Math.PI / 180) * _lon2;
        //地球半径
        double R = 6378.1;
        double d = Math.acos(Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1)) * R;
        return new BigDecimal(d).setScale(4, BigDecimal.ROUND_HALF_UP).doubleValue();
    }
    

    相关文章

      网友评论

        本文标题:Spring Boot整合Elasticsearch

        本文链接:https://www.haomeiwen.com/subject/mlvqhktx.html