第六章 ElasticSearch

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

场景:百度、京东、谷歌、淘宝这样的随机翻页搜索

优点:没有查询上限(单次查询的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 故障转移

  1. 故障检测:Es集群中的节点会定期进行心跳检查,以确定其他节点是否仍然可用。如果主节点在一定时间内没有响应心跳检查,那么它将被标记为不可用。
  2. 主节点选举:一旦主节点被标记为不可用,集群中的其他节点会开始一个新的主节点选举过程。
  3. 故障转移:一旦新的主节点被选举出来,集群的状态就会被转移到新的主节点上。新的主节点会开始协调集群的操作,如分片重新分配和索引创建等。
相关推荐
小黑屋说YYDS2 小时前
ElasticSearch7.x入门教程之全文搜索(九)
elasticsearch
动态一时爽,重构火葬场13 小时前
elasticsearch是如何进行搜索的?
大数据·elasticsearch·搜索引擎
P.H. Infinity13 小时前
【Elasticsearch】06-JavaRestClient查询
大数据·elasticsearch·搜索引擎
羽_羊17 小时前
Elasticsearch scroll 之滚动查询
elasticsearch·scroll
URBBRGROUN46717 小时前
Spring Data Elasticsearch
java·spring·elasticsearch
w_t_y_y17 小时前
ES中的字段类型
大数据·elasticsearch·搜索引擎
猫猫不是喵喵.21 小时前
分布式搜索引擎Elasticsearch(二)
分布式·elasticsearch·搜索引擎
CodingBrother1 天前
Elasticsearch做分词实践
大数据·elasticsearch·搜索引擎
weixin_470713541 天前
Elasticsearch 集成
大数据·elasticsearch·搜索引擎
mingyuewu1 天前
ElasticSearch笔记
笔记·elasticsearch