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
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);
}
}
測試成功
測試存儲數據
@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
整合復雜檢索
參考: 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());
}
檢索業務
只有上架的商品存儲到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 類型
- Object 數據類型的數組會被扁平化處理為一個簡單的鍵與值的列表,即對象的相同屬性會放到同一個數組中,在檢索時會出現錯誤。參考官網:How arrays of objects are flattened
- 對於 Object 類型的數組,要使用 nested 字段類型。參考官網:Using nested fields for arrays of objects
對於這種情況於是就會用到nested類型
商品上架及ElasticSearch檢索
利用es檢索商品信息,被檢索的只能是已經上架的商品,因此商品需要先上架,上架前需要檢查商品的庫存信息,沒有庫存不能夠上架,符合要求的拿到商品id再上架,最后再把上架的商品插入es的索引結構中,才能夠被檢索到。
根據es事先建立好的商品信息索引結構在原有SkuInfoEntity
類中還多了一些字段,還有一些字段名稱不相同,因此在SkuInfoEntity
類的基礎上建立了SkuesModel
類
獲取該商品spuId所對應的skuId信息
- 首先發送遠程調用查看庫存模塊是否還有庫存
- 根據skuid去庫存表
wms_ware_sku
查詢,返回商品skuId和是否有庫存信息的bool值,用Map<Long, Boolean>接收 - 根據商品的skuId集合,獲取到對應的Sku實體信息
SkuInfoEntity
,把SkuInfoEntity
復制到SkuesModel
集合,根據skuId查詢到品牌的相關信息后賦值,並把不相同無法復制的字段手動設置值。 - 同時獲取商品的屬性集合,並賦值給SkuesModel.attr
- 根據skuid去庫存表
- 最后將封裝好的
SkuesModel
數據發送給es保存- 遠程調用es檢索模塊,把
SkuesModel
數據批量保存到es索引中 - 根據返回結果,成功再更新商品的上架狀態,事務保持統一
- 遠程調用es檢索模塊,把