ElasticSearch - 全文检索服务 - RestHightLevel版

一、引言


1.1 数据库查询为什么还要ElasticSearch?

数据库一般只适合保存搜索结构化的数据,对于非结构化的数据( 比如文章内容),只能通过like%%模糊查询,但是在大量的数据面前,like%%有两个弊端:

1)搜索效率会很差,因为是做一个全表扫描(like%%会让索引失效)

2)搜索没办法通过相关度匹配排序(可能返回的是用户不关心的结果)

ElasticSearch就可以解决这些问题

1.2 什么是全文检索?

全文检索 将非结构化数据中的一部分信息提取出来,重新组织,使其变得具有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之索引。

例如字典的拼音表和部首检字表就相当于字典的索引,通过查找拼音表或者部首检字表就可以快速的查找到我们要查的字。

这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。

1.3 全文检索的流程


image.png

1.4 构建索引的过程

索引过程,对要搜索的原始内容进行索引构建一个索引库,索引过程包括:

获得文档→创建文档→分析文档→索引文档。

1.4.1 获得原始文档

原始文档是指要索引和搜索的内容。原始内容包括互联网上的网页、数据库中的数据、磁盘上的文件等。

1.4.2 创建文档对象(Document)

获取原始文档的目的是为了索引,在索引前需要将原始内容创建成文档(Document),文档中包括一个一个的域(Field),域中存储内容。

1.4.3 分析文档(分词)

将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程是经过对原始文档提取单词、将字母转为小写、去除标点符号、去除停用词等过程生成最终的语汇单元。

1.4.4 创建索引

创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫倒排索引结构。

1.5 倒排索引

1.5.1 正向索引

简单来说,正向索引就是根据文件ID找到该文件的内容,在文件内容中匹配搜索关键字,这种方法是顺序扫描方法,数据量大、搜索慢。


image.png

1.5.2 反向(倒排)索引

倒排索引和正向索引刚好相反,是根据内容(词语)找文档


image.png

二、ElasticSearch


2.1 ElasticSearch简介

ElasticSearch基本概念

2.1.1 索引库(index)

索引库是ElasticSearch存放数据的地方,可以理解为关系型数据库中的一个数据库。事实上,我们的数据被存储和索引在分片(shards)中,索引只是一个把一个或多个分片分组在一起的逻辑空间。

2.1.2 映射类型(type)

映射类型用于区分同一个索引下不同的数据类型,相当于关系型数据库中的表。

注意:在 6.0 的index下是无法创建多个type,并且会在 7.0 中完全移除。

2.1.3 文档(documents)

文档是ElasticSearch中存储的实体,类比关系型数据库,每个文档相当于数据库表中的一行数据。

2.1.4 字段(fields)

文档由字段组成,相当于关系数据库中列的属性。

2.1.5 分片与副本

如果一个索引具有很大的数据量,它的数据量可能会超出单个节点的容量限制(硬盘容量),而且单个节点数据量过大,执行性能也会随之下降,每个搜索请求的执行效率都会降低。 为了解决上述问题, Elasticsearch 提出了分片的概念,索引将划分成多份,称为分片。每个分片都可以创建对应的副本,以便保证服务的高可用性。

2.2 ElasticSearch的安装

1)准备ElasticSearch的docker-compose.yml文件

version: '3.1'
services:
  elasticsearch:
    image: elasticsearch:6.8.5
    restart: always
    container_name: elasticsearch
    ports:
      - 9200:9200
      - 9300:9300
    environment:
      discovery.type: single-node
    volumes:
      - ./es/data:/usr/share/elasticsearch/data:rw
      - ./es/logs:/usr/share/elasticsearch/logs:rw
      - ./es/plugins:/usr/share/elasticsearch/plugins
      - config:/usr/share/elasticsearch/config
  kibana:
    image: kibana:6.8.5
    container_name: kibana
    restart: always
    environment:
      SERVER_NAME: kibana
      ELASTICSEARCH_URL: http://193.168.195.135:9200
    ports:
      - 5601:5601
volumes:
  config:

注意:第23行必须写elasticsearch所在机器的ip地址,不能写127.0.0.1

2)执行docker-compose up -d 命令启动容器
docker-compose up -d

注意:第一次创建容器会失败,需要给.es文件夹赋予权限,执行chmod 777 -R ./es命令,然后重启容器

3)安装中文分词器

进入elasticsearch容器,执行中文分词器相关安装命令

docker exec -it elasticsearch bash

./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.8.5/elasticsearch-analysis-ik-6.8.5.zip

注意:可以访问

https://github.com/medcl/elasticsearch-analysis-ik/releases

查找和当前es匹配的版本

4)手动安装中文分词器

如果第3步安装超时失败,可以尝试进行手动安装,如果第三步成功则跳过该步骤

4.1)手动下载对应的IK分词器版本https://github.com/medcl/elasticsearch-analysis-ik/releases

4.2)直接将分词器压缩包上传到容器的plugins路径(上传到宿主机的容器卷路径即可)

4.3)解压分词器

unzip elasticsearch-analysis-ik-6.8.5.zip -d ./ik-analyze
5)重启容器
docker-compose restart
6)访问kibana服务
image.png

7)测试IK分词器

POST _analyze
{
  "analyzer":"ik_smart",
  "text":"歼10系列战斗机"
}


POST _analyze
{
  "analyzer":"ik_max_word",
  "text":"歼10系列战斗机"
}
image.png

2.3 索引库(Index)相关操作

2.3.1 概述

ElasticSearch采用Rest风格的API,因此其API就是一次Http请求

请求分为: PUT POST GET DELETE

GET:查询数据

PUT:插入数据

POST:更新数据,实际上很多情况下 es 不是很清晰你到底要作什么,有些时候POST也可用于新增或者查询

DELETE: 删除数据

2.3.2 新增索引库语法

PUT /索引库名称
{
  "settings":{
    "number_of_shards": 3, #分片的数量
    "number_of_replicas": 2 #副本的数量
  }
}

settings:表示索引库的设置 number_of_shards:表示分片的数量 number_of_replicas:副本数量

2.3.3 查询索引信息

GET /索引库名称

2.3.4 判断索引库是否存在

HEAD /索引库名称

2.3.5 删除索引库

DELETE /索引库名称

注意
1)索引库 类似于 MySQL中数据库的概念
2)如果创建索引不指定settings,默认会有5个分片,1个副本

2.4 映射类型(type)相关操作
2.4.1 新增映射类型语法

PUT /索引库名/_mapping/类型名称
{
    "properties": {
        "字段名1": {
            "type": "类型",
            "index": true,
            "store": true,
            "analyzer": "分词器"
        },
        "字段名2": {
            "type": "类型",
            "index": true,
            "store": true,
            "analyzer": "分词器"
        }
    }
}

type:类型,可以是text、long、date、integer、object、keyword(表示关键字,不能被分词)
index:是否参与索引,默认为true
store:是否参与存储,默认为false
analyzer:分词器,可选 “ik_max_word”或者“ik_smart”,表示使用ik分词器

2.4.2 查看映射类型信息

GET /索引库名称/_mapping/类型名称

2.4.3 字段属性详解

type

String类型,又分两种: text:可分词,不可参与聚合 keyword:不可分词,数据会作为完整字段进行匹配,可以参与聚合
Numerical数值类型,分两类 基本数据类型:long、interger、short、byte、double、flfloat、half_flfloat 浮点数的高精度类型:scaled_float,需要指定一个精度因子,比如10或100,elasticsearch会把真实值乘以这个因子后存储,取出时再还原
Date:日期类型 elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间
boolean: 设置字段类型为boolean后,可以填入的值为:true、false、"true"、"false"
binary: binary类型接受base64编码的字符串
geo_point: 地理点类型用于存储地理位置的经纬度对
更多类型参考:https://www.elastic.co/guide/en/elasticsearch/reference/6.5/mapping-types.html

index

index影响字段的索引情况。
true:字段会被索引,则可以用来进行搜索。默认值就是true
false:字段不会被索引,不能用来搜索
index的默认值就是true,也就是说你不进行任何配置,所有字段都会被索引。但是有些字段是我们不希望被索引的,比如商品的图片信息,就需要手动设置index为false。

store

是否将数据进行额外存储。在学习lucene和solr时,我们知道如果一个字段的store设置为false,那么在文档列表中就不会有这个字段的值,用户的搜索结果中不会显示出来。
但是在Elasticsearch中,即便store设置为false,也可以搜索到结果。
原因是Elasticsearch在创建文档索引时,会将文档中的原始数据备份,保存到一个叫做 _source 的属性中。而且我们可以通过过滤 _source 来选择哪些要显示,哪些不显示。而如果设置store为true,就会在 _source 以外额外存储一份数据,多余,因此一般我们都会将store设置为false,事实上,store的默认值就是false。

analyzer

定义的是该字段的分析器,默认的分析器是 standard 标准分析器,这个地方可定义为自定义的分析器。
比如IK分词器为: ik_max_word 或者 ik_smart

boost

激励因子。这个与lucene中一样,我们可以通过指定一个boost值来控制每个查询子句的相对权重。
该值默认为1。一个大于1的boost会增加该查询子句的相对权重
比如:

GET /_search {
    "query": {
        "bool": {
            "must": {
                "match": {
                    "content": {
                        "query": "full text search",
                        "operator": "and"
                    }
                }
            },
            "should": [{
                    "match": {
                        "content": {
                            "query": "Elasticsearch",
                            "boost": 3
                        }
                    }
                },
                {
                    "match": {
                        "content": {
                            "query": "Lucene",
                            "boost": 2
                        }
                    }
                }
            ]
        }
    }
}

注意
1)映射类型(type) 类似于 MySQL数据库中表的概念
2)从ElasticSearch 6.x之后,一个Index下只能有一个type

2.5 文档相关(document)操作
2.5.1 添加文档

#指定id的添加方式
PUT /索引库名/类型名称/id #id需要自己指定
{
  "field1":"value1",
  "field2":"value2",
  ...
}
#自动生成id的添加方式
POST /索引库名/类型名称  #使用POST无需指定id
{
  "field1":"value1",
  "field2":"value2",
  ...
}
#批量添加文档
PUT /索引库名称/类型名称/_bulk
{"index":{"_id":id值1}}
{"field1":"value1", "field2":"value2"...}
{"index":{"_id":id值2}}
{"field1":"value1", "field2":"value2"...}
....

2.5.2 更新文档

#全局更新,会将所有字段更新,没有指定的字段会自动删除
PUT /索引库名/类型名称/id #需要更新的id,id必须存在,如果不存在就变成了添加
{
  "field1":"value1",
  "field2":"value2",
  ...
}
#局部更新,只更新需要更新的字段
POST /索引库名/类型名称/id/_update
{
  "doc":{
    "field1": "新的value"
  }
}

2.5.3 删除文档

DELETE /索引库名/类型名称/id

2.5.4 查询文档

#查询索引库全部数据
GET /索引库名称/_search 
#根据id查询
GET /索引库名称/类型名称/id
#批量查询
GET /_mget
{
    "docs": [
        {
            "_index": "索引库名称1",
            "_type": "映射类型1",
            "_id":"查询文档id1"
        },
        {
            "_index": "索引库名称2",
            "_type": "映射类型2",
            "_id":"查询文档id2"
        }
    ]
}

注意
1)文档(document)类似于 数据库中表的一条记录
2)当添加的文档中,设置的field,而type中没有时,type会自动的添加该field的映射记录,
这是elasticsearch的自动映射功能

三、SpringBoot操作ElasticSearch(elasticsearch-rest-high-level-client)
3.1 配置ElasticSearch
1)添加依赖

<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>6.8.5</version>
</dependency>

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

2)配置application.yml

spring:
  elasticsearch:
    rest:
      uris: http://192.168.195.135:9200

3)在需要的地方注入RestHighLevelClient对象

@Autowired
 private RestHighLevelClient restHighLevelClient;

3.2 使用SpringBoot操作索引库(Index)

 @Autowired
    private RestHighLevelClient client;


    /**
     * 创建索引
     * @param indexName
     * @return
     */
    @Override
    public boolean createIndex(String indexName) {

        CreateIndexRequest indexRequest = new CreateIndexRequest(indexName);
        //设置索引库的相关属性
        Settings settings = Settings.builder()
                .put("number_of_shards", 1)//设置分片数量
                .put("number_of_replicas", 0)//设置副本数量
                .build();
        indexRequest.settings(settings);

        try {
            CreateIndexResponse response = client.indices().create(indexRequest, RequestOptions.DEFAULT);
            //返回结果
            return response.isAcknowledged();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

    /**
     * 判断索引是否存在
     * @param indexName
     * @return
     */
    @Override
    public boolean isExistsIndex(String indexName) {
        GetIndexRequest getIndexRequest = new GetIndexRequest(indexName);
        try {
            boolean exists = client.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
            return exists;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

    /**
     * 删除索引
     * @param indexName
     * @return
     */
    @Override
    public boolean deleteIndex(String indexName) {
        DeleteIndexRequest request = new DeleteIndexRequest(indexName);
        try {
            AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
            return response.isAcknowledged();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

3.3 使用SpringBoot操作映射类型(type)

/**
     * 添加映射
     * PUT /partform_hotal/_mapping/hotal
     * {
     *   "properties": {
     *     "hotalName":{
     *       "type": "text",
     *       "analyzer": "ik_max_word"
     *     },
     *     "hotalImage":{
     *       "type": "keyword",
     *       "index": false
     *     },
     *     "type":{
     *       "type": "integer"
     *     },
     *     "hotalInfo":{
     *       "type":"text",
     *       "analyzer": "ik_max_word"
     *     },
     *     "keyword":{
     *       "type":"text",
     *       "analyzer": "ik_max_word"
     *     },
     *     "location":{
     *       "type": "geo_point"
     *     },
     *     "star":{
     *       "type": "integer"
     *     },
     *     "brand":{
     *       "type": "text",
     *       "analyzer": "ik_max_word"
     *     },
     *     "address":{
     *       "type": "keyword"
     *     },
     *     "openTime":{
     *       "type": "date",
     *       "format": "yyyy-MM-dd"
     *     },
     *     "cityname":{
     *       "type": "keyword"
     *     },
     *     "regid":{
     *       "type": "text",
     *       "analyzer": "ik_max_word"
     *     }
     *   }
     * }
     *
     *
     * @return
     */
    @Override
    public boolean createMapping(String index) {

        PutMappingRequest putMappingRequest = new PutMappingRequest(index);

        try {
            XContentBuilder builder = JsonXContent.contentBuilder();
            builder
                    .startObject()
                    .startObject("properties")

                    .startObject("hotalName")
                    .field("type", "text")
                    .field("analyzer", "ik_max_word")
                    .endObject()

                    .startObject("hotalImage")
                    .field("type", "keyword")
                    .field("index", "false")
                    .endObject()

                    .startObject("type")
                    .field("type", "integer")
                    .endObject()

                    .startObject("hotalInfo")
                    .field("type", "text")
                    .field("analyzer", "ik_max_word")
                    .endObject()

                    .startObject("keyword")
                    .field("type", "text")
                    .field("analyzer", "ik_max_word")
                    .endObject()

                    .startObject("location")
                    .field("type", "geo_point")
                    .endObject()

                    .startObject("star")
                    .field("type", "integer")
                    .endObject()

                    .startObject("brand")
                    .field("type", "text")
                    .field("analyzer", "ik_max_word")
                    .endObject()

                    .startObject("address")
                    .field("type", "text")
                    .field("analyzer", "ik_max_word")
                    .endObject()

                    .startObject("openTime")
                    .field("type", "date")
                    .field("format", "yyyy-MM-dd")
                    .endObject()

                    .startObject("cityname")
                    .field("type", "keyword")
                    .endObject()

                    .startObject("regid")
                    .field("type", "text")
                    .field("analyzer", "ik_max_word")
                    .endObject()

                    .endObject().endObject();
            //设置到Request对象中
            putMappingRequest.source(builder);
            client.indices().putMapping(putMappingRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

3.4 使用SpringBoot操作文档(document)
新增文档

/**
     * 给索引库添加文档
     * @param indexName
     * @param hotal
     * @return
     */
    @Override
    public boolean insertDco(String indexName, Hotal hotal) {

        String json = JSON.toJSONString(hotal);
        System.out.println(json);

        IndexRequest indexRequest = new IndexRequest(indexName, "_doc")
                .id(hotal.getId() + "")
                .source(json, XContentType.JSON);
        try {
            IndexResponse index = client.index(indexRequest, RequestOptions.DEFAULT);
            long seqNo = index.getSeqNo();
            String lowercase = index.getResult().getLowercase();
            int status = index.status().getStatus();
            System.out.println("状态:" + status);
            System.out.println("返回:" + lowercase);
            System.out.println("序号:" + seqNo);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

删除文档

 /**
     * 根据ID删除
     * @param indexName
     * @param id
     * @return
     */
    @Override
    public boolean deleteDco(String indexName, Integer id) {

        DeleteRequest deleteRequest = new DeleteRequest(indexName, "_doc", id + "");

        try {
            DeleteResponse resp = client.delete(deleteRequest, RequestOptions.DEFAULT);
            int status = resp.status().getStatus();
            System.out.println("结果:" + status);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

更新文档

 /**
     * 根据id修改信息
     * @param indexName
     * @param hotal
     * @return
     */
    @Override
    public boolean updateDco(String indexName, Hotal hotal) {

        String json = JSON.toJSONString(hotal);
        System.out.println(json);

//        Map map = new HashMap();
//        map.put("hotalInfo", "xxxx");

        UpdateRequest updateRequest = new UpdateRequest(indexName, "_doc", hotal.getId() + "");
        updateRequest.doc(json, XContentType.JSON);

        try {
            UpdateResponse response = client.update(updateRequest, RequestOptions.DEFAULT);
            int status = response.status().getStatus();
            System.out.println("状态:" + status);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

四、基本查询
4.1 term、terms查询
什么是term查询?
term是代表完全匹配,也就是精确查询,搜索前不会再对搜索词进行分词,所以我们的搜索词必须是文档分词集合中的一个。 比如文档内容为:"美的微波炉",被分词为"美的"和"微波炉",term搜索的关键字必须为"美的"或者"微波炉"才能搜索出这个文档,搜索"美的微波炉"搜索不出来
语法

#term查询
GET /索引库/映射类型/_search
{
  "query": {
    "term": {
      "字段名称": {
        "value": "搜索关键字"
      }
    }
  }
}
#terms查询 - 可以同时查询多个关键词
GET /索引库/映射类型/_search
{
  "query":{
    "terms": {
      "字段名称": [
        "关键字1","关键字2"...
      ]
    }
  }  
}

注意:terms查询 多个关键字之间是或者的关系,也就是说只要符合一个关键字的文档就会被查询出来

4.2 match查询

什么是match查询?

match 查询是高层查询,它们了解字段映射的信息:

1.如果查询 日期(date) 或 整数(integer) 字段,它们会将查询字符串分别作为日期或整数对待。

2.如果查询一个( not_analyzed )未分词的精确值字符串字段, 它们会将整个查询字符串作为单个词项对待。

3.但如果要查询一个( analyzed )已分析的全文字段, 它们会先将查询字符串传递到一个合适的分析器,然后生成一个供查询的词项列表。 一旦组成了词项列表,这个查询会对每个词项逐一执行底层的查询,再将结果合并,然后为每个文档生成一个最终的相关度评分。 match查询其实底层是多个term查询,最后将term的结果合并。

语法

#match_all查询 - 查询指定库的指定类型的所有文档
GET /索引库/映射类型/_search
{
  "query": {
    "match_all": {}
  }
}
#match查询 - 根据关键字查询
GET /索引库/映射类型/_search
{
  "query": {
    "match": {
      "字段名称": "搜索关键字"
    }
  }
}
#布尔match查询
GET /索引库/映射类型/_search
{
  "query": {
    "match": {
      "字段名称": {
        "query": "搜索关键字",
        "operator": "OR或者AND"  
      }
    }
  }
}

注意:operator值为
and表示关键词分词后的结果,必须全部匹配上
or表示需要一个分词匹配上即可,默认为or

#mulit_match查询 - 可以查询多个字段
GET /索引库/映射类型/_search
{
  "query": {
    "multi_match": {
      "query": "搜索关键字",
      "fields": ["字段名称1^2.0", "字段名称2^0.5"],
      "operator": "or"
    }
  }
}

注意:
^2.0表示这个字段在搜索中的权重,值越高权重越大,可以不设置。

image.png
#match_phrase查询 - 短语查询
GET /索引库/映射类型/_search
{
  "query": {
    "match_phrase": {
      "字段名称": "关键词1 关键词2"
    }
  }
}

注意:

match_phrase查询,只会匹配关键词1 和关键词2 挨在一起的文档,如果两个关键词分开太远的文档是不会匹配上的

4.3 ids查询
什么ids查询?
ids查询是一类简单的查询,它过滤返回的文档只包含其中指定标识符的文档,
该查询默认指定作用在“_id”上面。
语法

GET /索引库/映射类型/_search
{
  "query": {
    "ids": {
      "values": ["1","3","6"...]
    }
  }
}
image.png

4.4 prefix前缀查询
什么是prefix查询?
前缀查询,可以使我们找到某个字段以给定前缀开头的文档。最典型的使用场景,一般是在文本框录入的时候的联想功能
语法

GET /索引库/映射类型/_search
{
  "query": {
    "prefix": {
      "字段名称": {
        "value": "前缀"
      }
    }
  }
}

注意:前缀查询并不是和搜索字段的内容前缀匹配,而是和搜索字段的所有分词的前缀匹配,匹配上一个分词后,就会查询出该文档,建议和keyword类型的字段结合使用


image.png

4.5 fuzzy查询

什么是fuzzy查询?

fuzzy(模糊)查询是一种模糊查询,term 查询的模糊等价。

语法

GET /索引库/映射类型/_search
{
  "query": {
    "fuzzy": {
      "字段名称": {
        "value": "关键词",
        "fuzziness": "2"
      }
    }
  }
}

注意:
1、fuzzy搜索以后,会自动尝试将你的搜索文本进行纠错,然后去跟文本进行匹配
2、fuzziness属性表示关键词最多纠正的次数, 比如空条 -> 空调,需要纠正一次,fuzziness需要设置为1
3、prefix_length属性表示不能被 “模糊化” 的初始字符数。 大部分的拼写错误发生在词的结尾,而不是词的开始。 例如通过将prefix_length 设置为 3 ,你可能够显著降低匹配的词项数量。(前面3个字不能出错,否则查不到)


image.png

4.6 wildcard查询
什么是wildcard查询?
wildcard(通配符)查询意为通配符查询

GET /索引库/映射类型/_search
{
  "query": {
    "wildcard": {
      "字段名称": {
        "value": "关键词? *"
      }
    }
  }
}

注意:
*表示匹配0或者多个字符
?表示匹配一个字符
wildcard查询不注意查询性能,应尽可能避免使用。

4.7 range查询
什么range查询?
range查询既范围查询,可以对某个字段进行范围匹配

GET /索引库/映射类型/_search
{
  "query": {
    "range": {
      "字段名称": {
        "gte": 0,
        "lte": 2000,
      }
    }
  }
}
image.png

4.8 regexp查询

什么是regexp查询?
正则表达式查询,wildcard和regexp查询的工作方式和prefix查询完全一样。它们也需要遍历倒排索引中的词条列表来找到所有的匹配词条,然后逐个词条地收集对应的文档ID。它们和prefix查询的唯一区别在于它们能够支持更加复杂的模式。
语法

GET /索引库/映射类型/_search
{
  "query": {
    "regexp": {
      "字段名称": "正则表达式"
    }
  }
}

注意:
1、prefix(前缀)wildcard(通配符)以及regexp(正则)查询基于词条进行操作。如果你在一个analyzed字段上使用了它们,它们会检查字段中的每个词条,而不是整个字段。
2、对一个含有很多不同词条的字段运行这类查询是非常消耗资源的。应该避免使用一个以通配符开头的模式(比如,*foo)

image.png

4.9 使用JavaAPI实现以上查询
查询的基础结构,通过不同的QueryBuilder对象,可以实现不同的查询

@Override
public List<Hotal> queryHotals(String indexName, QueryBuilder queryBuilder) {
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.query(queryBuilder);
        
    SearchRequest searchRequest = new SearchRequest(indexName);
    searchRequest.source(searchSourceBuilder);

    try {
        SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = search.getHits();

        //循环结果
        for (SearchHit hit : hits) {
            System.out.println("------------------------------------------");
            Map<String, DocumentField> fields = hit.getFields();
            for (Map.Entry<String, DocumentField> entry : fields.entrySet()) {
                System.out.println(entry.getKey() + ":" +  entry.getValue().getValue());
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}
//term
TermQueryBuilder termQueryBuilder = 
            QueryBuilders.termQuery("hotalName", "连锁");
//terms
TermsQueryBuilder termsQueryBuilder = 
            QueryBuilders.termsQuery("hotalName", "7天", "连锁");
//match
MatchQueryBuilder matchQueryBuilder = 
            QueryBuilders.matchQuery("hotalName", "爱丽丝");

//match查询
        MultiMatchQueryBuilder matchBuilder = QueryBuilders.multiMatchQuery("大中华")
                .field("hotalName", 1.0f)
                .field("hotalInfo", 2.0f);

//matchall
MatchAllQueryBuilder matchAllQueryBuilder = 
            QueryBuilders.matchAllQuery();
//Ids
IdsQueryBuilder idsQueryBuilder = 
            QueryBuilders.idsQuery().addIds("2","3");
//prefix
PrefixQueryBuilder prefixQueryBuilder = 
            QueryBuilders.prefixQuery("hotalName", "连");
//fuzzy
FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("regid", "平三区")
                .fuzziness(Fuzziness.TWO)
                .prefixLength(0);
//wildcard
WildcardQueryBuilder wildcardQueryBuilder = 
            QueryBuilders.wildcardQuery("hotalName", "爱丽*");
//range
RangeQueryBuilder rangeQuery = 
            QueryBuilders.rangeQuery("star").gte(0).lt(3);
//regexp
RegexpQueryBuilder regexQuery = 
            QueryBuilders.regexpQuery("hotalName", "\\S{0,}[0-9]{1}.*");

五、复合查询

5.1 bool查询
bool 过滤器。 这是个 复合过滤器(compound fifilter) ,它可以接受多个其他过滤器作为参数,并将这些过滤器结合成各式各样的布尔(逻辑)组合。
语法

GET /索引库/映射类型/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "content": {
              "value": "性价比"
            }
          }
        },{
          "match": {
            "title": "微波炉"
          }
        }
      ],
      "must": [
        {
          "match": {
            "content": "格力造"
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "price": {
              "gte": 300,
              "lte": 3000
            }
          }
        }
      ],
      "filter": {
        "match": {
          "title": "美的"
        }
      }
    }
  }
}

例子

#bool查询 - 将多个基本查询组合在一起
GET /hotal_index/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "brand": {
              "value": "7天"
            }
          }
        }
      ], 
      "must": [
        {
          "match": {
            "hotalName": "连锁"
          }
        },{
          "range": {
            "price": {
              "gte": 500,
              "lte": 3000
            }
          }
        }
      ],
      ##minimum_should_match:1
      "filter": {
          "multi_match": {
            "query": "大中华",
            "fields": ["hotalName^2.0", "hotalInfo"]
          }
      }
    }
  }
}

属性含义

must: 返回的文档必须满足must子句的条件,并且参与计算分值,与 AND 等价

must_not:所有的语句都 不能(must not) 匹配,与 NOT 等价

should: 返回的文档可能满足should子句的条件。在一个Bool查询中,如果没有must或者filter,有一个或 者多个should子句,那么只要满足一个就可以返回,与 OR 等价

minimum_should_match:用来指定should至少需要匹配几个语句

filter:返回的文档必须满足filter子句的条件。但是不会像Must一样,参与计算分值

注意

如果查询中没有must语句,那么至少要匹配一个should语句

5.1.1 什么是filter?

filter vs query
filter ,仅仅只是按照搜索条件过滤出需要的数据而已,不计算任何相关度分数,对相关度没有任何影响;
query ,会去计算每个document相对于搜索条件的相关度,并按照相关度进行排序;
一般来说,如果你是在进行搜索,需要将最匹配搜索条件的数据先返回,那么用query;如果你只是要根据一些条件筛选出一部分数据,不关注其排序,那么用filter; 除非是你的这些搜索条件,你希望越符合这些搜索条件的document越排在前面返回,那么这些搜索条件要放在query中;如果你不希望一些搜索条件来影响你的document排序,那么就放在filter中即可;
filter和query的性能对比
filter ,不需要计算相关度分数,不需要按照相关度分数进行排序,同时还有内置的自动cache最常使用filter的数据
query ,相反,要计算相关度分数,按照分数进行排序,而且无法cache结果
所以filter查询性能会高于query

5.3 boosting查询
什么是boosting查询?

该查询用于将两个查询封装在一起,并降低其中一个查询所返回文档的分值。它接受一个positive查询和一个negative查询。只有匹配了positive查询的文档才会被包含到结果集中,但是同时匹配了negative查询的文档会被降低其相关度,通过将文档原本的score和negative_boost参数进行相乘来得到新的score。因此,negative_boost参数必须小于1.0
(positive作为查询条件,如果查询条件同时符合negative条件,则会降低分值)
"negative_boost": 会把原来的分值乘以negative_boost的值作为最后的分值
如果negative_boost>1:提高分值
如果negative_boost<1:降低分值

运用场景

例如,在互联网上搜索"苹果"也许会返回,水果或者各种食谱的结果。但是用户可能只想搜索到苹果手机等电子产品,当然我们可以通过排除“水果 乔木 维生素”和这类单词,结合bool查询中的must_not子句,将结果范围缩小到只剩苹果手机,但是这种做法难免会排除掉那些真的想搜索水果的用户,这时可以通过boosting查询,通过降低“水果 乔木 维生素”等关键词的评分,让苹果等电子产品的排名靠前

语法

GET /索引库/映射类型/_search
{
  "query": {
    "boosting": {
      "positive": {
        "match": {
          "title": "性价比"
        }
      },
      "negative": {
        "match": {
          "content": "性价比"
        }
      },
      "negative_boost": 0.1
    }
  }
}

5.4 使用JavaAPI实现以上查询

//bool
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
                .must(....)
                .mustNot(...)
                .should(...)
                .filter(...)
                .minimumShouldMatch(1);
//boosting
BoostingQueryBuilder boostingQueryBuilder = QueryBuilders
                .boostingQuery(..., ...)
                .negativeBoost(0.2f);

例子:


image.png

六、排序

ElasticSearch默认会有一套相关性分数计算,分数越高,说明文档相关性越大,也就越会排在前面。除了相关性排序之外,开发者也可以通过自己的需要,通过某些规则设置查询文档的排序
如果进行手动排序,则评分为null

语法

GET /索引库/映射类型/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "排序字段1": {
        "order": "asc"
      }
    },
    {
      "排序字段2":{
        "order": "desc"
      }
    }
  ]
}
例子:
#手动排序
GET /hotal_index/_search
{
  "query": {
    "match": {
      "hotalName": "品牌连锁酒店"
    }
  },
  "sort": [
    {
      "price": {
        "order": "asc"
      }
    }
  ]
}
//创建查询构建器
QueryBuilder queryBuilder = .........
..................

//执行查询
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder
     .query(queryBuilder)
      //设置排序
     .sort("star", SortOrder.DESC)
     .sort("type", SortOrder.ASC);

七、高亮

什么是高亮?
许多应用都倾向于在每个搜索结果中 高亮 显示搜索的关键词,比如字体的加粗,改变字体的颜色等.以便让用户知道为何该文档符合查询条件。在 Elasticsearch 中检索出高亮片段也很容易。高亮显示需要一个字段的实际内容。 如果该字段没有被存储(映射mapping没有将存储设置为 true),则加载实际的source,并从source中提取相关的字段。

语法

GET /索引库/映射类型/_search
{
  "query": {
    ....
  },
  "highlight": {
    "fields": {
      "待高亮字段1": {},
      "待高亮字段2": {}
    },
    "post_tags": ["</font>"],
    "pre_tags": ["<font color='red'>"],
    //功能:搜索的摘要显示
    "number_of_fragments": 5,
    "fragment_size": 3
  }
}

例子:


image.png

摘要显示


image.png

参数含义
number_of_fragments: fragment 是指一段连续的文字。返回结果最多可以包含几段不连续的文字。
默认是5。
fragment_size: 某字段的值,长度是1万,但是我们一般不会在页面展示这么长,可能只是展示一部分。设置要显示出来的fragment文本判断的长度,默认是100
noMatchSize: 搜索出来的这个文档这个字段已经显示出高亮的情况,可是其它字段并没有任何显示,设置这个属性可以显示出来。
pre_tags: 标记 highlight 的开始标签。
post_tags: 标记 highlight 的结束标签。

JavaAPI

//设置高亮信息
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder
    .field("title", 100)
    .field("content", 100)
    .preTags("<font color='red'>")
    .postTags("</font>");

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder
     .query(queryBuilder)
     .highlighter(highlightBuilder)
     .sort("star", SortOrder.DESC)
     .sort("type", SortOrder.ASC);

........     
//获得高亮结果
Map<String, HighlightField> highlightFields 
                = hit.getHighlightFields();
for (Map.Entry<String, HighlightField> entry : highlightFields.entrySet()) {
    System.out.println(entry.getKey() + "--" 
                + entry.getValue().getFragments()[0].string());
}

八、地理位置搜索
地理位置在ElasticSearch中的字段类型geo-point
8.1 地理位置类型的相关操作

#创建映射类型:
PUT /soufang/_mapping/house
{
  "properties": {
    "name": {
      "type": "text"
    },
    "location": {
      "type": "geo_point"
    }
  }
}
#添加坐标点数据:
PUT /soufang/house/1
{
  "name": "市民中心",
  "location": {
    "lat": 22.54737, #lat代表纬度
    "lon": 114.067531 #lon代表经度
  }
}

8.2 通过geo_distance过滤器搜索坐标

geo_distance:地理距离过滤器( geo_distance )以给定位置为圆心画一个圆,来找出那些地理坐标落在其中的文档

GET /soufang/house/_search
{
  "query": {
    "geo_distance":{
      "location": {
        "lat": 22.551013,
        "lon": 114.065432
      },
      "distance": "1km",
      "distance_type": "arc"
    }
  }
}

distance:中心点的半径距离
distance_type:两点间的距离计算的精度算法
arc - 最慢但是最精确是弧形(arc)计算方式,这种方式把世界当作是球体来处理
plane - 平面(plane)计算方式,把地球当成是平坦的。 这种方式快一些但是精度略逊

8.3 通过geo_bounding_box过滤器搜索坐标
geo_bounding_box: 查找某个长方形区域内的位置

GET /soufang/house/_search
{
  "query": {
    "geo_bounding_box":{
      "location":{
        "top_left": {
          "lat": 22.628427,
          "lon": 114.009234
        },
        "bottom_right": {
          "lat": 22.521103,
          "lon": 114.148939
        }
      }
    }
  }
}

top_left:代表矩形左上角
bottom_right:代表矩形右下角

8.4 通过geo_polygon过滤器搜索坐标
geo_polygon:查找位于多边形内的地点

GET /soufang/house/_search
{
  "query": {
    "geo_polygon": {
      "location":{
        "points": [
          [113.908911, 22.613748],
          [114.056952,22.634298],
          [114.031368,22.575843],
          [114.097196,22.500803],
          [113.9,22.493591]
        ]
      }
    }
  }
}

8.5 过滤结果通过距离排序
query:是查询这个区域的house
sort:这个区域的house为location里的坐标为零点,按距离排序返回数据

GET /soufang/house/_search
{
  "query": {
    "geo_distance":{
      "location": {
        "lat": 22.551013,
        "lon": 114.065432
      },
      "distance": "1km",
      "distance_type": "arc"
    }
  },
  "sort": [
    {
      "_geo_distance": {
        "order": "asc",
        "location": {
          "lat": 22.551013,
          "lon": 114.065432
        },
        "unit": "km",
        "distance_type": "arc"
      }
    }
  ]
}

unit:以 公里(km)为单位,将距离设置到每个返回结果的 sort 键中

8.6 JavaApi的执行方式

//geo_distance查找方式
QueryBuilders.geoDistanceQuery("location")
                    .point(22.55243, 114.044335)
                    .distance(2.8, DistanceUnit.KILOMETERS)

//geo_bounding_box查找方式
QueryBuilders.geoBoundingBoxQuery("location")
        .setCorners(
        new GeoPoint(22.628427, 114.009234), 
        new GeoPoint(22.521103, 114.148939)) 

//geo_polygon查找方式
List<GeoPoint> points = new ArrayList<>();
    points.add(new GeoPoint(22.613748, 113.908911));
    points.add(new GeoPoint(22.634298, 114.056952));
    points.add(new GeoPoint(22.575843,114.031368));
    points.add(new GeoPoint(22.500803,114.097196));
    points.add(new GeoPoint(22.493591,113.9));
QueryBuilders.geoPolygonQuery("location", points)


//根据距离排序
SortBuilders
    .geoDistanceSort("location", 22.586737, 113.960829)
    .order(SortOrder.DESC)
    .unit(DistanceUnit.KILOMETERS)

九、function_score自定义文档相关性

9.1 什么是function_score?

在使用ES进行全文搜索时,搜索结果默认会以文档的相关度进行排序,而这个 "文档的相关度",是可以通过 function_score 自己定义的,也就是说我们可以透过使用function_score,来控制 "怎么样的文档相关度更高" 这件事

9.2 文档相关度评分默认大概规则

1、关键词词频越高,评分越高
2、关键词在所有文档中出现的频率越高,评分越低
3、搜索的关键词与目标文档中分词匹配个数越多,评分越高
4、匹配的字段权重越高,评分越高

9.3 function_score的基本用法

9.3.1 function_score提供的加强_score的函数

1、weight:设置权重提升值,可以用于任何查询
2、field_value_factor: 将某个字段的值乘上old_score
3、random_score : 为每个用户都使用一个不同的随机评分对结果排序,但对某一具体用户来说,看到的顺序始终是一致的
4、衰减函数 (linear、exp、guass) : 以某个字段的值为基准,距离某个值越近得分越高
5、script_score : 当需求超出以上范围时,可以用自定义脚本完全控制评分计算,不过因为还要额外维护脚本不好维护,因此尽量使用ES提供的评分函数,需求真的无法满足再使用script_score

image.png

9.3.2 function_score其他辅助的参数

boost_mode
决定 old_score 和 加强score 如何合并
可选值:

multiply(默认) : new_score = old_score * 加强score
sum : new_score = old_score + 加强score
min : old_score 和 加强score 取较小值,new_score = min(old_score, 加强score)
max : old_score 和 加强score 取较大值,new_score = max(old_score, 加强score)
replace : 加强score直接替换掉old_score,new_score = 加强score

score_mode

决定functions里面的加强score们怎么合并,会先合并加强score们成一个总加强score,再使用总加强score去和old_score做合并,换言之就是会先执行score_mode,再执行boost_mode

可选值:

multiply (默认):将所有加强score相乘
sum:求和
avg:取平均值
first : 使加强首个函数(可以有filter,也可以没有)的结果作为最终结果
max:取最大值
min:取最小值

max_boost
限制加强函数的最大效果,就是限制加强score最大能多少,但要注意不会限制old_score

9.3.3 function_score语法
单加强函数语法

GET /索引库/映射类型/_search
{
    "query": {
        "function_score": {
            //主查询,查询完后这裡自己会有一个评分,就是old_score
            "query": {.....}, 
            //在old_score的基础上,给他加强其他字段的评分,这裡会产生一个加强score,如果只有一个加强function时,直接将加强函数名写在query下面就可以了
            "field_value_factor": {...}, 
            //指定用哪种方式结合old_score和加强score成为new_score
            "boost_mode": "multiply", 
            //限制加强score的最高分,但是不会限制old_score
            "max_boost": 1.5 
        }
    }
}

多加强函数语法

GET /索引库/映射类型/_search
{
    "query": {
        "function_score": {
            "query": {.....},
            "functions": [
                //可以有多个加强函数(或是filter+加强函数),每一个加强函数会产生一个加强score,因此functions会有多个加强score
                { "field_value_factor": ... },
                { "gauss": ... },
                { "filter": {...}, "weight": ... }
            ],
            //决定加强score们怎么合并
            "score_mode": "sum", 
            //决定总加强score怎么和old_score合并
            "boost_mode": "multiply" 
        }
    }
}

weight加强函数用法

GET /shop/goods/_search
{
  "query": {
    "function_score": {
      "query": {
        "match_all": {}
      },
      "functions": [
        {"filter": {
          "range": {
            "price": {
              "gte": 1000,
              "lte": 3000
            }
          }
        }, "weight": 3}
      ],
      "boost_mode": "sum"
    }
  }
}

解析:查询所有文档,如果某个文档的价格在1000~3000范围内,文档评分就会*3,并且new_score会和old_score相加得到最终评分

image.png

random_score加强函数使用案例

GET /shop/goods/_search
{
  "query": {
    "function_score": {
      "query": {
        "match_all": {}
      },
      "functions": [
        {"random_score": {
          "seed": 2
        }}
      ]
    }
  }
}

解析:不同的用户,可以设置不同的seed值(比如用户的id号),实现随机排序的效果,但是对同一个用户排序结果又是恒定的

GET /shop/goods/_search
{
  "query": {
    "function_score": {
      "query": {
        "match_all": {}
      },
      "functions": [
        {"field_value_factor": {
          "field": "price"
        }}
      ]
    }
  }
}

解析:查询所有文档,并且将所有文档的old_score,乘以本身的价格,得到new_score,默认将new_score * old_score,得到最终评分

9.3.4 衰减函数评分

什么是衰减函数?

以某一个范围为基准,距离这个范围越远,评分越低。 比如以100为基准,那么大于100,或者小于100评分都将变得越来越低。

为什么需要衰减函数?

在一次搜索中,某个条件并不一定是线性增长或者递减来影响最终结果评分的。比如搜索商品,并不是价格越低就意味着越好,用户就会越感兴趣,往往可能维系在一个价格区间的用户会更感兴趣一些。比如原来做过一个调查,如果买车大概会买什么价位的,最后有40%的人选择的是10~15w之间的车型。所以我们会发现,价格这个因素,对用户来说并不是越高越好,同时也不意味着越低越好,而衰减函数就是为了对这一类的数据进行评分的

衰减函数的分类

linear、exp 和 gauss,三种衰减函数的差别只在于衰减曲线的形状,在DSL的语法上的用法完全一样
linear : 线性函数是条直线,一旦直线与横轴相交,所有其他值的评分都是0
exp : 指数函数是先剧烈衰减然后变缓
gauss(最常用) : 高斯函数则是钟形的,他的衰减速率是先缓慢,然后变快,最后又放缓
field_value_factor加强函数使用案例

image.png

衰减函数的支持参数

origin : 中心点,或是字段可能的最佳值,落在原点(origin)上的文档评分_score为满分1.0,支持数值、时间 以及 "经纬度地理座标点"等类型字段 _
offset : 从 origin 为中心,为他设置一个偏移量offset覆盖一个范围,在此范围内所有的评分_score也都是和origin一样满分1.0
scale : 衰减率,即是一个文档从origin下落时,_score改变的速度

衰减函数案例

GET /mytest/doc/_search
{
    "query": {
        "function_score": {
            "functions": [
                //第一个gauss加强函数,决定距离的衰减率
                {
                    "gauss": {
                        "location": {
                            "origin": {  //origin点设成酒店的经纬度座标
                                "lat": 51.5,
                                "lon": 0.12
                            },
                            "offset": "2km", //距离中心点2km以内都是满分1.0,2km外开始衰减
                            "scale": "3km"  //衰减率
                        }
                    }
                },
                //第二个gauss加强函数,决定价格的衰减率,因为用户对价格更敏感,所以给了这个gauss                      加强函数2倍的权重
                {
                    "gauss": {
                        "price": {
                            "origin": "50", 
                            "offset": "50",
                            "scale": "20"
                        }
                    },
                    "weight": 2
                }
            ]
        }
    }
}

JavaAPI设置评分

/**
* 自定义评分 
*/
@Test
public void functionScore() throws IOException {
    List<FunctionScoreQueryBuilder.FilterFunctionBuilder> list 
        = new ArrayList<>();
    list.add(new FunctionScoreQueryBuilder.
        FilterFunctionBuilder(ScoreFunctionBuilders.
            gaussDecayFunction("location", new GeoPoint(22.586203, 114.031687),             "6km", "5km")));

    SearchRequest searchRequest = new SearchRequest("soufang").types("house");
    searchRequest.source().query(
        QueryBuilders.functionScoreQuery(QueryBuilders.matchAllQuery(),                     list.toArray(new FunctionScoreQueryBuilder.FilterFunctionBuilder[0]))
            .boostMode(CombineFunction.REPLACE));

    SearchResponse response = 
        restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    SearchHits hits = response.getHits();
    for (SearchHit hit : hits) {
        System.out.println("查询结果:" + hit.getSourceAsString() + " 评分:" + hit.getScore());
    }
}

例子:
注意:如果price用“scaled_float”会报错。需要改成double


image.png

十、分页
任何搜索都可以加两个参数“from”/"size",相当于数据库的limit ?,?
总条数在结果中的“total”参数中,可以自己运算出分页数


image.png

十一、ElasticSearch集群搭建

1、创建基本目录/usr/local/es-cluster

image.png

2、在master/conf/elasticsearch.yml添加如下内容

bootstrap.memory_lock: false
cluster.name: "es-cluster"
node.name: es-master
node.master: true
node.data: false
network.host: 0.0.0.0
http.port: 9200
transport.tcp.port: 9300
discovery.zen.ping.unicast.hosts: ["es-master:9300"]
discovery.zen.minimum_master_nodes: 1

path.logs: /usr/share/elasticsearch/logs
http.cors.enabled: true
http.cors.allow-origin: "*"
xpack.security.audit.enabled: true

3、在node1&node2/conf/elasticsearch.yml添加如下内容

cluster.name: "es-cluster"
node.name: node1 #这里注意替换
node.master: false
node.data: true
network.host: 0.0.0.0
http.port: 9202
transport.tcp.port: 9302
discovery.zen.ping.unicast.hosts: ["es-master:9300"]

path.logs: /usr/share/elasticsearch/logs

4、编写docker-compose.yml

version: '3.1'
services:
     es-master:
       image:  elasticsearch:6.8.5
       container_name: es-master
       restart: always
       volumes:
         - ./master/data:/usr/share/elasticsearch/data:rw
         - ./master/conf/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
         - ./master/logs:/user/share/elasticsearch/logs:rw
       ports:
         - 9200:9200
         - 9300:9300
       networks:
         - es-network
     es-node1:
       image:  elasticsearch:6.8.5
       container_name: es-node1
       restart: always
       networks:
         - es-network
       volumes:
         - ./node1/data:/usr/share/elasticsearch/data:rw
         - ./node1/conf/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
         - ./node1/logs:/user/share/elasticsearch/logs:rw
     es-node2:
       image:  elasticsearch:6.8.5
       container_name: es-node2
       restart: always
       networks:
         - es-network
       volumes:
         - ./node2/data:/usr/share/elasticsearch/data:rw
         - ./node2/conf/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
         - ./node2/logs:/user/share/elasticsearch/logs:rw
     es-head:
       image: mobz/elasticsearch-head:5
       container_name: es-head
       restart: always
       ports:
         - 9100:9100
       networks:
         - es-network
     kibana:
       image: kibana:6.8.5
       restart: always
       container_name: kibana 
       environment:
         SERVER_NAME: kibana
         ELASTICSEARCH_URL: http://192.168.195.135:9200
       ports:
         - 5601:5601
       networks:
         - es-network
networks:
  es-network:

5、启动docker-compose.yml

#授权
chmod 777 -R master node1 node2

#启动
docker-compose up -d

安装中遇到问题

问题:max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
解决:在宿主机执行sysctl -w vm.max_map_count=262144,重启docker容器

十二、综合使用

12.1、案例1、


image.png

image.png

代码实现

/**
     * 通过查询条件,执行相应的查询
     * @param searchParams
     * @return
     * @throws IOException
     */
    @Override
    public List<Hotal> query(SearchParams searchParams) throws IOException {
        //构建查询构建器

        //城市查询
        TermQueryBuilder cityQuery = QueryBuilders.termQuery("cityname", searchParams.getCityName());
        //通过关键词匹配多个字段
        QueryBuilder keywordQuery = null;
        if (StringUtils.isNotEmpty(searchParams.getKeyword())) {
            //用户关键字不为空
            keywordQuery = QueryBuilders
                    .multiMatchQuery(searchParams.getKeyword())
                    .field("hotalName").boost(2)
                    .field("brand").boost(2)
                    .field("regid")
                    .field("keyword")
                    .field("hotalInfo");
        } else {
            keywordQuery = cityQuery;
        }
        //通过价格限制条件查询
        RangeQueryBuilder priceQuery = QueryBuilders.rangeQuery("price")
                .gte(searchParams.getMinPirce() != null ? searchParams.getMinPirce().doubleValue() : 0)
                .lte(searchParams.getMaxPirce() != null ? searchParams.getMaxPirce().doubleValue() : Integer.MAX_VALUE);

        //bool查询将以上两个查询整合
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
                .must(keywordQuery)
                .must(priceQuery);

        //降级查询 - 符合这个条件的文档,评分会降低
        //bool对城市查询取非
        BoolQueryBuilder boolQuery2 = QueryBuilders.boolQuery().mustNot(cityQuery);
        //使用boosting查询将以上两个bool查询组合起来
        BoostingQueryBuilder execQuery = QueryBuilders.boostingQuery(
                boolQuery,
                boolQuery2
        ).negativeBoost(0.2f);
        return execQuery;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269