(二)、SpringCloud整合ElasticSearch

本文涉及的产品
Elasticsearch Serverless通用抵扣包,测试体验金 200元
简介: (二)、SpringCloud整合ElasticSearch

Elasticsearch-Rest-Client

1. 通过 9300: TCP

  • spring-data-elasticsearch:transport-api.jar;
  • springboot版本不同,ransport-api.jar不同,不能适配es版本
  • 7.x已经不建议使用,8以后就要废弃

2. 通过 9200: HTTP

  • jestClient: 非官方,更新慢;
  • RestTemplate:模拟HTTP请求,ES很多操作需要自己封装,麻烦;
  • HttpClient:同上;
  • Elasticsearch-Rest-Client:官方RestClient,封装了ES操作,API层次分明,上手简单;

最终选择Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client); https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

创建 Elasticsearch 检索服务模块

gulimall-search

8d9717d8e57e95787aad0dafe5317c32_202110021030272.png

pom.xml

<?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>
        <artifactId>gulimall</artifactId>
        <groupId>com.ylc.gulimall</groupId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath></relativePath>
    </parent>
    <artifactId>gulimall-search</artifactId>
    <name>gulimall-search</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.ylc.gulimall</groupId>
            <artifactId>gulimall-coupon</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>com.baomidou</groupId>
                    <artifactId>mybatis-plus-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

父pom

<!--  这里的属性会被子模块继承  -->
    <properties>
        ...
        <elasticsearch.version>7.4.2</elasticsearch.version>
    </properties>
    <!--  子模块继承父模块之后,提供作用:锁定版本 + 子模块不用再写 version  -->
    <dependencyManagement>
        <dependencies>
            ...
            <!-- 重写覆盖 spring-boot-dependencies 中的依赖版本  -->
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-high-level-client</artifactId>
                <version>${elasticsearch.version}</version>
            </dependency>
            <dependency>
                <groupId>org.elasticsearch</groupId>
                <artifactId>elasticsearch</artifactId>
                <version>${elasticsearch.version}</version>
            </dependency>
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-client</artifactId>
                <version>${elasticsearch.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

YML

spring:
  application:
    name: gulimall-search
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
      config:
        server-addr: 127.0.0.1:8848
server:
  port: 13000

主启动类

package com.ylc.gulimallsearch;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class GulimallSearchApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallSearchApplication.class, args);
    }
}

编写配置类

package com.ylc.gulimallsearch.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MallElasticSearchConfig {
    /**
     * 配置请求选项
     * 参考:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-low-usage-requests.html#java-rest-low-usage-request-options
     */
    public static final RequestOptions COMMON_OPTIONS;
    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
        COMMON_OPTIONS = builder.build();
    }
    @Bean
    public RestHighLevelClient esRestClient() {
        return new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("192.168.195.100", 9200, "http")));
    }
}

编写测试类

package com.ylc.gulimallsearch;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class GulimallSearchApplicationTests {
    @Autowired
    RestHighLevelClient client;
    @Test
    void contextLoads() {
        System.out.println(client);
    }
}

测试成功

7d5b737359b258f015684e3905366c97_202110021047719.png

测试存储数据

@Test
    void  indexData() throws IOException {
        IndexRequest indexRequest = new IndexRequest("users");
        indexRequest.id("1");
        User user=new User();
        user.setAge(22);
        user.setName("ylc");
        user.setGender("男");
        String json= JSON.toJSONString(user);
        indexRequest.source(json, XContentType.JSON);
        //执行操作
        IndexResponse index =client.index(indexRequest, MallElasticSearchConfig.COMMON_OPTIONS);
        System.out.println(index);
    }
    @Data
    public  class  User
    {
        private  String name;
        private String gender;
        private  Integer age;
    }

查看结果

GET users/_search

cf22a2ba503afb001e8e5467e926eb9c_202110021103200.png

整合复杂检索

参考: Search API

/**
     * 检索地址中带有 mill 的人员年龄分布和平均薪资
     */
    @Test
    void searchData() throws IOException {
        // 1. 创建检索请求
        SearchRequest searchRequest = new SearchRequest();
        // 指定索引
        searchRequest.indices("bank");
        // 指定 DSL 检索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 1.1 构建检索条件 address 包含 mill
        searchSourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));
        // 1.2 按照年龄值分布进行聚合
        TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
        searchSourceBuilder.aggregation(ageAgg);
        // 1.3 计算平均薪资
        AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
        searchSourceBuilder.aggregation(balanceAvg);
        System.out.println("检索条件:" + searchSourceBuilder.toString());
        searchRequest.source(searchSourceBuilder);
        // 2. 执行检索, 获得响应
        SearchResponse searchResponse = client.search(searchRequest, MallElasticSearchConfig.COMMON_OPTIONS);
        // 3. 分析结果
        // 3.1 获取所有查到的记录
        SearchHits hits = searchResponse.getHits();
        SearchHit[] searchHits = hits.getHits();
        for (SearchHit hit : searchHits) {
            // 数据字符串
            String jsonString = hit.getSourceAsString();
            System.out.println(jsonString);
            // 可以通过 json 转换成实体类对象
            // Account account = JSON.parseObject(jsonString, Account.class);
        }
        // 3.2 获取检索的分析信息(聚合数据等)
        Aggregations aggregations = searchResponse.getAggregations();
        // for (Aggregation aggregation : aggregations.asList()) {
        //     System.out.println("当前聚合名:" + aggregation.getName());
        // }
        Terms ageAgg1 = aggregations.get("ageAgg");
        for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
            String keyAsString = bucket.getKeyAsString();
            System.out.println("年龄:" + keyAsString + " 岁的有 " + bucket.getDocCount() + " 人");
        }
        Avg balanceAvg1 = aggregations.get("balanceAvg");
        System.out.println("平均薪资: " + balanceAvg1.getValue());
    }

1dc9546283159d1b974d572ae6e1cdcc_202110021135901.png

检索业务

只有上架的商品存储到Elasticsearch中才能被检索

1. 需要保存 sku 信息

  • 当搜索商品名时,查询的是 sku 的标题 sku_title;
  • 可能通过 sku 的标题、销量、价格区间检索

2. 需要保存品牌、分类等信息

  • 点击分类,检索分类下的所有信息
  • 点击品牌,检索品牌下的商品信息

3. 需要保存 spu 信息

  • 选择规格,检索共有这些规格的商品

方案1-空间换时间

{
    skuId:1
    spuId:11
    skyTitile:华为xx
    price:999
    saleCount:99
    attrs:[
        {尺寸:5存},
        {CPU:高通945},
        {分辨率:全高清}
  ]
}
# 缺点:会产生冗余字段,对于相同类型的商品,attrs 属性字段会重复,空间占用大
# 好处:方便检索

方案2-时间换空间

sku索引
{
    skuId:1
    spuId:11
}
attr索引
{
    spuId:11
    attrs:[
        {尺寸:5寸},
        {CPU:高通945},
        {分辨率:全高清}
  ]
}
# 缺点:选择公共属性attr时,会检索当前属性的所有商品分类,然后再查询当前商品分类的所有可能属性;
#      导致耗时长。
# 好处:空间利用率高

最终结构

PUT product
{
  "mappings": {
    "properties": {
      "skuId": { "type": "long" },
      "spuId": { "type": "keyword" },
      "skuTitle": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "skuPrice": { "type": "keyword" },
      "skuImg": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "saleCount":{ "type":"long" },
      "hasStock": { "type": "boolean" },
      "hotScore": { "type": "long"  },
      "brandId":  { "type": "long" },
      "catalogId": { "type": "long"  },
      "brandName": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "brandImg":{
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "catalogName": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "attrs": {
        "type": "nested",
        "properties": {
          "attrId": {"type": "long"  },
          "attrName": {
            "type": "keyword",
            "index": false,
            "doc_values": false
          },
          "attrValue": { "type": "keyword" }
        }
      }
    }
  }
}

结构说明

"mappings": {
  "properties": {
    "skuId": { "type": "long" },
    "spuId": { "type": "keyword" }, # 精确检索,不分词
    "skuTitle": {
      "type": "text", # 全文检索
      "analyzer": "ik_smart" # 分词器
    },
    "skuPrice": { "type": "keyword" },
    "skuImg": {
      "type": "keyword",
      "index": false, # false 不可被检索
      "doc_values": false # false 不可被聚合
    },
    "saleCount":{ "type":"long" }, # 商品销量
    "hasStock": { "type": "boolean" }, # 商品是否有库存
    "hotScore": { "type": "long"  }, # 商品热度评分
    "brandId":  { "type": "long" }, # 品牌id
    "catalogId": { "type": "long"  }, # 分类id
    "brandName": {  # 品牌名,只用来查看,不用来检索和聚合
      "type": "keyword",
      "index": false,
      "doc_values": false
    },
    "brandImg":{  # 品牌图片,只用来查看,不用来检索和聚合
      "type": "keyword",
      "index": false,
      "doc_values": false
    },
    "catalogName": {  # 分类名,只用来查看,不用来检索和聚合
      "type": "keyword",
      "index": false,
      "doc_values": false
    },
    "attrs": {  # 属性对象
      "type": "nested", # 嵌入式,内部属性
      "properties": {
        "attrId": {"type": "long"  },
        "attrName": { # 属性名
          "type": "keyword",
          "index": false,
          "doc_values": false
        },
        "attrValue": { "type": "keyword" }  # 属性值
      }
    }
  }
}

关于 nested 类型

f0641d0fd2933ad6a2f9c75b4b550fd4_202110021608629.png

对于这种情况于是就会用到nested类型

商品上架及ElasticSearch检索

利用es检索商品信息,被检索的只能是已经上架的商品,因此商品需要先上架,上架前需要检查商品的库存信息,没有库存不能够上架,符合要求的拿到商品id再上架,最后再把上架的商品插入es的索引结构中,才能够被检索到。

根据es事先建立好的商品信息索引结构在原有SkuInfoEntity类中还多了一些字段,还有一些字段名称不相同,因此在SkuInfoEntity类的基础上建立了SkuesModel

获取该商品spuId所对应的skuId信息

  1. 首先发送远程调用查看库存模块是否还有库存
  1. 根据skuid去库存表 wms_ware_sku查询,返回商品skuId和是否有库存信息的bool值,用Map接收
  2. 根据商品的skuId集合,获取到对应的Sku实体信息SkuInfoEntity,把SkuInfoEntity复制到SkuesModel集合,根据skuId查询到品牌的相关信息后赋值,并把不相同无法复制的字段手动设置值。
  3. 同时获取商品的属性集合,并赋值给SkuesModel.attr
  1. 最后将封装好的SkuesModel数据发送给es保存
  1. 远程调用es检索模块,把SkuesModel数据批量保存到es索引中
  2. 根据返回结果,成功再更新商品的上架状态,事务保持统一


相关实践学习
以电商场景为例搭建AI语义搜索应用
本实验旨在通过阿里云Elasticsearch结合阿里云搜索开发工作台AI模型服务,构建一个高效、精准的语义搜索系统,模拟电商场景,深入理解AI搜索技术原理并掌握其实现过程。
ElasticSearch 最新快速入门教程
本课程由千锋教育提供。全文搜索的需求非常大。而开源的解决办法Elasricsearch(Elastic)就是一个非常好的工具。目前是全文搜索引擎的首选。本系列教程由浅入深讲解了在CentOS7系统下如何搭建ElasticSearch,如何使用Kibana实现各种方式的搜索并详细分析了搜索的原理,最后讲解了在Java应用中如何集成ElasticSearch并实现搜索。 &nbsp;
相关文章
|
监控 Cloud Native 搜索推荐
Springboot/Springcloud整合ELK平台,(Filebeat方式)日志采集及管理(Elasticsearch+Logstash+Filebeat+Kibana)
Springboot/Springcloud整合ELK平台,(Filebeat方式)日志采集及管理(Elasticsearch+Logstash+Filebeat+Kibana)
3012 0
Springboot/Springcloud整合ELK平台,(Filebeat方式)日志采集及管理(Elasticsearch+Logstash+Filebeat+Kibana)
|
canal 缓存 SpringCloudAlibaba
Springcloud Alibaba 使用Canal将MySql数据实时同步到Elasticsearch
本篇文章在Springcloud Alibaba使用Canal将Mysql数据实时同步到Redis保证缓存的一致性-CSDN博客 基础上使用canal将mysql数据实时同步到Elasticsearch。
367 0
|
存储 Java Maven
如何使用Spring Cloud搭建高可用的Elasticsearch集群?详解Elasticsearch的安装与配置及Spring Boot集成的实现(下)
如何使用Spring Cloud搭建高可用的Elasticsearch集群?详解Elasticsearch的安装与配置及Spring Boot集成的实现(下)
429 0
|
存储 负载均衡 搜索推荐
如何使用Spring Cloud搭建高可用的Elasticsearch集群?详解Elasticsearch的安装与配置及Spring Boot集成的实现(上)
如何使用Spring Cloud搭建高可用的Elasticsearch集群?详解Elasticsearch的安装与配置及Spring Boot集成的实现
762 0
|
机器学习/深度学习 存储 SQL
探究Spring Cloud Elasticsearch:最佳性能调优技巧
探究Spring Cloud Elasticsearch:最佳性能调优技巧
418 0
|
Java Maven
SpringCloud系列----->SpringBoot项目中整合ElasticSearch
SpringBoot项目中整合ElasticSearch: 1、首先在maven的pom.xml中加入以下配置: <properties> <!-- 设定和你的es版本相同的版本号,springboot默认的是2.
2294 0
|
8月前
|
安全 Java Linux
Linux安装Elasticsearch详细教程
Linux安装Elasticsearch详细教程
1362 64
|
7月前
|
JSON 安全 数据可视化
Elasticsearch(es)在Windows系统上的安装与部署(含Kibana)
Kibana 是 Elastic Stack(原 ELK Stack)中的核心数据可视化工具,主要与 Elasticsearch 配合使用,提供强大的数据探索、分析和展示功能。elasticsearch安装在windows上一般是zip文件,解压到对应目录。文件,elasticsearch8.x以上版本是自动开启安全认证的。kibana安装在windows上一般是zip文件,解压到对应目录。elasticsearch的默认端口是9200,访问。默认用户是elastic,密码需要重置。
3348 0
|
存储 安全 数据管理
如何在 Rocky Linux 8 上安装和配置 Elasticsearch
本文详细介绍了在 Rocky Linux 8 上安装和配置 Elasticsearch 的步骤,包括添加仓库、安装 Elasticsearch、配置文件修改、设置内存和文件描述符、启动和验证 Elasticsearch,以及常见问题的解决方法。通过这些步骤,你可以快速搭建起这个强大的分布式搜索和分析引擎。
461 5
|
NoSQL 关系型数据库 Redis
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
mall在linux环境下的部署(基于Docker容器),docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongodb、minio详细教程,拉取镜像、运行容器
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo