(二)、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

image-20211002103032207

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);
    }
}

測試成功

image-20211002104752615

測試存儲數據

@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

image-20211002110358088

整合復雜檢索

參考: 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());
    }

image-20211002113537838

檢索業務

只有上架的商品存儲到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 類型

image-20211002160812439

對於這種情況於是就會用到nested類型

商品上架及ElasticSearch檢索

利用es檢索商品信息,被檢索的只能是已經上架的商品,因此商品需要先上架,上架前需要檢查商品的庫存信息,沒有庫存不能夠上架,符合要求的拿到商品id再上架,最后再把上架的商品插入es的索引結構中,才能夠被檢索到。

根據es事先建立好的商品信息索引結構在原有SkuInfoEntity類中還多了一些字段,還有一些字段名稱不相同,因此在SkuInfoEntity類的基礎上建立了SkuesModel

獲取該商品spuId所對應的skuId信息

  1. 首先發送遠程調用查看庫存模塊是否還有庫存
    1. 根據skuid去庫存表 wms_ware_sku查詢,返回商品skuId和是否有庫存信息的bool值,用Map<Long, Boolean>接收
    2. 根據商品的skuId集合,獲取到對應的Sku實體信息SkuInfoEntity,把SkuInfoEntity復制到SkuesModel集合,根據skuId查詢到品牌的相關信息后賦值,並把不相同無法復制的字段手動設置值。
    3. 同時獲取商品的屬性集合,並賦值給SkuesModel.attr
  2. 最后將封裝好的SkuesModel數據發送給es保存
    1. 遠程調用es檢索模塊,把SkuesModel數據批量保存到es索引中
    2. 根據返回結果,成功再更新商品的上架狀態,事務保持統一


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM