Elasticsearch搜索引擎

目录

初识elasticsearch

了解ES

什么是elasticsearch

elasticsearch的发展

搜索引擎技术排名:

总结

倒排索引

正向索引和倒排索引

正向索引

倒排索引

总结

es的一些概念

文档

索引

概念对比

架构

总结

安装es,kibana

安装es

安装kibana

安装分词器

分词器

安装IK分词器

查看数据卷

上传ik安装包​编辑

重启docker容器

测试

IK分词器扩展和停用词典​编辑

总结

索引库操作

mapping映射属性

mapping属性​编辑

总结

索引库CRUD

创建索引库

查看,删除索引库​编辑

示例:查询

示例:删除

修改索引库

示例:修改

总结

文档操作

新增文档

新增文档的DSL语法如下:​编辑

示例

查询文档

DSL语法​编辑

示例

删除文档

DSL语法​编辑

示例

修改文档

方式一:全量修改,会删除旧文档,添加新文​编辑

示例

方式二:增加修改,修改指定字段值​编辑

示例

总结

RestAPI

RestClient操作索引库

初始化JavaRestClient

创建索引库​编辑

删除索引库

判断索引库是否存在

总结

RestClient操作文档

基本步骤

新增文档

示例

运行结果​编辑​编辑

查询文档

示例

运行结果​编辑

修改文档

示例

运行结果

修改前​编辑

修改后​编辑

删除文档

示例

运行结果

删除前

删除后​编辑

总结

批量导入文档

利用JavaRestClient批量导入索引库中

DSL查询文档

DSL查询分类

DSL Query基本语法​编辑

总结

全文检索查询​编辑

match查询:

multi_match查询:

总结

精准查询

term查询:

range查询:

总结

地理坐标查询

geo_bounding_box:

geo_distance:

复合查询

function score

总结

Bloolean Query

案例:

总结

搜索结果处理

排序

示例:对酒店数据按照用户评价降序排序,评价相同的按照价格升序排序

示例:实现对酒店数据按照你的位置坐标的距离升序排序

分页

针对深度分页,ES提供了两种解决方案:

总结

高亮​编辑

总结​编辑

RestClient查询文档

快速入门

我们通过match_all来演示下基本api,先看DSL的组织:​编辑

代码

运行结果​编辑

我们通过match_all来演示下基本的API,再看结果的解析:​编辑

代码

运行结果​编辑

总结

match查询

代码

运行结果​编辑

精确查询​编辑

复合查询

总结

排序和分页

代码

运行结果​编辑

高亮

高亮结果处理​编辑

代码

运行结果​编辑

总结

黑马旅游案例

案例1:实现黑马旅游的酒店搜索功能,完成关键字搜索和分页

步骤:

案例2:添加品牌,城市,星际,价格等过滤功能

步骤:

案例3:我附近的酒店

步骤

案例4:让指定的酒店再搜索中排名位置置顶

步骤

数据聚合

聚合的种类

聚合分类

总结

什么是聚合?

聚合的常见种类有哪些?

参与聚合的字段类型必须是:

DSL实现聚合

DSL实现Bucket聚合

总结

DSL实现Metrics聚合

RestAPI实现聚合

案例:在IUserService中定义方法,实现对品牌,城市,星级的聚合

自动补全

拼音分词器

测试​编辑

自定义分词器

总结

自动补全查询

总结

实现酒店搜索框自动补全

RestAPI实现自动补全

自动补全​编辑

数据同步

数据同步思路分析

方案一:同步调用​编辑

方案二:异步调用​编辑

方案三:监听binlog​编辑

总结

实现elasticsearch与数据库同步

利用MQ实现mysql于elasticsearch中数据也要完成操作。

elasticsearch集群

搭建ES集群

ES集群结构

集群脑裂问题

ES集群的脑裂

总结

集权故障转移

集群分布式存储

集群分布式查询​编辑

elasticsearch的查询分成两个阶段:

总结

ES集群故障转移

总结


初识elasticsearch

了解ES

什么是elasticsearch

elasticsearch是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。

elasticsearch结合kibana,Logstash,Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析,实时监控等领域。

elasticsearch是elastic stack的核心,负责存储,搜索,分析数据。

Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,有DougCutting于1999年研发。官网地址:Apache Lucene - Welcome to Apache Lucene 。

Lucene的优势:

  1. 易扩展

  2. 高性能(基于倒排索引)

Lucene的缺点:

  1. 只限于Java语言开发

  2. 学习曲线陡峭

  3. 不支持水平扩展

elasticsearch的发展

2004年Shay Banon基于Lucene开发Compass

2010年Shay Banon重写了Compass,取名为Elasticsearch

官网地址:Elasticsearch 平台 — 大规模查找实时答案 | Elastic

相比与lucene,elasticsearch具备下列优势:

  1. 支持分布式,可水平扩展

  2. 提供Restful接口,可被任何语言调用

搜索引擎技术排名:

  1. Elasticsearch:开源的分布式搜索引擎

  2. Splunk:商业项目

  3. Solr:Apache的开源搜索引擎

总结

什么是elaticsearch?

一个开源的分布式搜索引擎,可以用来实现搜索,日志统计,分析,系统监控等功能

什么是elastic stack(ELK)?

是以elaticsearch为核心的技术栈,包括bears,Logstash,kibana,elaticsearch

什么是Lucene?

是Apache的开源搜索引擎类库,提供了搜索引擎的核心API

倒排索引

正向索引和倒排索引

正向索引

传统数据库(如MySQL)采用正向索引,例如给下表(tb_goods)中的id创建索引:

倒排索引

elasticsarch采用倒排索引:

  1. 文档(document):每条数据就是一个文档

  2. 词条(term):文档按照语义分成的词语

总结

什么是文档和词条?

  1. 每一条数据就是一个文档

  2. 对文档中的内容分词,得到的词语就是词条

什么是正向索引?

  1. 基于文档id创建索引,查询词条时必须先找到文档,而后判断是否包含词条

什么是倒排索引?

  1. 对文档内容分词,对词条创建索引,并记录词条所在文档的信息。查询时先更具词条查询到文档id,而后获取到文档

es的一些概念

文档

elasticsearch是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息。

文档数据会被序列化为json格式后存储在elasticsearch中。

索引

索引(index):相同类型的文档的集合

映射(mapping):索引中文档的字段约束信息,类似表的结构约束

概念对比

MySQLElasticsearch说明
TableIndex索引(index),就是文档的集合,类似数据库的表(table)
RowDocument文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式
ColumnField字段(Field),就是JSON文档中的字段,类似数据库中的列(column)
SchemaMappingMapping(映射)是索引中文档的约束,例如字段类型约束。类似数据的表结构(Schema)
SQLDSLDSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticserch,实现CRUD

架构

MySQL:擅长事物类型操作,可以确保数据的安全和一致性

Elaticsearch:擅长海量数据的搜索,分析,计算

总结

文档:一条数据就是一个文档,es中是Json格式

字段:Json文档中的字段

索引:同类型文档的集合

映射:索引中文档的约束,比如字段名称,类型

elasticaserch与数据库的关系:

  1. 数据库负责事物类型操作

  2. elasticsearch负责海量数据的搜索,分析,计算

安装es,kibana

安装es

  1. 创建网络

    因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络:

    docker network create es-net

  2. 加载镜像

    docker pull elasticsearch:7.12.1

  3. 运行es

    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

    命令解释:

    • -e "cluster.name=es-docker-cluster":设置集群名称

    • -e "http.host=0.0.0.0":监听的地址,可以外网访问

    • -e "ES_JAVA_OPTS=-Xms512m -Xmx512m":内存大小

    • -e "discovery.type=single-node":非集群模式

    • -v es-data:/usr/share/elasticsearch/data:挂载逻辑卷,绑定es的数据目录

    • -v es-logs:/usr/share/elasticsearch/logs:挂载逻辑卷,绑定es的日志目录

    • -v es-plugins:/usr/share/elasticsearch/plugins:挂载逻辑卷,绑定es的插件目录

    • --privileged:授予逻辑卷访问权

    • --network es-net :加入一个名为es-net的网络中

    • -p 9200:9200:端口映射配置

  4. 开放9200端口,访问端口

    8.137.59.245:9200

安装kibana

kibana可以给我们提供一个elasticsearch的可视化界面,便于我们学习。

  1. 部署kibana

    docker run -d \
    --name kibana \
    -e ELASTICSEARCH_HOSTS=http://es:9200 \
    --network=es-net \
    -p 5601:5601  \
    kibana:7.12.1
    • --network es-net :加入一个名为es-net的网络中,与elasticsearch在同一个网络中

    • -e ELASTICSEARCH_HOSTS=http://es:9200":设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch

    • -p 5601:5601:端口映射配置

    kibana启动一般比较慢,需要多等待一会,可以通过命令:

    docker logs -f kibana

    查看运行日志,当查看到下面的日志,说明成功:

    此时,在浏览器输入地址访问:http://192.168.150.101:5601,即可看到结果

安装分词器

分词器

es在创建倒排索引时需要对文档分词;在搜索时,需要对用户输入内容分词。但默认的分词规则对中文处理并不友好。

我们在kibana中DevTools中测试:

POST /_analyze
{
  "analyzer": "standard",
  "text": "黑马程序员学习java太棒了"
}

安装IK分词器

查看数据卷

上传ik安装包
重启docker容器
docker restart es
测试

IK分词器包含两种模式:

  1. ik_smart:最少切分

  2. ik_max_word:最细切分

    POST /_analyze
    {
      "analyzer": "ik_max_word",
      "text": "黑马程序员学习java太棒了"
    }

IK分词器扩展和停用词典

总结

分词器的作用是什么?

  1. 创建倒排索引时对文档分词

  2. 用户搜索时,对输入的内容分词

IK分词器又几种模式?

  1. ik_smart:智能切分,粗粒度

  2. ik_max_word:最细切分,细粒度

IK分词器如何扩展词条?如何停用词条?

  1. 利用config目录的IKAnalyzer.cfg.xml文件添加扩展词条和停用词典

  2. 在词典中添加扩展词条或者停用词条

索引库操作

mapping映射属性

mapping属性

mapping是对索引库中文档的约束,常见的mapping属性包括:

  1. type:字段数据类型,常见的简单类型有:

    1. 字符串:text(可分词的文本),keyword(精确值,例如:品牌,国家,ip地址)

    2. 数值:long,integer,short,byte,double,float

    3. 布尔:boolean

    4. 日期:date

    5. 对象:object

  2. index:是否创建索引,默认为true

  3. analyzer:使用哪种分词器

  4. properties:该字段的子字段

总结

mapping常见属性有哪些?

  1. type:数据类型

  2. index:是否索引

  3. analyzer:分词器

  4. prperties:子字段

type常见的有哪些?

  1. 字符串:text,keyword

  2. 数字:long,integer,short,byte,double,float

  3. 布尔:boolean

  4. 日期:date

  5. 对象:object

索引库CRUD

创建索引库

ES通过Restful请求操作索引库,文档。请求内容用DSL语句来表示。创建索引库和mapping的DSL语法如下:

#创建索引库
PUT /heima
{
  "mappings": {
    "properties": {
      "info" : {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "email":{
        "type" : "keyword",
        "index": false
      },
      "name" : {
        "type": "object",
        "properties": {
          "firstName" : {
            "type" : "keyword"
          },
          "lastName" : {
            "type" : "keyword"
          }
        }
      }
    }
  }
}

成功运行

查看,删除索引库

示例:查询

示例:删除

修改索引库

DELETE /heima
 
示例:修改

总结

索引库操作有哪些?

  1. 创建索引库:PUT/索引库名

  2. 查询索引库:GET/索引库名

  3. 删除索引库:DELETE/索引库名

  4. 添加字段:PUT/索引库名/_mapping (可以添加字段但不能修改以前的字段)

文档操作

新增文档

新增文档的DSL语法如下:

示例

#插入文档
POST /heima/_doc/1
{
  "info" : "黑马程序员Java讲师",
  "email" : "zy@itcast.cn",
  "name" : {
    "firstName" : "云",
    "lastName" : "赵"
  }
}

运行结果

{
  "_index" : "heima",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}
​

查询文档

DSL语法

示例
 #查询文档
GET /heima/_doc/1
​

运行结果

{
  "_index" : "heima",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "info" : "黑马程序员Java讲师",
    "email" : "zy@itcast.cn",
    "name" : {
      "firstName" : "云",
      "lastName" : "赵"
    }
  }
}

删除文档

DSL语法

示例
#删除文档
DELETE /heima/_doc/1

运行结果

{
  "_index" : "heima",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 2,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}

修改文档

方式一:全量修改,会删除旧文档,添加新文

示例
#全量修改文档
PUT /heima/_doc/1
{
  "info" : "黑马程序员Java讲师",
  "email" : "ZhaoYun@itcast.cn",
  "name" : {
    "firstName" : "云",
    "lastName" : "赵"
  }
}

运行结果(版本增加1)

{
  "_index" : "heima",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 3,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 4,
  "_primary_term" : 1
}

方式二:增加修改,修改指定字段值

示例
#局部修改文档字段
POST /heima/_update/1
{
  "doc" : {
    "email" : "ZYun@itcast.cn"
  }
}

运行结果

{
  "_index" : "heima",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 4,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 5,
  "_primary_term" : 1
}
总结

文档操作有哪些?

  1. 创建文档:POST /索引库名/_doc/文档id {json文档}

  2. 查询文档:GET /索引库名/_doc/文档id

  3. 修改文档:DELETE /索引库名/_doc/文档id

  4. 修改文档:

    1. 全量修改: PUT /索引库名/_doc/文档id {json文档}

    2. 增量修改: POST/索引库名/_update/文档id {"doc":{字段}}

RestAPI

什么是RestClient

ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址:Elasticsearch Clients | Elastic

RestClient操作索引库

mapping要考虑的问题:

字段名,数据类型,是否参与搜索,是否分词,如果分词,分词器是什么?

ES中支持两种地理

  1. geo_point:由纬度(latitude)和经度(longitude)确定的一个点。例如:"32.83232,120.233231"

  2. geo_shape:有多个geo_point组成的复杂几何图形。例如一条直线,"LINESTRING(-77.2344232 36.421231,-77.009099 38.8821384)"

字段拷贝可以使用copy_to属性将当前字段拷贝到指定字段。示例:

"business" : { ​ "type": "keyword", ​ "copy_to": "all" ​ }, "all" : { ​ "type": "text", ​ "analyzer": "ik_max_word" ​ }

初始化JavaRestClient

  1. 引入es的RestHighLeveClient依赖:

    <!--elasticsearch-->
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>7.12.1</version>
    </dependency>

  2. 因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:

    <properties>
        <java.version>1.8</java.version>
        <elasticsearch.version>7.12.1</elasticsearch.version>
    </properties>

  3. 初始化RestHighLevelClient:

    新建一个测试类

    public class HotelIndexTest {
        private RestHighLevelClient client;
    ​
        @Test
        void name(){
            System.out.println(client);
        }
    ​
        //初始化
        @BeforeEach
        void setUp(){
            this.client = new RestHighLevelClient(RestClient.builder(
                    HttpHost.create("http://8.137.59.245:9200")
            ));
        }
    ​
        //清理
        @Test
        @AfterEach
        void tearDown() throws IOException {
            this.client.close();
        }
    ​
    }

创建索引库

编写DSL语句

public class HotelConstants {
    public static final String MAPPING_TEMPLATE = "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\" : {\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"name\" : {\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"addres\" : {\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"price\" : {\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"score\" : {\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"brand\" : {\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"city\" : {\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"starName\" : {\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            " \n" +
            "      \"location\" : {\n" +
            "        \"type\": \"geo_point\"\n" +
            "      },\n" +
            "      \"pic\" : {\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"all\" : {\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\"\n" +
            "      }\n" +
            "    }\n" +
            "  }\n" +
            "}";
}

编写测试类

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

查看运行结果

删除索引库

//    删除索引
    @Test
    void testDeleteHotelIndex() throws IOException {
        //1.创建Request对象
        DeleteIndexRequest request = new DeleteIndexRequest("hotel");
​
        //2.发送请求
        client.indices().delete(request, RequestOptions.DEFAULT);
    }

运行结果

判断索引库是否存在

//    判断索引是否存在
    @Test
    void testExistHotelIndex() throws IOException {
        //1.创建Request对象
        GetIndexRequest request = new GetIndexRequest("hotel");

        //2.发送请求
        boolean exist = client.indices().exists(request, RequestOptions.DEFAULT);

        //3.判断结果
        System.out.println(exist ? "索引库已经存在!" : "索引库不存在!");
    }

运行结果

总结

索引库操作的基本步骤:

  1. 初始化RestHighLevelClient

  2. 创建XxxIndexRequest。xxx是CREATE,Get,Delete

  3. 准备DSL(CREATE时需要)

  4. 发送请求。调用RestHighLevelClient#indices().xx()方法,xxx时create, exists, delete

RestClient操作文档

基本步骤

利用JavaRestClient实现文档的CRUD

去数据库查询酒店数据,导入到hotel索引库,实现酒店数据的CRUD。

基本步骤如下:

  1. 初始化JavaRestClient

  2. 利用JavaRestClient新增酒店数据

  3. 利用JavaRestClient根据id查询酒店数据

  4. 利用javaRestClient删除酒店数据

  5. 利用JavaRestClient修改酒店数据

新增文档

先查询酒店数据,然后给这条数据创建倒排索引,即可完成添加:

示例
//RestClient的新增数据
@Test
void testAddDocument() throws IOException {
    // 根据id查询酒店数据
    Hotel hotel = hotelService.getById(39141L);
    // 转换为文档类型
    HotelDoc hotelDoc = new HotelDoc(hotel);
​
    //1.准备Request对象
    IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
​
    //2.准备Json文档
    request.source(JSON.toJSONString(hotelDoc),XContentType.JSON);
​
    //3.发送请求
    client.index(request,RequestOptions.DEFAULT);
​
}
运行结果

查询文档

根据id查询到文档数据是json,需要反序列化为java对象:

示例
//RestClient的查询数据
@Test
void testGetDocument() throws IOException {
    // 1.准备Request

    GetRequest request = new GetRequest("hotel", "39141");
    // 2.发送请求,得到响应
    GetResponse response = client.get(request, RequestOptions.DEFAULT);
    // 3.解析响应结果
    String json = response.getSourceAsString();

    HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
    System.out.println(hotelDoc);

}
运行结果

修改文档

修改文档数据有两种方式:

方式一:全局更新。再次写入id一样的文档,就会删除旧文档,添加新文档

方式二:局部更新。只跟新局部字段,我们演示方式二

示例
//RestClient的修改数据
@Test
void testUpdateDocument() throws IOException {
    // 1.准备Request
    UpdateRequest request = new UpdateRequest("hotel", "39141");
​
    // 2.准备请求参数
    request.doc(
            "price" , "952",
            "starName" , "四钻"
    );
​
    // 3.发送请求
    client.update(request, RequestOptions.DEFAULT);
}
运行结果
修改前
修改后

删除文档

示例
//RestClient的删除数据
@Test
void testDeleteDocument() throws IOException {
    //准备Request
    DeleteRequest request = new DeleteRequest("hotel", "39141");
    //发送请求参数
    client.delete(request, RequestOptions.DEFAULT);
}
运行结果
删除前
删除后
总结

文档操作的基本步骤

  1. 初始化RestHighLevelClient

  2. 创建XxxRequest。XXX是Index,Get,Update,Delete

批量导入文档

利用JavaRestClient批量导入索引库中

思路:

  1. 利用mybatis-plus查询酒店数据

  2. 将查询到的酒店数据(Hotel)转换为文档类型数据(HotelDoc)

  3. 利用JavaRestClient总的Bulk批量处理,实现批量新增文档

    //批量导入es
    @Test
    void testBulkRequest() throws IOException {
        // 批量查询酒店数据
        List<Hotel> hotels = hotelService.list();
    ​
    ​
        // 1.创建Request
        BulkRequest request = new BulkRequest();
    ​
        // 2.准备参数,添加多个新增的Request
        //转换为文档类型HotelDoc
        for (Hotel hotel : hotels) {
            HotelDoc hotelDoc = new HotelDoc(hotel);
            //创建新增文档的Request对象
            request.add(new IndexRequest("hotel")
                    .id(hotelDoc.getId().toString())
                    .source(JSON.toJSONString(hotelDoc), XContentType.JSON));
    ​
        }
        // 3.发送请求
        client.bulk(request, RequestOptions.DEFAULT);
    }

    运行结果

DSL查询文档

DSL查询分类

Elasticashearch提供了基于JSON的DSL来定义查询。常见查询类型包括:

  1. 查询所有:查询出所有数据,一般测试用。例如:mathc_all

  2. 全文检索(full text)查询:利用分词器对用户输入内容分词,然后倒排索引库中匹配。例如:

    1. match_query

    2. multi_match_query

  3. 精准查询:根据精准词条查找数据,一般是查找keyword,数值,日期,boolean等类型字段。例如

    1. ids

    2. range

    3. term

  4. 地理(geo)查询:根据经纬度查询。例如

    1. geo_distance

    2. geo_bounding_box

  5. 复合(compound)查询:复合查询可以将上述各种条件组合起来,合并查询条件。例如:

    1. bool

    2. funcation_score

DSL Query基本语法
#查询所有
GET /hotel/_search
{
  "query": {
    "match_all": {}
  }
}

运行结果

总结

查询DSL的基本语法是什么?

  1. GET /索引库名/_search

  2. { "query" : { "查询类型" : { "FIELD" : "TEXT" } } }

全文检索查询

match查询:

全文检索查询的一种,会对用户输入内容分词,然后去倒排索引检索,语法:

# match查询
GET /hotel/_search
{
  "query": {
    "match": {
      "all": "外滩如家"
    }
  }
}
multi_match查询:

与match查询类似,只不过允许同时查询多个字段,语法:

# multi_match查询
GET /hotel/_search
{
  "query": {
    "multi_match": {
      "query": "外滩如家",
      "fields": ["brand","name","business"]
    }
  }
} 
总结

match和multi_match的区别是什么?

  1. match:更具一个字段查询

  2. muti_match:根据多个字段查询,参与查询字段越多,查阅性能越差

精准查询

准确查询一般是查找keyword,数值,日期,boolean等类型字段。所以不会对搜索条件分词。常见的有:

term查询:
#term查询
GET /hotel/_search
{
  "query": {
    "term": {
      "city": {
        "value": "上海"
      }
    }
  }
}

range查询:
# range查询
GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 100,
        "lte": 200
      }
    }
  }
}

总结

精确查询常见的有哪些?

  1. term查询:根据词条精确匹配,一般搜索keywored类型,数值类型,布尔类型,日期类型字段

  2. range查询:根据数值查询范围,可以是数值,日期的范围

地理坐标查询

根据经纬度查询。常见的使用场景包括:

  1. 携程:搜索我的附近的酒店

  2. 滴滴:搜索我附近的出租车

  3. 微信:搜索我附近的人

根据经纬度查询。例如:

geo_bounding_box:

查询geo_point值落在某个矩形范围所有文档

geo_distance:

查询到指定中心点小于某个距离值的所有文档

# distance查询
GET /hotel/_search
{
  "query": {
    "geo_distance": {
      "distance": "5km",
      "location": "31.21, 121.5"
    }
  }
}

复合查询

复合(compound)查询:复合查询可以将其他简单查询组合起来,实现更复杂的搜索逻辑,例如:

function score

算分函数查询,可以控制文档相关性算分,控制文档排名。例如百度竞价

当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时安装分值降序排列。

例如,我们搜索”虹桥如家“,结果如下:

使用function score query,可以修改文档的相关性算分(query score),根据新得到得算分排序。

# function score查询
GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {
        "match": {
          "all": "外滩"
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "brand": "如家"
            }
          },
          "weight": 10
        }
      ],
      "boost_mode": "sum"
    }
  }
}
总结

function score query定义得三要素是什么?

  1. 过滤条件:哪些文档要加分

  2. 算分函数:如何计算function score

  3. 加权方式:function score 与 query score如何运算

Bloolean Query

参与算分越多,越影响性能。

布尔查询是一个或多个子句得组合。子查询得组合方式有:

  1. must:必须匹配每个子查询,类似 ”与“

  2. should:选择性匹配子查询,类似 ”或“

  3. must_not:必须不匹配,不参与算法,类似”非“

  4. filter:必须匹配,不参与算分

案例:

利用bool查询名字包含 ”如家“,价格不高于400,在坐标31.21,121.5周围10km范围内的酒店。

 # bool 查询
 GET /hotel/_search
 {
   "query": {
     "bool": {
       "must": [
         {
           "match": {
             "name": "如家"
           }
         }
       ],
       "must_not": [
         {
           "range": {
             "price": {
               "gt": 400
             }
           }
         }
       ],
       "filter": [
         {
           "geo_distance": {
             "distance": "10km",
             "location": {
               "lat": 31.21,
               "lon": 121.5
             }
           }
         }
       ]
     }
   }
 }

总结

elasticsearch中的相关性打分算法时什么?

  1. TF-IDF:在elasticserch5.0之前,会随着词频增加反而越来越大

  2. BM25:在elasticsearch5.0之后,会随着词频增大而增大,但增长曲线会趋于水平

搜索结果处理

排序

elaticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序。可以排序字段类型有:keyword类型,数值类型,地理坐标类型,日期类型等。

示例:对酒店数据按照用户评价降序排序,评价相同的按照价格升序排序
# sort排序
GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "score": "desc"
    },
    {
      "price": "asc"
    }
  ]
}
示例:实现对酒店数据按照你的位置坐标的距离升序排序

获取经纬度的方式:获取鼠标点击经纬度-地图属性-示例中心-JS API 2.0 示例 | 高德地图API

 
 # 找到121.612282,31.034661周围的酒店,距离升序排序
GET /hotel/_search
{
  "query": {
    "match_all": {
    }
  },
    "sort" : [
      {
       "_geo_distance":{
         "location":{
           "lat": "31.034661",
           "lon": "121.612282"
         },
         "order": "asc",
         "unit" : "km"
      }
    }
  ]
}

分页

elatcsearch默认情况下只返回top10的数据,而如果要查询更多数据就需要修改分页参数了。

elatcsearch中通过修改from,size参数来控制返回的分页结果:

# 分页查询
GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "price": "asc"
    }
  ],
  "from": 10,
  "size": 10
}

深度ES是分布式的,所以会面临深度分页问题。例如按price排序后,后去from = 990,size = 10的数据:

  1. 首先在每个数据分片上都排序并查询前1000条文档。

  2. 然后将所有结点的结果聚合,在内存中重新排序选出前1000条文档

  3. 最后从这1000条中,选取从990开始的10条文档

如果搜索页数过深,或者结果集(from+size)越大,对内存和CPU的消耗越高。硬扯ES设定结果查询上限时10000

针对深度分页,ES提供了两种解决方案:
  1. search after:分页时需要排序,原理上是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。

  2. scroll:原理将排序数据形成快照,保存在内存。官方已经不推荐使用。

总结

from + size:

  1. 优点:支持随机翻页

  2. 缺点:深度分页问题,默认查询上限(from+size)是10000

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

after search:

  1. 优点:没有查询上线(单次查询的size不超过10000)

  2. 缺点:智能下岗后逐页查询,不支持随机翻页

  3. 场景:没有随机分页需求的搜索,例如手机向下滚动翻页

scroll:

  1. 优点:没有查询上限(单次查询的size不超过10000)

  2. 缺点:会有额外内存损耗,并且搜索结果是非实时的

  3. 场景:海量数据的获取和迁移。重ES7.1开始不推荐使用,建议用after search 方案

高亮

高亮:就是在搜索结果中把收索关键字突出显示。

原理是这样的:

  1. 将搜索结果中的关键字用标签标记出来

  2. 在页面中给标签添加css样式

语法:

总结

RestClient查询文档

快速入门
我们通过match_all来演示下基本api,先看DSL的组织:
代码
@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);
​
    System.out.println(response);
}
运行结果
我们通过match_all来演示下基本的API,再看结果的解析:
代码
@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);

    System.out.println(response);

    //4.解析响应
    SearchHits searchHits = response.getHits();

    //4.1. 获取总条数
    long total = searchHits.getTotalHits().value;
    System.out.println("共搜索到"+ total+"条数据");

    //4.2. 文档数组
    SearchHit[] hits = searchHits.getHits();
    //4.3. 便利
    for (SearchHit hit : hits){
        // 获取文档source
        String json = hit.getSourceAsString();
        //反序列化
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        System.out.println("hotelDoc"+hotelDoc);
    }
    System.out.println(response);
}
运行结果

RestAPI其中构建DSL是通过HighLevelRestClient中的resource()来实现的,其中包含了查询,排序,分页,高亮等所有功能

RestAPI中其中构建查询条件的核心是由一个名为QueryBuilders的工具类提供的,其中包含了各种查询方法:

总结
  1. 创建SearchRequest对象

  2. 准备Request.source(),也就是DSL.

    1. QueryBuilders来构建查询条件

    2. 传入Request.source()的query()方法

  3. 发送请求,得到结果

  4. 解析结果(参考JSON结果,从外到内,逐层解析)

match查询
代码
@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);

    System.out.println(response);

    extracted(response);

    System.out.println(response);
}

private void extracted(SearchResponse response) {
    //4.解析响应
    SearchHits searchHits = response.getHits();

    //4.1. 获取总条数
    long total = searchHits.getTotalHits().value;
    System.out.println("共搜索到"+ total+"条数据");

    //4.2. 文档数组
    SearchHit[] hits = searchHits.getHits();
    //4.3. 便利
    for (SearchHit hit : hits){
        // 获取文档source
        String json = hit.getSourceAsString();
        //反序列化
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        System.out.println("hotelDoc"+hotelDoc);
    }
}
运行结果

精确查询

复合查询

精确查询常见的有term查询和range查询,同样利用QueryBuilders实现:

​
    @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(250));
​
        request.source().query(boolQuery);
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
​
        System.out.println(response);
​
        extracted(response);
    }
​
    private void extracted(SearchResponse response) {
        //4.解析响应
        SearchHits searchHits = response.getHits();
​
        //4.1. 获取总条数
        long total = searchHits.getTotalHits().value;
        System.out.println("共搜索到"+ total+"条数据");
​
        //4.2. 文档数组
        SearchHit[] hits = searchHits.getHits();
        //4.3. 便利
        for (SearchHit hit : hits){
            // 获取文档source
            String json = hit.getSourceAsString();
            //反序列化
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            System.out.println("hotelDoc"+hotelDoc);
        }
    }

总结

要构建查询条件,只要记住一个类:QueryBuilders

排序和分页
代码
@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. 排序 sort
    request.source().sort("price", SortOrder.ASC);
    // 2.3. 分页 from,size
    request.source().from((page-1)*size).size(5);
​
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    System.out.println(response);
    extracted(response);
}
运行结果

高亮

高亮API包括请求DSL构建和结果解析两部分,我们先看请求的DSL构建:

高亮结果处理
代码
@Test
void testHighlight() throws IOException {
    // 1.准备Request
    SearchRequest request = new SearchRequest("hotel");
    // 2.准备DSL
    // 2.1. 准备Query
    request.source().query(QueryBuilders.matchQuery("all","如家"));
    // 2.2. 高亮
    request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    System.out.println(response);
    handleResponse(response);
}
​
private void handleResponse(SearchResponse response) {
    //4.解析响应
    SearchHits searchHits = response.getHits();
​
    //4.1. 获取总条数
    long total = searchHits.getTotalHits().value;
    System.out.println("共搜索到"+ total+"条数据");
​
    //4.2. 文档数组
    SearchHit[] hits = searchHits.getHits();
    //4.3. 遍历
    for (SearchHit hit : hits){
        // 获取文档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("hotelDoc"+hotelDoc);
    }
}
运行结果
总结
  1. 所有的搜索DSL的构建,记住一个API:SearchRequest的source()方法

  2. 高亮结果解析是参考JSON结果,逐层解析

黑马旅游案例

案例1:实现黑马旅游的酒店搜索功能,完成关键字搜索和分页

步骤:
  1. 定义实体类,接收前端请求

  2. 定义controller接口,接收页面请求,调用IHotelService的search方法

  3. 定义IHotelService中的search方法,利用match查询实现根据关键字搜索酒店信息

@Override
public PageResult search(RequestParams params) {
    try {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备DSL
        // 2.1 query
        String key = params.getKey();
        if (key == null || key.trim().length() == 0){
            request.source().query(QueryBuilders.matchAllQuery());
        } else {
            request.source().query(QueryBuilders.matchQuery("all",key));
        }
        // 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);
        System.out.println(response);
        handleResponse(response);
        return handleResponse(response);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
​
​
private PageResult handleResponse(SearchResponse response) {
    //4.解析响应
    SearchHits searchHits = response.getHits();
    //4.1. 获取总条数
    long total = searchHits.getTotalHits().value;
    //4.2. 文档数组
    SearchHit[] hits = searchHits.getHits();
    //4.3. 遍历
    List<HotelDoc> hotels = new ArrayList<>();
    for (SearchHit hit : hits){
        // 获取文档source
        String json = hit.getSourceAsString();
        //反序列化
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        hotels.add(hotelDoc);
​
    }
    // 4.4.封装返回
    return new PageResult(total, hotels);
​
}

案例2:添加品牌,城市,星际,价格等过滤功能

步骤:
  1. 修改RequestParams类,添加brand,city,starName,minPrice,maxPrice等参数

  2. 修改search方法的实现类,再关键字搜索时,如果brand等参数存在,对其做过滤

    1. city精确匹配

    2. brand精确匹配

    3. starNmae精确匹配

    4. price范围过滤

    5. 注意事项

      1. 多个条件之间时AND关系,组合多条件用BooleanQuery

      2. 参数存在才需要过滤,做好非空判断

@Autowired
private RestHighLevelClient client;
​
@Override
public PageResult search(RequestParams params) {
    try {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备DSL
        // 2.1 query
        // 构建BooleanQuery
        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 (IOException e) {
        throw new RuntimeException(e);
    }
}
​
private void buildBasicQuery(RequestParams params,SearchRequest request) {
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    //关键字搜索
​
    String key = params.getKey();
    if (key == null || "".equals(key)){
        boolQuery.must(QueryBuilders.matchAllQuery());
    } else {
        boolQuery.must(QueryBuilders.matchQuery("all",key));
    }
    // 城市条件
    if (params.getCity() != null && !params.getCity().equals("")){
        boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
    }
    // 品牌条件
    if (params.getBrand() != null && !params.getBrand().equals("")){
        boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
    }
    // 星级条件
    if (params.getStarName() != null && !params.getStarName().equals("")){
        boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
    }
    // 价格条件
    if (params.getMinPrice() != null && !params.getMinPrice().equals("")){
        boolQuery.filter(QueryBuilders
                .rangeQuery("price").gte(params.getMaxPrice()).lte(params.getMaxPrice()));
    }
    request.source().query(boolQuery);
}
​
​
private PageResult handleResponse(SearchResponse response) {
    //4.解析响应
    SearchHits searchHits = response.getHits();
    //4.1. 获取总条数
    long total = searchHits.getTotalHits().value;
    //4.2. 文档数组
    SearchHit[] hits = searchHits.getHits();
    //4.3. 遍历
    List<HotelDoc> hotels = new ArrayList<>();
    for (SearchHit hit : hits){
        // 获取文档source
        String json = hit.getSourceAsString();
        //反序列化
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        hotels.add(hotelDoc);
​
    }
    // 4.4.封装返回
    return new PageResult(total, hotels);
​
}

案例3:我附近的酒店

步骤
  1. 前端页面定位后,会将你所有的位置发送到后台:

  2. 我们根据这个坐标,将酒店结果按照这个点的距离升序排序。

  3. 思路如下:

    1. 修改RequestParams参数,接收location字段

    2. 修改search方法业务逻辑,如果location有值,添加根据geo_distance排序的功能

案例4:让指定的酒店再搜索中排名位置置顶

步骤

我们给需要置顶的酒店文档添加一个标记。然后利用function score给带有标记的文档增加权重。

实现步骤分析:

  1. 给HotelDoc类添加isAD字段,Boolean类型

  2. 挑选几个你喜欢的酒店,给它的文档数据添加isAD字段,值为true

  3. 修改search方法,添加function score功能,给isAD值为true的酒店增加权重

数据聚合

聚合的种类

聚合分类

聚合(aggregatons)可以实现对文档数据的统计,分析,运算。聚合常见的有三类:

  1. 桶(Bucket)聚合:用来对文档做分组

    1. TermaAggregation:按照文档字段值分组

    2. Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

  2. 度量(Metric)聚合:用以计算一些值,比如:最大值,最小值,平均值等

    1. Avg:求平均值

    2. Max:求最大值

    3. Min:求最小值

    4. Stats:同时求max,min,avg,sum等

  3. 管道(pipeline)聚合:其它聚合的结果为基础做聚合

总结
什么是聚合?

聚合是对文档数据的统计,分析,计算

聚合的常见种类有哪些?
  1. Bucket:对文档数据分组,并统计每组数量

  2. Meric:最文档数做计算,例如avg

  3. Pipeline:基于其他聚合结果在做聚合

参与聚合的字段类型必须是:
  1. keword

  2. 数值

  3. 日期

  4. 布尔

DSL实现聚合

DSL实现Bucket聚合

现在,我们要统计所有数据中的酒店品牌有几种,此时可以根据酒店品牌的名称做聚合。

类型为term类型,DSL示例:

默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照 _count升序排序。

我们可以修改结果排序方式:

#聚合功能,自定义排序规则
​
GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 20,
        "order": {
          "_count": "asc"
        }
      }
    }
  }
}

默认情况下,Bucket聚合是对索引库的所有文档做聚合,我们可以根据聚合的文档范围,只要添加query条件即可:

#聚合功能,限定聚合范围
GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "lte": 200
      }
    }
  }, 
  "size": 0,
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 20
      }
    }
  }
}
总结

aggs代表聚合,与query同级,此时query的作用是?

限定聚合的文档范围

聚合必须的三要素

  1. 聚合名称

  2. 聚合类型

  3. 聚合字段

聚合可配置属性有:

  1. size:指定聚合结果数量

  2. order:指定聚合结果排序方式

  3. field:指定聚合字段

DSL实现Metrics聚合

例如,我们要求获取每个品牌的用户评分的min,max,avg等值。

我们可以利用stats聚合:

#嵌套聚合metric
GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 20,
        "order": {
          "scoreAgg.avg": "desc"
        }
      },
      "aggs": {
        "scoreAgg": {
          "stats": {
            "field": "score"
          }
        }
      }
    }
  }
}

RestAPI实现聚合

我们以品牌聚合为例,演示以下Java的RestClient使用,先看请求组装:

再看下聚合结果解析

​
    @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();
        Terms brandTerms = aggregations.get("brandAgg");
        List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
        // 4.3遍历
        for (Terms.Bucket bucket : buckets) {
            String key = bucket.getKeyAsString();
            System.out.println(key);
        }
    }

案例:在IUserService中定义方法,实现对品牌,城市,星级的聚合

需求:在搜索页面的品牌,城市等信息不应该是在页面写死,而是通过聚合索引库中的酒店数据得来的:


@Override
public Map<String, List<String>> filters() {
    try {
        // 1. 准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2. 准备DSL
        // 2.1 设置size
        request.source().size(0);
        // 2.2. 聚合
        buildAggregation(request);
​
        // 3. 发出请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
​
        Map<String, List<String>> result = new HashMap<>();
        // 4. 解析结果
        Aggregations aggregations = response.getAggregations();
        // 4.1. 根据品牌名称,获取品牌结果
        List<String> brandList = getAggByName(aggregations,"brandAgg");
        // 4.4. 放入map
        result.put("品牌",brandList);
​
        List<String> cityList = getAggByName(aggregations,"cityAgg");
        // 4.4. 放入map
        result.put("城市",cityList);
​
        List<String> starList = getAggByName(aggregations,"starAgg");
        // 4.4. 放入map
        result.put("星级",starList);
        return result;
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
​
private List<String> getAggByName(Aggregations aggregations,String aggName) {
    // 4.1. 根据聚合名称获取聚合结果
    Terms brandTerms = aggregations.get(aggName);
    // 4.2. 获取buckets
    List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
    // 4.3遍历
    List<String> brandList = new ArrayList<>();
    for (Terms.Bucket bucket : buckets) {
        String key = bucket.getKeyAsString();
        System.out.println(key);
        brandList.add(key);
    }
    return brandList;
}
​
private void buildAggregation(SearchRequest request) {
    request.source().aggregation(AggregationBuilders
            .terms("brandAgg")
            .field("brand")
            .size(100)
    );
    request.source().aggregation(AggregationBuilders
            .terms("cityAgg")
            .field("city")
            .size(100)
    );
    request.source().aggregation(AggregationBuilders
            .terms("starAgg")
            .field("starName")
            .size(100)
    );
}

前端页面会向服务端发起请求,查询品牌,城市,星级等字段的聚合结果:

自动补全

拼音分词器

要实现根据字母做补全,就必须对文档按照拼音分词。在GiHub上恰好有elasticsearch的拼音分词插件。地址:

GitHub - infinilabs/analysis-pinyin: 🛵 This Pinyin Analysis plugin is used to do conversion between Chinese characters and Pinyin.

测试

自定义分词器

elasticsearch中分词器(analyzer)的组成包含三部分:

  1. character fiters:在tokenizer之前对文本进行处理。例如删除字符,替换字符

  2. tokenizer:将文本按照按照一定的规则切割词条(term)。例如keyword,就是不分词;还有ik_smart

  3. tokenizer filter :将tokenizer输出的词条做进一步处理。例如大小写转换,同义词处理,拼音处理等

拼音分词器适合在创建倒排索引的时候使用,但不能在搜索的时候使用。

# 自定义分词器
PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "ik_max_word",
          "filter": "py"
        }
      },
      "filter": {
        "py": {
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize":false
          
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "my_analyzer"
        , "search_analyzer": "ik_smart"
      }
    }
  }
}
​
POST /test/_doc/1
{
  "id": 1,
  "name": "狮子"
}
​
POST /test/_doc/2
{
  "id": 2,
  "name": "虱子"
}
​
GET /test/_search
{
  "query": {
    "match": {
      "name": "调入狮子笼子咋办"
    }
  }
}
总结

如何使用拼音分词器?

  1. 下载pinyin分词器

  2. 解压并放到elasticsearch的plugin目录

  3. 重启即可

如何自定义分词器?

  1. 创建索引库,在settings中配置,可以包含三部分

  2. character filter

  3. tokenizer

  4. filter

拼音分词器注意思事项?

为了避免搜索到同音字,搜索时不要使用拼音分词器

自动补全查询

completion suggester查询

elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:

  1. 参与补全查询的字段必须时completion类型。

  2. 字段的类容一般是用来补全的多词条形成的数组

#自动补全索引库
PUT test2
{
  "mappings": {
    "properties": {
      "title":{
        "type": "completion"
      }
    }
  }
}
​
# 示例数据
POST test2/_doc
{
  "title" : ["Sony", "WH-1000XM3"]
}
POST test2/_doc
{
  "title": ["SK-II", "PITERA"]
}
POST test2/_doc
{
  "title": ["Nintendo", "switch"]
}
​
# 自动补全查询
GET /test2/_search
{
  "suggest": {
    "titleSuggest": {
      "text": "s",
      "completion": {
        "field": "title",
        "skip_duplicates": true,
        "size": 10
      }
    }
  }
}
总结

自动补全对字段的要求:

  1. 类型是completion类型

  2. 字段值是多词条的数组

实现酒店搜索框自动补全

实现思路如下:

  1. 修改hotel索引库结构,设置自定义拼音分词器

  2. 修改索引库的name,all字段,使用自定义分词器

  3. 索引库添加一个新字段suggestion,类型为completion类型,使用自定义的分词器

  4. 给HoteDoc类添加suggestio字段,内容包含brand,susiness

  5. 重新导入数据到hotel库

@Override
public List<String> getSuggestions(String prefix) {
    try {
        // 1. 准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2. 准备SDL
        request.source().suggest(new SuggestBuilder().addSuggestion(
                "suggestions",
                SuggestBuilders.completionSuggestion("suggestion")
                        .prefix(prefix)
                        .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. 遍历
        List<String> list = new ArrayList<>(options.size());
        for (CompletionSuggestion.Entry.Option option : options){
            String text = option.getText().toString();
            list.add(text);
        }
        return list;
    } catch (IOException e) {
        throw new RuntimeException();
    }
}

RestAPI实现自动补全

先看请求参数构造API

@Test
void testSuggest() throws IOException {
    // 1. 准备Request
    SearchRequest request = new SearchRequest("hotel");
    // 2. 准备SDL
    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. 解析响应结果
    System.out.println(response);
}

自动补全

数据同步

数据同步思路分析

elasticsearch中的酒店数据来自于mysql数据库,因此mysql数据发生改变时,elasticsearch也必须跟着改变,这个就是elasticserch于mysql之间的数据同步。

方案一:同步调用

方案二:异步调用

方案三:监听binlog

总结

方式一:同步调用

  1. 优点:实现简单,粗暴

  2. 缺点:业务耦合度高

方式二:异步通知

  1. 优点:低耦合,实现难度一般

  2. 缺点:依赖mq的可靠性

方式三:监听binlog

  1. 优点:完全解除服务耦合

  2. 缺点:开启binlog增加数据库负担,实现复杂度高

实现elasticsearch与数据库同步

利用课前资料提供的hotel-admin项目作为酒店管理的微服务。当酒店数据发生增,删,查,改时,要求对elasticsearch中数据也要完成相同操作。

利用MQ实现mysql于elasticsearch中数据也要完成操作。

步骤:

  1. 导入课前资料提供的hotel-admin项目,启动并测试酒店数据的CRUD

  2. 声明exchange,queue,RoutingKey

  3. 在hotel-admin中的增,删,改业务中完成发送消息

  4. 在hotel-demo中完成消息监听,并更新elasticsearch中数据

  5. 启动并测试数据同步功能

elasticsearch集群

搭建ES集群

ES集群结构

单机的elasticsearch做数据存储,必然面临两个问题:海量数据存储问题,单点故障问题。

  1. 海量数据存储问题:将索引从逻辑上拆分为N个分片(shard),存储到多个结点

  2. 单点故障问题:将分片数据在不同结点备份(replica)

文件资料里有对应文档。

集群脑裂问题

elasticsearch中集群结点有不同的职责划分:

结点类型配置参数默认值节点职责
master eligiblenode.mastertrue备选主节点:主节点可以管理和记录集群状态,决定分片在哪个节点,处理创建和删除索引库的请求
datanode.datatrue数据节点:存储数据,搜索,聚合,CRUD
ingestnode.ingesttrue数据存储之前的预处理
coordinating上面3个参数都是为false则为coordinating节点路由请求到其他节点,合并其它节点处理的结果,返回给用户

ES集群的脑裂

默认情况下,每个节点都是master eligible节点,因此一旦master节点宕机,其它候选节点会选举一个成为主节点。当主节点于其它节点网路解耦故障时,可能发生脑裂问题。

为了避免脑裂,需要要求选票超过(eligible节点数量+1)/ 2 才能当选为主节点,因此eligible节点数量组好是奇数。对于配置项discovery.zen.minimum_master_nodes,在es7.0以后,已经成为我们配置,因此一般不会发生脑裂问题

总结

master eligible节点的作用是上面?

  1. 参与集群选主

  2. 主节点可以管理集群状态,管理分片信息,处理创建和删除索引库的请求

  3. data节点的作用是上面?

data节点的作用是上面?

  1. 数据的CRUD

coordinator节点的作用是什么?

  1. 路由请求到其它节点

  2. 合并查询到的结果,返回给用户

集权故障转移

集群分布式存储

当新增文档时,应该保存不同分片,保证数据均衡,那么coordinating node如何确定数据存储到哪个分片呢?

elasticsearch会通过hash算法来计算文档应该存储到哪个分片:

shard = hash(_routing) % number_of_shards

说明:

  1. _routing默认是文旦的id

  2. 算法与分片数量有关,因此索引库一旦创建,分片数量不能修改!

集群分布式查询

elasticsearch的查询分成两个阶段:

  1. scatter phase:分散阶段,coordinating node会把请求分发到每一个分片

  2. gather phase:聚集阶段,coordinating node汇总data node的搜索结果,并处理为最终结果集返回给用户

总结

分布式新增如何确定分片?

  1. coordinating node根据id做hash运算,得到结果对shard数量取余,余数就是对应的分片

分布式查询:

  1. 分散阶段:coordinating node将查询请求分发给不同分片

  2. 收集阶段:将查询结果汇总到coordinating node,整理并返回给用户

ES集群故障转移

集群的master节点会监控集群中的节点状态,如果发现有节点宕机,会立即将宕机节点的分片数据迁移到其它急待你,确保数据安全,这个叫做故障转移

总结

故障转移

  1. master宕机后,EligibleMaster选举为新的主节点。

  2. master节点监控分片,阶段状态,将故障节点上的分片转移到正常节点,确保数据安全。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/424383.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在网页上踢球:打造我自己的python(Django)足球网站

足球不仅仅是球场上的90分钟。它是一个不断发展的故事&#xff0c;一个全球球迷社群的粘合剂&#xff0c;一个数据和热情交织的世界。作为一名开发者和球迷&#xff0c;我决定将这两大爱好结合起来&#xff0c;用 Django 打造一个足球网站&#xff0c;让球迷们能够追踪他们最爱…

守护无价数据:文件备份的重要性与实用策略

一、数据安全&#xff1a;为何文件备份至关重要 在数字化时代&#xff0c;我们的生活和工作越来越离不开电子设备与其中的文件数据。这些文件可能包含重要的工作文档、珍贵的家庭照片、个人的创意作品等&#xff0c;它们是我们回忆的载体&#xff0c;也是我们工作和创新的基石…

xss.haozi.me靶机练习

目录 第零关&#xff1a; 第一关&#xff1a; 第二关&#xff1a; 第三关&#xff1a; 第四关&#xff1a; 第五关&#xff1a; 第六关&#xff1a; 第七关&#xff1a; 第八关&#xff1a; 第九关&#xff1a; 第十关&#xff1a; 第十一关&#xff1a; 第十二关…

百度SEO快排原理是什么?如何快速排名方法?

前言&#xff1a;我之前说过我不打算写这个快速排序。 首先&#xff0c;我从来没有在自己的网站上操作过所谓的快速排序。 其次&#xff0c;我不能像网上很多人写的那样透露百度快速排序的秘密&#xff08;说实话&#xff0c;你可以透露秘密&#xff09;。 方法是有了&#xff…

unity 数学 如何计算线和平面的交点

已知一个平面上的一点P0和法向量n&#xff0c;一条直线上的点L0和方向L,求该直线与该平面的交点P 如下图 首先我们要知道向量归一化点乘之后得到就是两个向量的夹角的余弦值&#xff0c;如果两个向量相互垂直则值是0&#xff0c;小于0则两个向量的夹角大于90度&#xff0c;大于…

Python3零基础教程之条件控制语句

大家好,我是千与编程,今天我们讲解Python3零基础教程中的If-else条件控制语句,这是Python3语言编程中非常重要的部分,是程序代码有逻辑性和决策性的根本。 尤其针对刷算法题的几乎每一道题都会使用,以下是本次教程内容的思维导图: 一、基本的条件控制语句 在Python 3中…

Jenkins的安装和helloworld Pipeline

文章目录 环境安装下载安装启动初始化 PipelineUISCM&#xff08;Source Control Management&#xff09;准备pipeline 参考 环境 RHEL 9.3Jenkins 2.44.0.1 安装 参考 https://www.jenkins.io/doc/book/installing/linux/#red-hat-centos 。 下载安装 [ding192 ~]$ sudo …

2024新算法:鹅算法优化VMD参数,五种适应度函数任意切换,最小包络熵、样本熵、信息熵、排列熵、排列熵/互信息熵...

本期采用鹅算法优化一下VMD参数。利用MATLAB官方自带的VMD函数。 替换为官方自带的VMD函数后&#xff0c;寻优速度真的大幅度提升&#xff01;数据量大的不妨都试试这个官方的VMD函数。当然要下载2020a以上的MATLAB才可以哦&#xff01; 同样以西储大学数据集为例&#xff0c;选…

vue 部署后修改配置文件(接口IP)

近期&#xff0c;有一个项目&#xff0c;运维在部署的时候&#xff0c;接口ip还没有确定&#xff0c;而且ip后面的路径一直有变动&#xff0c;导致我这里一天打包至少四五次才行&#xff0c;很麻烦&#xff0c;然后看了下有没有打包后修改配置文件修改接口ip的方法&#xff0c;…

【QT+QGIS跨平台编译】之六十四:【QGIS_CORE跨平台编译】—【错误处理:未定义类型QTemporaryDir - QgsSourceCache】

文章目录 一、未定义类型QTemporaryDir二、解决办法 一、未定义类型QTemporaryDir 报错&#xff1a; 二、解决办法 QgsSourceCache.h文件中 第25行修改为&#xff1a; #include <QTemporaryDir>

深入了解Kafka的文件存储原理

Kafka简介 Kafka最初由Linkedin公司开发的分布式、分区的、多副本的、多订阅者的消息系统。它提供了类似于JMS的特性&#xff0c;但是在设计实现上完全不同&#xff0c;此外它并不是JMS规范的实现。kafka对消息保存是根据Topic进行归类&#xff0c;发送消息者称为Producer&…

FreeRTOS学习笔记——FreeRTOS中断管理

什么是中断&#xff1f; 简介&#xff1a;让CPU打断正常运行的程序&#xff0c;转而去处理紧急的事件&#xff08;程序&#xff09;&#xff0c;就叫中断 例&#xff1a; 中断执行机制&#xff0c;可简单概括为三步&#xff1a; 中断优先级分组设置 ARM Cortex-M 使用了 8 位…

微信小程序触屏事件_上划下划事件

一、微信小程序触屏事件 bindtouchstart&#xff1a;手指触摸动作开始 bindtouchmove&#xff1a;手指触摸后移动 bindend&#xff1a;手指触摸动作结束 属性类型说明touchesArray触摸事件&#xff0c;当前停留在屏幕中的触摸点信息的数组 Touch 对象 属性类型说明identi…

基于MVS的三维重建算法学习笔记(一)— MVS三维重建概述与OpenMVS开源框架配置

基于MVS的三维重建算法学习笔记&#xff08;一&#xff09;— MVS三维重建概述与OpenMVS开源框架配置 声明1. MVS&#xff08;Multi-view stereo&#xff09;概述稀疏重建与稠密重建的区别稀疏重建——SFM&#xff08;Structure from Motion&#xff09;算法稠密重建——MVS&am…

家政行业的数字转型:开发智能化家政预约APP的挑战与机遇

为了适应这一变化&#xff0c;许多家政企业开始积极探索数字化转型&#xff0c;其中包括开发智能化预约APP。本文将探讨开发智能化预约APP所面临的挑战与机遇。 ​一、挑战&#xff1a; 1.用户体验设计&#xff1a; -开发智能化预约APP需要注重用户体验设计&#xff0c;包括…

Android使用OpenGL和FreeType绘制文字

Open GL主要是渲染图形的&#xff0c;有时候需要绘制文字&#xff0c;网上搜了一下&#xff0c;基本思路都是把文字转成位图&#xff0c;再使用Open GL纹理进行渲染。加载纹理在特定阶段才能成功&#xff08;在onSurfaceCreated中加载&#xff09;&#xff0c;这样就无法动态的…

动态规划(算法竞赛、蓝桥杯)--树形DP树形背包

1、B站视频链接&#xff1a;E18 树形DP 树形背包_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std; const int N110; int n,V,p,root; int v[N],w[N]; int h[N],to[N],ne[N],tot; //邻接表 int f[N][N];void add(int a,int b){to[tot]b;ne[tot]h[a];h[a…

Docusaurus框架——react+antd+echarts自定义mdx生成图表代码解释文档

文章目录 ⭐前言⭐Docusaurus框架渲染mdx内容&#x1f496; 创建一个mdx文件&#x1f496; 创建一个react jsx文件&#x1f496; mdx引入react的组件并渲染&#x1f496; mdx引入react的组件源代码内容 ⭐渲染一个echarts地图的代码解释文档&#x1f496; echarts 渲染地图&…

idea中springboot项目创建后追加依赖

springboot项目创建后追加依赖 前言1、安装插件editstarters设置->插件 2、进入pom.xml 页面 前言 在项目创建的时候选择好依赖创建项目&#xff0c;之后追加依赖不是很方便&#xff0c;介绍一个简单的使用方法&#xff0c;通过editstarters进行添加 1、安装插件editstart…

html基本标签

<h1></h1> <p></p> h是标签从h1~h6&#xff0c;没用h7,h8 p是段落 <a href"https://www.educoder.net">Educoder平台</a> href可以指定链接进行跳转 <img src"https://www.educoder.net/attachments/download/2078…