1. 初识ElasticSearch
1.1 倒排索引
1.2 什么是文档和词条?
每一条数据就是一个文档,文档会被序列化成json存储在elastic search中
对文档中的内容分词,得到的词语就是词条
1.3 什么是正向索引?
基于文档id创建索引。查询词条时必须先找到文档,而后判断是否包含词条
1.4 什么是倒排索引?
对文档内容分词,对词条创建索引,并记录词条所在文档的信息。查询时先根据词条查询到文档id,再根据文档id查询文档
1.5 ElasticSearch与Mysql的概念对比
es是一个开源的分布式搜索引擎,可以用来实现搜索、日志分析、统计和系统监控等功能。
1.6 mysql和elasticSearch的区别是什么?
mysql擅长事务类型的操作,可以确保数据的安全和一致性;
而es擅长海量数据的搜索、分析、计算,适合复杂场景查询。
1.7 docker安装es
bash
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
部署kibana:
bash
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601 \
kibana:7.12.1
2. ik分词器
中文分词器:
ik_smart,智能切分,粗力度
ik_max_word,最细切分,细粒度
2.1 自定义词典
ik的配置文件:
/var/lib/docker/volumes/es-plugins/_data/ik/config/IKAnalyzer.cfg.xml
xml
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">ext.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">stopword.dic</entry>
拓展字典ext.dic:
properties
黑马程序员
传智教育
博学谷
孙克旭
停止词字典stopword.dic:
properties
的
嗯
恩
了
哦
啊
3. 索引库
3.1 mapping属性
mapping属性就是文档字段的数据类型
3.2 创建索引库
查看索引库:
GET /索引库名
删除索引库:
DELETE /索引库名
修改索引库:
索引库一旦创建,无法修改,但是可以增加新的字段:
properties
PUT /索引库名/_mapping{
"properties": {
"新字段名": {
"type":
......
}
}
}
4. 文档操作
(1)增加文档:
(2)查看、删除文档:
(3)修改文档:
方式一:全量修改
先删除原来的文档,再增加文档;如果指定的文档id不存在,就能直接增加文档
方式二:直接修改字段数据
4.1 总结
5. RestClient基础API
5.1 导入es客户端依赖
依赖版本应与es组件的版本相同
xml
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.12.1</version>
</dependency>
点击maven插件发现:
有两个子依赖的版本不一致,这是因为es依赖受到SpringBoot的管理。打开SpringBoot的父依赖
所以需要手动定义es的版本:
这样es的依赖版本就统一了
5.2 es客户端初始化
java
package cn.itcast.hotel;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
public class HotelIndexTest {
private RestHighLevelClient client;
@Test
void name() {
System.out.println(client);
}
/**
* 初始化es客户端,指定es服务器地址
*/
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.137.110:9200")
));
}
/**
* 销毁es客户端
*/
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
}
5.3 索引库API
java
/**
* 创建索引库
*
* @throws IOException
*/
@Test
void createHotelIndex() throws IOException {
//1.创建Request对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
//2.准备请求的参数:DSL语句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
//3.发送请求
client.indices().create(request, RequestOptions.DEFAULT);
}
/**
* 删除索引库
*
* @throws IOException
*/
@Test
void deleteHotelIndex() throws IOException {
//1.创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
//2.发送请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
/**
* 判断索引库是否存在
* @throws IOException
*/
@Test
void existHotelIndex() throws IOException {
//1.创建Request对象
GetIndexRequest request = new GetIndexRequest("hotel");
//2.发送请求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
5.4 文档API
java
package cn.itcast.hotel;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import org.apache.http.HttpHost;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
/**
* @ClassName HotelDocumentTest
* @Description 文档的crud测试
* @Author 孙克旭
* @Date 2024/11/27 12:12
*/
@SpringBootTest
public class HotelDocumentTest {
@Autowired
private IHotelService hotelService;
private RestHighLevelClient client;
/**
* 增加文档
*
* @throws IOException
*/
@Test
void testAddDocument() throws IOException {
//根据id查询酒店数据
Hotel hotel = hotelService.getById(45870L);
//转为文档类型,es的hotel库中的字段与数据库的字段有差异
HotelDoc hotelDoc = new HotelDoc(hotel);
//1.准备Request对象
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
//2.准备json文档,序列化成json格式
request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
//3.发送请求
client.index(request, RequestOptions.DEFAULT);
}
/**
* 查找文档
*
* @throws IOException
*/
@Test
void testGetDocumentById() throws IOException {
//1.准备Request
GetRequest request = new GetRequest("hotel", "45870");
//2.发送请求,得到响应
GetResponse response = client.get(request, RequestOptions.DEFAULT);
//3.解析响应结果
String json = response.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(hotelDoc);
}
/**
* 修改文档
*
* @throws IOException
*/
@Test
void testUpdateDocument() throws IOException {
//1.准备Request
UpdateRequest request = new UpdateRequest("hotel", "45870");
//2.准备请求参数
request.doc(
"price", "952",
"starName", "四钻"
);
//3.发送请求
client.update(request, RequestOptions.DEFAULT);
}
/**
* 删除文档
*
* @throws IOException
*/
@Test
void testDeleteDocument() throws IOException {
//1.准备Request
DeleteRequest request = new DeleteRequest("hotel", "45870");
//2.发送请求
client.delete(request, RequestOptions.DEFAULT);
}
/**
* 初始化es客户端,指定es服务器地址
*/
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.137.110:9200")
));
}
/**
* 销毁es客户端
*/
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
}
5.4.1 配量操作
java
/**
* 批量添加文档
*
* @throws IOException
*/
@Test
void testBulkRequest() throws IOException {
//批量查询酒店数据
List<Hotel> hotels = hotelService.list();
//1.创建Request
BulkRequest request = new BulkRequest();
//2.准备参数,添加多个新增的Request
//将hotel类型转换成hotelDoc
for (Hotel hotel : hotels) {
HotelDoc hotelDoc = new HotelDoc(hotel);
request.add(new IndexRequest("hotel")
.id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc), XContentType.JSON));
}
//3.发送请求
client.bulk(request, RequestOptions.DEFAULT);
}
5.5 两种地理坐标类型
ES支持两种地理坐标数据类型:
geo_point:由纬度和经度确定的一个点
geo_shape:由多个geo_point组成的复杂几何图形
5.6 字段拷贝使用的是哪个属性
字段拷贝可以使用cope_to属性将当前字段拷贝到指定字段;
5.7 批量查询的语法?
GET /索引库名/_search---批量查询
6. DSL查询
6.1 全文检索查询
6.1.1 match和multi_match的区别是什么?
match:根据一个字段查询
mutil_match:根据多个字段查询,参与查询字段越多,查询性能越差
6.2 精确查询
范围查询:
6.2.1 精确查询常见的有哪些?
term查询:根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段
range查询:根据数值范围查询,可以是数值、日期的范围
6.3 地理查询
根据字段查询到中心点的距离小于指定距离的文档数据
6.4 复合查询
6.4.1 elasticsearch中的相关性打分算法是什么?
TF-IDF:在elasticsearch5.0之前,会随着词频增加而越来越大
BM258:在elasticsearch5.0之后,会随着词频增加而增大,但增长曲 线会趋于水平
6.4.2 算分查询Function Score Query
6.4.2.1 function score query定义的三要素是什么?
(1)过滤条件:哪些文档要加分
(2)算分函数:如何计算function score
(3)加权方式:function score与query score如何运行,是相乘还是相加
6.4.3 布尔查询 Boolean Query
上图表示查询在上海、品牌是皇冠假日或华美达、费用不低于500和评分大于等于45的酒店
6.4.3.1 案例
6.5 排序
6.6 分页
6.6.1 from+size 普通分页方式
优点:支持随机翻页
缺点:深度分页问题,es集群过多时,分页查询时需要将每个节点的部分数据排序,再分页,这样对cpu和内存的要求较高。所以默认查询上限是10000
场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
6.6.2 after search 后续搜索
优点:没有查询上限(单次查询的size不超过10000)
缺点:每次查询是在上一次查询的基础上进行查询的,只能向后查询,不支持随机翻页。
场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
6.6.3 scroll 滚动翻页
优点:没有查询上限(单次查询的size不超过10000)
缺点:该方式会生成搜索快照,会有额外的内存消耗,并且搜索结果是非实时的
场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用after search方案。
6.7 高亮
对于高亮字段会自动添加 <em>
标签
6.8 DSL查询的整体语法
7. RestClient查询API
7.1 查询全部数据
java
/**
* es客户端
*/
private RestHighLevelClient client;
/**
* 查询所有数据
*
* @throws IOException
*/
@Test
void testMatchAll() throws IOException {
//1.准备Request
SearchRequest request = new SearchRequest("hotel");
//2.准备DSL
request.source().query(QueryBuilders.matchAllQuery());
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析数据
parseSearchResponse(response);
}
/**
* 解析查询响应对象
*
* @param response
*/
private void parseSearchResponse(SearchResponse response) {
int n = 0;
//1.解析响应
SearchHits searchHits = response.getHits();
//2.获取总条数
long total = searchHits.getTotalHits().value;
System.out.println("总条数:" + total);
//3.获取文档数组
SearchHit[] hits = searchHits.getHits();
//4.遍历
for (SearchHit hit : hits) {
n++;
//4.1 获取文档source
String json = hit.getSourceAsString();
//反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(n + ": hotelDoc:" + hotelDoc);
}
}
7.2 按指定字段查询
java
/**
* 按指定字段查询数据
*
* @throws IOException
*/
@Test
void testMatch() throws IOException {
//1.准备Request
SearchRequest request = new SearchRequest("hotel");
//2.准备DSL
request.source().query(QueryBuilders.matchQuery("all", "如家"));
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析数据
parseSearchResponse(response);
}
7.3 布尔查询
java
/**
* 布尔查询
*
* @throws IOException
*/
@Test
void testBool() throws IOException {
//1.准备Request
SearchRequest request = new SearchRequest("hotel");
//2.准备DSL
//2.1 准备BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//2.2 添加term
boolQuery.must(QueryBuilders.termQuery("city", "上海"));
//2.3 添加range
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(500).gte(100));
request.source().query(boolQuery);
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析数据
handleResponse(response);
}
7.4 排序和分页
java
/**
* 排序和分页
*
* @throws IOException
*/
@Test
void testPageAndSort() throws IOException {
int page = 2, size = 5;
//1.准备Request
SearchRequest request = new SearchRequest("hotel");
//2.准备DSL
//2.1 query
request.source().query(QueryBuilders.matchAllQuery());
//2.2 排序
request.source().sort("price", SortOrder.DESC);
//2.3 分页 from、size
request.source().from((page - 1) * size).size(size);
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析数据
handleResponse(response);
}
7.5 高亮
java
/**
* 高亮显示
*
* @throws IOException
*/
@Test
void testHighLight() throws IOException {
//1.准备Request
SearchRequest request = new SearchRequest("hotel");
//2.准备DSL
request.source().query(QueryBuilders.matchQuery("all", "如家"));
//2.1 高亮
HighlightBuilder highlight = new HighlightBuilder();
request.source().highlighter(highlight.field("name").requireFieldMatch(false));
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析数据
handleResponse(response);
}
/**
* 解析查询响应对象
*
* @param response
*/
private void handleResponse(SearchResponse response) {
int n = 0;
//1.解析响应
SearchHits searchHits = response.getHits();
//2.获取总条数
long total = searchHits.getTotalHits().value;
System.out.println("总条数:" + total);
//3.获取文档数组
SearchHit[] hits = searchHits.getHits();
//4.遍历
for (SearchHit hit : hits) {
n++;
//4.1 获取文档source
String json = hit.getSourceAsString();
//反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
//获取高亮结果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (!CollectionUtils.isEmpty(highlightFields)) {
//根据字段名获取高亮结果
HighlightField highlightField = highlightFields.get("name");
if (highlightField != null) {
//获取高亮值
String name = highlightField.getFragments()[0].string();
//覆盖非高亮结果
hotelDoc.setName(name);
}
}
System.out.println(n + ": hotelDoc:" + hotelDoc);
}
}
8. 黑马旅游案例
8.1 查询全部数据
1.根据前端传递参数定义Bean
2.业务有分页功能,定义PageResult作为返回结果。
3.Service层进行es查询时,注意key值可以为空;排序规则也可以没有,即默认方式
8.2 条件查询
1.先根据前端请求参数重新编写对应的Bean
2.每一个条件都可为空,所以需要挨个判断
8.2.1 代码实现
Bean:
java
package cn.itcast.hotel.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @ClassName RequestParams
* @Description 接受前端请求的bean
* @Author 孙克旭
* @Date 2024/11/28 14:47
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestParams {
/**
* 关键词
* 用户搜索的关键词
*/
private String key;
/**
* 分页的页码
*/
private Integer page;
/**
* 分页的每页大小
*/
private Integer size;
/**
* 排序规则
* default、score、price
*/
private String sortBy;
/**
* 城市
*/
private String city;
/**
* 品牌
*/
private String brand;
/**
* 星级
*/
private String starName;
/**
* 价格最小值
*/
private Integer minPrice;
/**
* 价格最大值
*/
private Integer maxPrice;
}
java
package cn.itcast.hotel.pojo;
import lombok.Data;
import java.util.List;
/**
* @ClassName PageResult
* @Description 响应的bean
* @Author 孙克旭
* @Date 2024/11/28 14:56
*/
@Data
public class PageResult {
/**
* 总条数
*/
private Long total;
/**
* 数据列表
*/
private List<HotelDoc> hotels;
}
Controller层:
java
package cn.itcast.hotel.web;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName HotelController
* @Description 黑马旅游业务管理层
* @Author 孙克旭
* @Date 2024/11/28 15:05
*/
@RestController
@RequestMapping("/hotel")
public class HotelController {
@Autowired
private IHotelService hotelService;
/**
* 查询数据
*
* @param params
* @return
*/
@PostMapping("/list")
public PageResult search(@RequestBody RequestParams params) {
return hotelService.search(params);
}
}
Service层实现类:
java
package cn.itcast.hotel.service.impl;
import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @ClassName HotelService
* @Description service层实现类
* @Author 孙克旭
* @Date 2024/11/28 15:13
*/
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Autowired
private RestHighLevelClient client;
@Override
public PageResult search(RequestParams params) {
try {
//1.准备Request
SearchRequest request = new SearchRequest("hotel");
//2.准备DSL
//2.1 query
buildBasicQuery(params, request);
//2.2 分页查询
int page = params.getPage();
int size = params.getSize();
request.source().from((page - 1) * size).size(size);
//3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析数据
return handleResponse(response);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 封装查询条件
*
* @param params
*/
private void buildBasicQuery(RequestParams params, SearchRequest request) {
//构建BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//关键字搜索
String key = params.getKey();
if (key == null || key.length() == 0) {
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
boolQuery.must(QueryBuilders.matchQuery("all", key));
}
//城市条件
if (params.getCity() != null && params.getCity().length() != 0) {
boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
}
//品牌条件
if (params.getBrand() != null && params.getBrand().length() != 0) {
boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
}
//星级条件
if (params.getStarName() != null && params.getStarName().length() != 0) {
boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
}
//价格条件
Integer minPrice = params.getMinPrice();
Integer maxPrice = params.getMaxPrice();
if (minPrice != null && maxPrice != null) {
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice));
}
request.source().query(boolQuery);
}
/**
* 解析查询响应对象
*
* @param response
*/
private PageResult handleResponse(SearchResponse response) {
PageResult pageResult = new PageResult();
List<HotelDoc> hotelDocs = new ArrayList<>();
//1.解析响应
SearchHits searchHits = response.getHits();
//2.获取总条数
long total = searchHits.getTotalHits().value;
pageResult.setTotal(total);
//3.获取文档数组
SearchHit[] hits = searchHits.getHits();
//4.遍历
for (SearchHit hit : hits) {
//4.1 获取文档source
String json = hit.getSourceAsString();
//反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
//获取高亮结果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (!CollectionUtils.isEmpty(highlightFields)) {
//根据字段名获取高亮结果
HighlightField highlightField = highlightFields.get("name");
if (highlightField != null) {
//获取高亮值
String name = highlightField.getFragments()[0].string();
//覆盖非高亮结果
hotelDoc.setName(name);
}
}
hotelDocs.add(hotelDoc);
}
pageResult.setHotels(hotelDocs);
return pageResult;
}
}
8.3 根据地理位置排序
点击地图定位后,会显示酒店距离用户的距离。
1.在前端请求的bean中添加经纬度属性
2.在构建请求时,使用地理坐标排序
3.在响应的bean中需要添加距离的属性
8.3.1 代码实现
java
//2.3 根据地理坐标排序
String location = params.getLocation();
if (location != null && location.length() != 0) {
request.source().sort(SortBuilders
//该方法会基于距离排序
.geoDistanceSort("location", new GeoPoint(location))
.order(SortOrder.ASC)
//指定单位
.unit(DistanceUnit.KILOMETERS)
);
}
8.4 广告置顶-增加权重
es查询时,会根据每个查询条件增加结果权重,再根据权重排序,即完全匹配的搜索结果会在前面。如果要为广告置顶,需要增加文档的权重。
java
//2. 算分控制,如果是广告,就增加排序的权重
FunctionScoreQueryBuilder functionScoreQueryBuilder =
QueryBuilders.functionScoreQuery(
//原始查询,相关性算分的查询
boolQuery,
//function score的数组
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
//其中一个function score
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
//过滤条件
QueryBuilders.termQuery("isAD", true),
//算分函数,直接*10
ScoreFunctionBuilders.weightFactorFunction(10)
)
}
);
9. 数据聚合
9.1 什么是聚合?
聚合是对文档数据的统计、分析、计算,与sql中的分组类似
9.2 聚合常见的种类?
(1)bucket:桶聚合,对文档数据分组,并统计每组数据
(2)metric:度量聚合,对文档数据做计算,例如avg
(3)pipline:管道聚合,基于其他聚合结果再做聚合
9.3 参与聚合的字段类型必须是?
不能分词的字段:
keyword
数值
日期
布尔
9.4 bucket聚合
对酒店品牌分组
结果中默认会有每种结果的数量字段
限定聚合范围就是先按条件查询,再聚和
9.4.1 聚合必须的三要素?
聚合名称
聚合类型
聚合字段
9.4.2 聚合可配置属性有?
size:指定聚合结果数量
order:指定聚合结果排序方式
field:指定聚合字段
9.5 metric聚合
在bucket聚合的基础上,再做聚合
9.6 RestAPI实现聚合
9.6.1 代码实现
java
/**
* 按照酒店品牌分桶
*
* @throws IOException
*/
@Test
void testAggregation() throws IOException {
//1.准备Request
SearchRequest request = new SearchRequest("hotel");
//2.准备DSL
//2.1 设置size 清除文档数据,只需要聚合结果
request.source().size(0);
//2.2 设置聚合函数
request.source().aggregation(AggregationBuilders
//指定聚合结果字段名称
.terms("brandAgg")
//指定聚合的名称
.field("brand")
//指定结果数据数据
.size(10));
//3.发出请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析结果
Aggregations aggregations = response.getAggregations();
//4.1 根据聚合名称获取聚合结果
Terms brandTerms = aggregations.get("brandAgg");
//4.2 获取buckets
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
//4.3 遍历
for (Terms.Bucket bucket : buckets) {
//4.4 获取key
String key = bucket.getKeyAsString();
System.out.println(key);
}
}
9.6.2 获取城市、星级、品牌的分组
java
public Map<String, List<String>> filters() throws IOException {
Map<String, List<String>> map = new HashMap<>();
String indexName = "hotel";
String field1 = "city";
String field2 = "brand";
String field3 = "starName";
List<String> cityList = buildAggregation(indexName, field1);
List<String> brandList = buildAggregation(indexName, field2);
List<String> starNameList = buildAggregation(indexName, field3);
map.put(field1, cityList);
map.put(field2, brandList);
map.put(field3, starNameList);
return map;
}
/**
* 按照字段名分桶,获取key值
*
* @param indexName 索引库名
* @param field 字段名
* @return
* @throws IOException
*/
private List<String> buildAggregation(String indexName, String field) throws IOException {
List<String> result = new ArrayList<>();
//定义聚合名称
String aggName = field + "Agg";
//1.准备Request
SearchRequest request = new SearchRequest(indexName);
//2.准备DSL
//2.1 设置size 清除文档数据,只需要聚合结果
request.source().size(0);
//2.2 设置聚合函数
request.source().aggregation(AggregationBuilders
//指定聚合结果字段名称
.terms(aggName)
//指定聚合的名称
.field(field)
//指定结果数据数量
.size(100));
//3.发出请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析结果
Aggregations aggregations = response.getAggregations();
//4.1 根据聚合名称获取聚合结果
Terms brandTerms = aggregations.get(aggName);
//4.2 获取buckets
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
//4.3 遍历
for (Terms.Bucket bucket : buckets) {
//4.4 获取key
String key = bucket.getKeyAsString();
result.add(key);
}
return result;
}
9.6.3 条件聚合
根据搜索条件,对城市做聚合。在执行聚合前增加查询条件
java
//查询条件
buildBasicQuery(params, request);
10. 自动补全
10.1 自定义分词器
如何自定义分词器?
(1)创建索引库时,在settings中配置,包含三部分
(2)character filter
(3)tokenizer
(4)filter
拼音分词器注意事项?
为了避免搜索到同音字,搜 索时不要使用拼音分词器
10.2 自动补全对字段的要求
类型是completion类型
字段值是多词条的数组
10.3 代码实现
java
/**
* 测试自动补全
*
* @throws IOException
*/
@Test
void testSuggest() throws IOException {
//1.准备Request
SearchRequest request = new SearchRequest("hotel");
//2.准备DSL
request.source().suggest(new SuggestBuilder().addSuggestion(
"suggestions",
SuggestBuilders.completionSuggestion("suggestion")
.prefix("h")
.skipDuplicates(true)
.size(10)
));
//3.发出请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.解析结果
Suggest suggest = response.getSuggest();
//4.1 根据补全查询名称,获取补全结果
CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
//4.2 获取options
List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
//4.3 遍历
for (CompletionSuggestion.Entry.Option option : options) {
String text = option.getText().toString();
System.out.println(text);
}
}
11. 数据同步
mysql数据变更时,发送mq,搜索模块接收到消息后会对es进行数据更新。
11.1 酒店管理模块代码
发送的消息为数据id,因为mq的队列是基于内存的,如果发送hotel对象,占用内存会很大。直接发送id,es模块会根据id进行处理。
java
/**
* 新增数据
*
* @param hotel
*/
@PostMapping
public void saveHotel(@RequestBody Hotel hotel) {
hotelService.save(hotel);
rabbitTemplate.convertAndSend(MqConstant.HOTEL_EXCHANGE, MqConstant.HOTEL_INSERT_KEY, hotel.getId());
}
/**
* 修改数据
*
* @param hotel
*/
@PutMapping()
public void updateById(@RequestBody Hotel hotel) {
if (hotel.getId() == null) {
throw new InvalidParameterException("id不能为空");
}
hotelService.updateById(hotel);
rabbitTemplate.convertAndSend(MqConstant.HOTEL_EXCHANGE, MqConstant.HOTEL_INSERT_KEY, hotel.getId());
}
/**
* 删除数据
*
* @param id
*/
@DeleteMapping("/{id}")
public void deleteById(@PathVariable("id") Long id) {
hotelService.removeById(id);
rabbitTemplate.convertAndSend(MqConstant.HOTEL_EXCHANGE, MqConstant.HOTEL_DELETE_KEY, id);
}
11.2 酒店搜索模块代码
java
@Override
public void insertById(Long id) {
try {
//根据id查询酒店数据
Hotel hotel = hotelService.getById(id);
//转为文档类型,es的hotel库中的字段与数据库的字段有差异
HotelDoc hotelDoc = new HotelDoc(hotel);
//1.准备Request对象
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
//2.准备json文档,序列化成json格式
request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
//3.发送请求
client.index(request, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void deleteById(Long id) {
try {
//1.准备Request
DeleteRequest request = new DeleteRequest("hotel", id.toString());
//2.发送请求
client.delete(request, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
}
12. es集群
12.1 节点角色
默认情况下,节点同时具备四种角色。
12.2 脑裂
在es的集群中,发生网络阻塞时,主节点可能会与其他节点失联,其他子节点会认为主节点宕机,所以会竞选主节点。这样集群中出现了两个主节点,所以是脑裂现象。
避免方式:
规定集群的主节点必须获得(集群节点数量+1)/2及以上的票数后才能成为主节点,这样发生网络阻塞时原先的主节点会变成子节点。所以集群的数量尽量是奇数。
12.3 分布式新增如何确定分片?
coordinating node(协调节点)根据id做hash运算,得到结果对shard数量取余,余数就是对应的分片
12.4 分布式查询
分散阶段:coordinating node将查询请求分发给不同的分片
收集阶段:将查询结果汇总到coordinating node,整理并返回给用户
12.5 故障转移
- 故障检测:Es集群中的节点会定期进行心跳检查,以确定其他节点是否仍然可用。如果主节点在一定时间内没有响应心跳检查,那么它将被标记为不可用。
- 主节点选举:一旦主节点被标记为不可用,集群中的其他节点会开始一个新的主节点选举过程。
- 故障转移:一旦新的主节点被选举出来,集群的状态就会被转移到新的主节点上。新的主节点会开始协调集群的操作,如分片重新分配和索引创建等。