ElasticSearch - 全文檢索服務(wù) - RestHightLevel版

一、引言


1.1 數(shù)據(jù)庫(kù)查詢?yōu)槭裁催€要ElasticSearch?

數(shù)據(jù)庫(kù)一般只適合保存搜索結(jié)構(gòu)化的數(shù)據(jù),對(duì)于非結(jié)構(gòu)化的數(shù)據(jù)( 比如文章內(nèi)容),只能通過(guò)like%%模糊查詢,但是在大量的數(shù)據(jù)面前,like%%有兩個(gè)弊端:

1)搜索效率會(huì)很差,因?yàn)槭亲鲆粋€(gè)全表掃描(like%%會(huì)讓索引失效)

2)搜索沒(méi)辦法通過(guò)相關(guān)度匹配排序(可能返回的是用戶不關(guān)心的結(jié)果)

ElasticSearch就可以解決這些問(wèn)題

1.2 什么是全文檢索?

全文檢索 將非結(jié)構(gòu)化數(shù)據(jù)中的一部分信息提取出來(lái),重新組織,使其變得具有一定結(jié)構(gòu),然后對(duì)此有一定結(jié)構(gòu)的數(shù)據(jù)進(jìn)行搜索,從而達(dá)到搜索相對(duì)較快的目的。這部分從非結(jié)構(gòu)化數(shù)據(jù)中提取出的然后重新組織的信息,我們稱之索引。

例如字典的拼音表和部首檢字表就相當(dāng)于字典的索引,通過(guò)查找拼音表或者部首檢字表就可以快速的查找到我們要查的字。

這種先建立索引,再對(duì)索引進(jìn)行搜索的過(guò)程就叫全文檢索(Full-text Search)。

1.3 全文檢索的流程


image.png

1.4 構(gòu)建索引的過(guò)程

索引過(guò)程,對(duì)要搜索的原始內(nèi)容進(jìn)行索引構(gòu)建一個(gè)索引庫(kù),索引過(guò)程包括:

獲得文檔→創(chuàng)建文檔→分析文檔→索引文檔。

1.4.1 獲得原始文檔

原始文檔是指要索引和搜索的內(nèi)容。原始內(nèi)容包括互聯(lián)網(wǎng)上的網(wǎng)頁(yè)、數(shù)據(jù)庫(kù)中的數(shù)據(jù)、磁盤(pán)上的文件等。

1.4.2 創(chuàng)建文檔對(duì)象(Document)

獲取原始文檔的目的是為了索引,在索引前需要將原始內(nèi)容創(chuàng)建成文檔(Document),文檔中包括一個(gè)一個(gè)的域(Field),域中存儲(chǔ)內(nèi)容。

1.4.3 分析文檔(分詞)

將原始內(nèi)容創(chuàng)建為包含域(Field)的文檔(document),需要再對(duì)域中的內(nèi)容進(jìn)行分析,分析的過(guò)程是經(jīng)過(guò)對(duì)原始文檔提取單詞、將字母轉(zhuǎn)為小寫(xiě)、去除標(biāo)點(diǎn)符號(hào)、去除停用詞等過(guò)程生成最終的語(yǔ)匯單元。

1.4.4 創(chuàng)建索引

創(chuàng)建索引是對(duì)語(yǔ)匯單元索引,通過(guò)詞語(yǔ)找文檔,這種索引的結(jié)構(gòu)叫倒排索引結(jié)構(gòu)。

1.5 倒排索引

1.5.1 正向索引

簡(jiǎn)單來(lái)說(shuō),正向索引就是根據(jù)文件ID找到該文件的內(nèi)容,在文件內(nèi)容中匹配搜索關(guān)鍵字,這種方法是順序掃描方法,數(shù)據(jù)量大、搜索慢。


image.png

1.5.2 反向(倒排)索引

倒排索引和正向索引剛好相反,是根據(jù)內(nèi)容(詞語(yǔ))找文檔


image.png

二、ElasticSearch


2.1 ElasticSearch簡(jiǎn)介

ElasticSearch基本概念

2.1.1 索引庫(kù)(index)

索引庫(kù)是ElasticSearch存放數(shù)據(jù)的地方,可以理解為關(guān)系型數(shù)據(jù)庫(kù)中的一個(gè)數(shù)據(jù)庫(kù)。事實(shí)上,我們的數(shù)據(jù)被存儲(chǔ)和索引在分片(shards)中,索引只是一個(gè)把一個(gè)或多個(gè)分片分組在一起的邏輯空間。

2.1.2 映射類型(type)

映射類型用于區(qū)分同一個(gè)索引下不同的數(shù)據(jù)類型,相當(dāng)于關(guān)系型數(shù)據(jù)庫(kù)中的表。

注意:在 6.0 的index下是無(wú)法創(chuàng)建多個(gè)type,并且會(huì)在 7.0 中完全移除。

2.1.3 文檔(documents)

文檔是ElasticSearch中存儲(chǔ)的實(shí)體,類比關(guān)系型數(shù)據(jù)庫(kù),每個(gè)文檔相當(dāng)于數(shù)據(jù)庫(kù)表中的一行數(shù)據(jù)。

2.1.4 字段(fields)

文檔由字段組成,相當(dāng)于關(guān)系數(shù)據(jù)庫(kù)中列的屬性。

2.1.5 分片與副本

如果一個(gè)索引具有很大的數(shù)據(jù)量,它的數(shù)據(jù)量可能會(huì)超出單個(gè)節(jié)點(diǎn)的容量限制(硬盤(pán)容量),而且單個(gè)節(jié)點(diǎn)數(shù)據(jù)量過(guò)大,執(zhí)行性能也會(huì)隨之下降,每個(gè)搜索請(qǐng)求的執(zhí)行效率都會(huì)降低。 為了解決上述問(wèn)題, Elasticsearch 提出了分片的概念,索引將劃分成多份,稱為分片。每個(gè)分片都可以創(chuàng)建對(duì)應(yīng)的副本,以便保證服務(wù)的高可用性。

2.2 ElasticSearch的安裝

1)準(zhǔn)備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行必須寫(xiě)elasticsearch所在機(jī)器的ip地址,不能寫(xiě)127.0.0.1

2)執(zhí)行docker-compose up -d 命令啟動(dòng)容器
docker-compose up -d

注意:第一次創(chuàng)建容器會(huì)失敗,需要給.es文件夾賦予權(quán)限,執(zhí)行chmod 777 -R ./es命令,然后重啟容器

3)安裝中文分詞器

進(jìn)入elasticsearch容器,執(zhí)行中文分詞器相關(guān)安裝命令

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

注意:可以訪問(wèn)

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

查找和當(dāng)前es匹配的版本

4)手動(dòng)安裝中文分詞器

如果第3步安裝超時(shí)失敗,可以嘗試進(jìn)行手動(dòng)安裝,如果第三步成功則跳過(guò)該步驟

4.1)手動(dòng)下載對(duì)應(yīng)的IK分詞器版本https://github.com/medcl/elasticsearch-analysis-ik/releases

4.2)直接將分詞器壓縮包上傳到容器的plugins路徑(上傳到宿主機(jī)的容器卷路徑即可)

4.3)解壓分詞器

unzip elasticsearch-analysis-ik-6.8.5.zip -d ./ik-analyze
5)重啟容器
docker-compose restart
6)訪問(wèn)kibana服務(wù)
image.png

7)測(cè)試IK分詞器

POST _analyze
{
  "analyzer":"ik_smart",
  "text":"殲10系列戰(zhàn)斗機(jī)"
}


POST _analyze
{
  "analyzer":"ik_max_word",
  "text":"殲10系列戰(zhàn)斗機(jī)"
}
image.png

2.3 索引庫(kù)(Index)相關(guān)操作

2.3.1 概述

ElasticSearch采用Rest風(fēng)格的API,因此其API就是一次Http請(qǐng)求

請(qǐng)求分為: PUT POST GET DELETE

GET:查詢數(shù)據(jù)

PUT:插入數(shù)據(jù)

POST:更新數(shù)據(jù),實(shí)際上很多情況下 es 不是很清晰你到底要作什么,有些時(shí)候POST也可用于新增或者查詢

DELETE: 刪除數(shù)據(jù)

2.3.2 新增索引庫(kù)語(yǔ)法

PUT /索引庫(kù)名稱
{
  "settings":{
    "number_of_shards": 3, #分片的數(shù)量
    "number_of_replicas": 2 #副本的數(shù)量
  }
}

settings:表示索引庫(kù)的設(shè)置 number_of_shards:表示分片的數(shù)量 number_of_replicas:副本數(shù)量

2.3.3 查詢索引信息

GET /索引庫(kù)名稱

2.3.4 判斷索引庫(kù)是否存在

HEAD /索引庫(kù)名稱

2.3.5 刪除索引庫(kù)

DELETE /索引庫(kù)名稱

注意
1)索引庫(kù) 類似于 MySQL中數(shù)據(jù)庫(kù)的概念
2)如果創(chuàng)建索引不指定settings,默認(rèn)會(huì)有5個(gè)分片,1個(gè)副本

2.4 映射類型(type)相關(guān)操作
2.4.1 新增映射類型語(yǔ)法

PUT /索引庫(kù)名/_mapping/類型名稱
{
    "properties": {
        "字段名1": {
            "type": "類型",
            "index": true,
            "store": true,
            "analyzer": "分詞器"
        },
        "字段名2": {
            "type": "類型",
            "index": true,
            "store": true,
            "analyzer": "分詞器"
        }
    }
}

type:類型,可以是text、long、date、integer、object、keyword(表示關(guān)鍵字,不能被分詞)
index:是否參與索引,默認(rèn)為true
store:是否參與存儲(chǔ),默認(rèn)為false
analyzer:分詞器,可選 “ik_max_word”或者“ik_smart”,表示使用ik分詞器

2.4.2 查看映射類型信息

GET /索引庫(kù)名稱/_mapping/類型名稱

2.4.3 字段屬性詳解

type

String類型,又分兩種: text:可分詞,不可參與聚合 keyword:不可分詞,數(shù)據(jù)會(huì)作為完整字段進(jìn)行匹配,可以參與聚合
Numerical數(shù)值類型,分兩類 基本數(shù)據(jù)類型:long、interger、short、byte、double、flfloat、half_flfloat 浮點(diǎn)數(shù)的高精度類型:scaled_float,需要指定一個(gè)精度因子,比如10或100,elasticsearch會(huì)把真實(shí)值乘以這個(gè)因子后存儲(chǔ),取出時(shí)再還原
Date:日期類型 elasticsearch可以對(duì)日期格式化為字符串存儲(chǔ),但是建議我們存儲(chǔ)為毫秒值,存儲(chǔ)為long,節(jié)省空間
boolean: 設(shè)置字段類型為boolean后,可以填入的值為:true、false、"true"、"false"
binary: binary類型接受base64編碼的字符串
geo_point: 地理點(diǎn)類型用于存儲(chǔ)地理位置的經(jīng)緯度對(duì)
更多類型參考:https://www.elastic.co/guide/en/elasticsearch/reference/6.5/mapping-types.html

index

index影響字段的索引情況。
true:字段會(huì)被索引,則可以用來(lái)進(jìn)行搜索。默認(rèn)值就是true
false:字段不會(huì)被索引,不能用來(lái)搜索
index的默認(rèn)值就是true,也就是說(shuō)你不進(jìn)行任何配置,所有字段都會(huì)被索引。但是有些字段是我們不希望被索引的,比如商品的圖片信息,就需要手動(dòng)設(shè)置index為false。

store

是否將數(shù)據(jù)進(jìn)行額外存儲(chǔ)。在學(xué)習(xí)lucene和solr時(shí),我們知道如果一個(gè)字段的store設(shè)置為false,那么在文檔列表中就不會(huì)有這個(gè)字段的值,用戶的搜索結(jié)果中不會(huì)顯示出來(lái)。
但是在Elasticsearch中,即便store設(shè)置為false,也可以搜索到結(jié)果。
原因是Elasticsearch在創(chuàng)建文檔索引時(shí),會(huì)將文檔中的原始數(shù)據(jù)備份,保存到一個(gè)叫做 _source 的屬性中。而且我們可以通過(guò)過(guò)濾 _source 來(lái)選擇哪些要顯示,哪些不顯示。而如果設(shè)置store為true,就會(huì)在 _source 以外額外存儲(chǔ)一份數(shù)據(jù),多余,因此一般我們都會(huì)將store設(shè)置為false,事實(shí)上,store的默認(rèn)值就是false。

analyzer

定義的是該字段的分析器,默認(rèn)的分析器是 standard 標(biāo)準(zhǔn)分析器,這個(gè)地方可定義為自定義的分析器。
比如IK分詞器為: ik_max_word 或者 ik_smart

boost

激勵(lì)因子。這個(gè)與lucene中一樣,我們可以通過(guò)指定一個(gè)boost值來(lái)控制每個(gè)查詢子句的相對(duì)權(quán)重。
該值默認(rèn)為1。一個(gè)大于1的boost會(huì)增加該查詢子句的相對(duì)權(quán)重
比如:

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數(shù)據(jù)庫(kù)中表的概念
2)從ElasticSearch 6.x之后,一個(gè)Index下只能有一個(gè)type

2.5 文檔相關(guān)(document)操作
2.5.1 添加文檔

#指定id的添加方式
PUT /索引庫(kù)名/類型名稱/id #id需要自己指定
{
  "field1":"value1",
  "field2":"value2",
  ...
}
#自動(dòng)生成id的添加方式
POST /索引庫(kù)名/類型名稱  #使用POST無(wú)需指定id
{
  "field1":"value1",
  "field2":"value2",
  ...
}
#批量添加文檔
PUT /索引庫(kù)名稱/類型名稱/_bulk
{"index":{"_id":id值1}}
{"field1":"value1", "field2":"value2"...}
{"index":{"_id":id值2}}
{"field1":"value1", "field2":"value2"...}
....

2.5.2 更新文檔

#全局更新,會(huì)將所有字段更新,沒(méi)有指定的字段會(huì)自動(dòng)刪除
PUT /索引庫(kù)名/類型名稱/id #需要更新的id,id必須存在,如果不存在就變成了添加
{
  "field1":"value1",
  "field2":"value2",
  ...
}
#局部更新,只更新需要更新的字段
POST /索引庫(kù)名/類型名稱/id/_update
{
  "doc":{
    "field1": "新的value"
  }
}

2.5.3 刪除文檔

DELETE /索引庫(kù)名/類型名稱/id

2.5.4 查詢文檔

#查詢索引庫(kù)全部數(shù)據(jù)
GET /索引庫(kù)名稱/_search 
#根據(jù)id查詢
GET /索引庫(kù)名稱/類型名稱/id
#批量查詢
GET /_mget
{
    "docs": [
        {
            "_index": "索引庫(kù)名稱1",
            "_type": "映射類型1",
            "_id":"查詢文檔id1"
        },
        {
            "_index": "索引庫(kù)名稱2",
            "_type": "映射類型2",
            "_id":"查詢文檔id2"
        }
    ]
}

注意
1)文檔(document)類似于 數(shù)據(jù)庫(kù)中表的一條記錄
2)當(dāng)添加的文檔中,設(shè)置的field,而type中沒(méi)有時(shí),type會(huì)自動(dòng)的添加該field的映射記錄,
這是elasticsearch的自動(dòng)映射功能

三、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對(duì)象

@Autowired
 private RestHighLevelClient restHighLevelClient;

3.2 使用SpringBoot操作索引庫(kù)(Index)

 @Autowired
    private RestHighLevelClient client;


    /**
     * 創(chuàng)建索引
     * @param indexName
     * @return
     */
    @Override
    public boolean createIndex(String indexName) {

        CreateIndexRequest indexRequest = new CreateIndexRequest(indexName);
        //設(shè)置索引庫(kù)的相關(guān)屬性
        Settings settings = Settings.builder()
                .put("number_of_shards", 1)//設(shè)置分片數(shù)量
                .put("number_of_replicas", 0)//設(shè)置副本數(shù)量
                .build();
        indexRequest.settings(settings);

        try {
            CreateIndexResponse response = client.indices().create(indexRequest, RequestOptions.DEFAULT);
            //返回結(jié)果
            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();
            //設(shè)置到Request對(duì)象中
            putMappingRequest.source(builder);
            client.indices().putMapping(putMappingRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

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

/**
     * 給索引庫(kù)添加文檔
     * @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("狀態(tài):" + status);
            System.out.println("返回:" + lowercase);
            System.out.println("序號(hào):" + seqNo);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

刪除文檔

 /**
     * 根據(jù)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("結(jié)果:" + status);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

更新文檔

 /**
     * 根據(jù)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("狀態(tài):" + status);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

四、基本查詢
4.1 term、terms查詢
什么是term查詢?
term是代表完全匹配,也就是精確查詢,搜索前不會(huì)再對(duì)搜索詞進(jìn)行分詞,所以我們的搜索詞必須是文檔分詞集合中的一個(gè)。 比如文檔內(nèi)容為:"美的微波爐",被分詞為"美的"和"微波爐",term搜索的關(guān)鍵字必須為"美的"或者"微波爐"才能搜索出這個(gè)文檔,搜索"美的微波爐"搜索不出來(lái)
語(yǔ)法

#term查詢
GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "term": {
      "字段名稱": {
        "value": "搜索關(guān)鍵字"
      }
    }
  }
}
#terms查詢 - 可以同時(shí)查詢多個(gè)關(guān)鍵詞
GET /索引庫(kù)/映射類型/_search
{
  "query":{
    "terms": {
      "字段名稱": [
        "關(guān)鍵字1","關(guān)鍵字2"...
      ]
    }
  }  
}

注意:terms查詢 多個(gè)關(guān)鍵字之間是或者的關(guān)系,也就是說(shuō)只要符合一個(gè)關(guān)鍵字的文檔就會(huì)被查詢出來(lái)

4.2 match查詢

什么是match查詢?

match 查詢是高層查詢,它們了解字段映射的信息:

1.如果查詢 日期(date) 或 整數(shù)(integer) 字段,它們會(huì)將查詢字符串分別作為日期或整數(shù)對(duì)待。

2.如果查詢一個(gè)( not_analyzed )未分詞的精確值字符串字段, 它們會(huì)將整個(gè)查詢字符串作為單個(gè)詞項(xiàng)對(duì)待。

3.但如果要查詢一個(gè)( analyzed )已分析的全文字段, 它們會(huì)先將查詢字符串傳遞到一個(gè)合適的分析器,然后生成一個(gè)供查詢的詞項(xiàng)列表。 一旦組成了詞項(xiàng)列表,這個(gè)查詢會(huì)對(duì)每個(gè)詞項(xiàng)逐一執(zhí)行底層的查詢,再將結(jié)果合并,然后為每個(gè)文檔生成一個(gè)最終的相關(guān)度評(píng)分。 match查詢其實(shí)底層是多個(gè)term查詢,最后將term的結(jié)果合并。

語(yǔ)法

#match_all查詢 - 查詢指定庫(kù)的指定類型的所有文檔
GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "match_all": {}
  }
}
#match查詢 - 根據(jù)關(guān)鍵字查詢
GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "match": {
      "字段名稱": "搜索關(guān)鍵字"
    }
  }
}
#布爾match查詢
GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "match": {
      "字段名稱": {
        "query": "搜索關(guān)鍵字",
        "operator": "OR或者AND"  
      }
    }
  }
}

注意:operator值為
and表示關(guān)鍵詞分詞后的結(jié)果,必須全部匹配上
or表示需要一個(gè)分詞匹配上即可,默認(rèn)為or

#mulit_match查詢 - 可以查詢多個(gè)字段
GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "multi_match": {
      "query": "搜索關(guān)鍵字",
      "fields": ["字段名稱1^2.0", "字段名稱2^0.5"],
      "operator": "or"
    }
  }
}

注意:
^2.0表示這個(gè)字段在搜索中的權(quán)重,值越高權(quán)重越大,可以不設(shè)置。

image.png
#match_phrase查詢 - 短語(yǔ)查詢
GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "match_phrase": {
      "字段名稱": "關(guān)鍵詞1 關(guān)鍵詞2"
    }
  }
}

注意:

match_phrase查詢,只會(huì)匹配關(guān)鍵詞1 和關(guān)鍵詞2 挨在一起的文檔,如果兩個(gè)關(guān)鍵詞分開(kāi)太遠(yuǎn)的文檔是不會(huì)匹配上的

4.3 ids查詢
什么ids查詢?
ids查詢是一類簡(jiǎn)單的查詢,它過(guò)濾返回的文檔只包含其中指定標(biāo)識(shí)符的文檔,
該查詢默認(rèn)指定作用在“_id”上面。
語(yǔ)法

GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "ids": {
      "values": ["1","3","6"...]
    }
  }
}
image.png

4.4 prefix前綴查詢
什么是prefix查詢?
前綴查詢,可以使我們找到某個(gè)字段以給定前綴開(kāi)頭的文檔。最典型的使用場(chǎng)景,一般是在文本框錄入的時(shí)候的聯(lián)想功能
語(yǔ)法

GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "prefix": {
      "字段名稱": {
        "value": "前綴"
      }
    }
  }
}

注意:前綴查詢并不是和搜索字段的內(nèi)容前綴匹配,而是和搜索字段的所有分詞的前綴匹配,匹配上一個(gè)分詞后,就會(huì)查詢出該文檔,建議和keyword類型的字段結(jié)合使用


image.png

4.5 fuzzy查詢

什么是fuzzy查詢?

fuzzy(模糊)查詢是一種模糊查詢,term 查詢的模糊等價(jià)。

語(yǔ)法

GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "fuzzy": {
      "字段名稱": {
        "value": "關(guān)鍵詞",
        "fuzziness": "2"
      }
    }
  }
}

注意:
1、fuzzy搜索以后,會(huì)自動(dòng)嘗試將你的搜索文本進(jìn)行糾錯(cuò),然后去跟文本進(jìn)行匹配
2、fuzziness屬性表示關(guān)鍵詞最多糾正的次數(shù), 比如空條 -> 空調(diào),需要糾正一次,fuzziness需要設(shè)置為1
3、prefix_length屬性表示不能被 “模糊化” 的初始字符數(shù)。 大部分的拼寫(xiě)錯(cuò)誤發(fā)生在詞的結(jié)尾,而不是詞的開(kāi)始。 例如通過(guò)將prefix_length 設(shè)置為 3 ,你可能夠顯著降低匹配的詞項(xiàng)數(shù)量。(前面3個(gè)字不能出錯(cuò),否則查不到)


image.png

4.6 wildcard查詢
什么是wildcard查詢?
wildcard(通配符)查詢意為通配符查詢

GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "wildcard": {
      "字段名稱": {
        "value": "關(guān)鍵詞? *"
      }
    }
  }
}

注意:
*表示匹配0或者多個(gè)字符
?表示匹配一個(gè)字符
wildcard查詢不注意查詢性能,應(yīng)盡可能避免使用。

4.7 range查詢
什么range查詢?
range查詢既范圍查詢,可以對(duì)某個(gè)字段進(jìn)行范圍匹配

GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "range": {
      "字段名稱": {
        "gte": 0,
        "lte": 2000,
      }
    }
  }
}
image.png

4.8 regexp查詢

什么是regexp查詢?
正則表達(dá)式查詢,wildcard和regexp查詢的工作方式和prefix查詢完全一樣。它們也需要遍歷倒排索引中的詞條列表來(lái)找到所有的匹配詞條,然后逐個(gè)詞條地收集對(duì)應(yīng)的文檔ID。它們和prefix查詢的唯一區(qū)別在于它們能夠支持更加復(fù)雜的模式。
語(yǔ)法

GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "regexp": {
      "字段名稱": "正則表達(dá)式"
    }
  }
}

注意:
1、prefix(前綴),wildcard(通配符)以及regexp(正則)查詢基于詞條進(jìn)行操作。如果你在一個(gè)analyzed字段上使用了它們,它們會(huì)檢查字段中的每個(gè)詞條,而不是整個(gè)字段。
2、對(duì)一個(gè)含有很多不同詞條的字段運(yùn)行這類查詢是非常消耗資源的。應(yīng)該避免使用一個(gè)以通配符開(kāi)頭的模式(比如,*foo)

image.png

4.9 使用JavaAPI實(shí)現(xiàn)以上查詢
查詢的基礎(chǔ)結(jié)構(gòu),通過(guò)不同的QueryBuilder對(duì)象,可以實(shí)現(xiàn)不同的查詢

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

        //循環(huán)結(jié)果
        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", "愛(ài)麗絲");

//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", "平三區(qū)")
                .fuzziness(Fuzziness.TWO)
                .prefixLength(0);
//wildcard
WildcardQueryBuilder wildcardQueryBuilder = 
            QueryBuilders.wildcardQuery("hotalName", "愛(ài)麗*");
//range
RangeQueryBuilder rangeQuery = 
            QueryBuilders.rangeQuery("star").gte(0).lt(3);
//regexp
RegexpQueryBuilder regexQuery = 
            QueryBuilders.regexpQuery("hotalName", "\\S{0,}[0-9]{1}.*");

五、復(fù)合查詢

5.1 bool查詢
bool 過(guò)濾器。 這是個(gè) 復(fù)合過(guò)濾器(compound fifilter) ,它可以接受多個(gè)其他過(guò)濾器作為參數(shù),并將這些過(guò)濾器結(jié)合成各式各樣的布爾(邏輯)組合。
語(yǔ)法

GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "content": {
              "value": "性價(jià)比"
            }
          }
        },{
          "match": {
            "title": "微波爐"
          }
        }
      ],
      "must": [
        {
          "match": {
            "content": "格力造"
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "price": {
              "gte": 300,
              "lte": 3000
            }
          }
        }
      ],
      "filter": {
        "match": {
          "title": "美的"
        }
      }
    }
  }
}

例子

#bool查詢 - 將多個(gè)基本查詢組合在一起
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子句的條件,并且參與計(jì)算分值,與 AND 等價(jià)

must_not:所有的語(yǔ)句都 不能(must not) 匹配,與 NOT 等價(jià)

should: 返回的文檔可能滿足should子句的條件。在一個(gè)Bool查詢中,如果沒(méi)有must或者filter,有一個(gè)或 者多個(gè)should子句,那么只要滿足一個(gè)就可以返回,與 OR 等價(jià)

minimum_should_match:用來(lái)指定should至少需要匹配幾個(gè)語(yǔ)句

filter:返回的文檔必須滿足filter子句的條件。但是不會(huì)像Must一樣,參與計(jì)算分值

注意

如果查詢中沒(méi)有must語(yǔ)句,那么至少要匹配一個(gè)should語(yǔ)句

5.1.1 什么是filter?

filter vs query
filter ,僅僅只是按照搜索條件過(guò)濾出需要的數(shù)據(jù)而已,不計(jì)算任何相關(guān)度分?jǐn)?shù),對(duì)相關(guān)度沒(méi)有任何影響;
query ,會(huì)去計(jì)算每個(gè)document相對(duì)于搜索條件的相關(guān)度,并按照相關(guān)度進(jìn)行排序;
一般來(lái)說(shuō),如果你是在進(jìn)行搜索,需要將最匹配搜索條件的數(shù)據(jù)先返回,那么用query;如果你只是要根據(jù)一些條件篩選出一部分?jǐn)?shù)據(jù),不關(guān)注其排序,那么用filter; 除非是你的這些搜索條件,你希望越符合這些搜索條件的document越排在前面返回,那么這些搜索條件要放在query中;如果你不希望一些搜索條件來(lái)影響你的document排序,那么就放在filter中即可;
filter和query的性能對(duì)比
filter ,不需要計(jì)算相關(guān)度分?jǐn)?shù),不需要按照相關(guān)度分?jǐn)?shù)進(jìn)行排序,同時(shí)還有內(nèi)置的自動(dòng)cache最常使用filter的數(shù)據(jù)
query ,相反,要計(jì)算相關(guān)度分?jǐn)?shù),按照分?jǐn)?shù)進(jìn)行排序,而且無(wú)法cache結(jié)果
所以filter查詢性能會(huì)高于query

5.3 boosting查詢
什么是boosting查詢?

該查詢用于將兩個(gè)查詢封裝在一起,并降低其中一個(gè)查詢所返回文檔的分值。它接受一個(gè)positive查詢和一個(gè)negative查詢。只有匹配了positive查詢的文檔才會(huì)被包含到結(jié)果集中,但是同時(shí)匹配了negative查詢的文檔會(huì)被降低其相關(guān)度,通過(guò)將文檔原本的score和negative_boost參數(shù)進(jìn)行相乘來(lái)得到新的score。因此,negative_boost參數(shù)必須小于1.0
(positive作為查詢條件,如果查詢條件同時(shí)符合negative條件,則會(huì)降低分值)
"negative_boost": 會(huì)把原來(lái)的分值乘以negative_boost的值作為最后的分值
如果negative_boost>1:提高分值
如果negative_boost<1:降低分值

運(yùn)用場(chǎng)景

例如,在互聯(lián)網(wǎng)上搜索"蘋(píng)果"也許會(huì)返回,水果或者各種食譜的結(jié)果。但是用戶可能只想搜索到蘋(píng)果手機(jī)等電子產(chǎn)品,當(dāng)然我們可以通過(guò)排除“水果 喬木 維生素”和這類單詞,結(jié)合bool查詢中的must_not子句,將結(jié)果范圍縮小到只剩蘋(píng)果手機(jī),但是這種做法難免會(huì)排除掉那些真的想搜索水果的用戶,這時(shí)可以通過(guò)boosting查詢,通過(guò)降低“水果 喬木 維生素”等關(guān)鍵詞的評(píng)分,讓蘋(píng)果等電子產(chǎn)品的排名靠前

語(yǔ)法

GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "boosting": {
      "positive": {
        "match": {
          "title": "性價(jià)比"
        }
      },
      "negative": {
        "match": {
          "content": "性價(jià)比"
        }
      },
      "negative_boost": 0.1
    }
  }
}

5.4 使用JavaAPI實(shí)現(xiàn)以上查詢

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

例子:


image.png

六、排序

ElasticSearch默認(rèn)會(huì)有一套相關(guān)性分?jǐn)?shù)計(jì)算,分?jǐn)?shù)越高,說(shuō)明文檔相關(guān)性越大,也就越會(huì)排在前面。除了相關(guān)性排序之外,開(kāi)發(fā)者也可以通過(guò)自己的需要,通過(guò)某些規(guī)則設(shè)置查詢文檔的排序
如果進(jìn)行手動(dòng)排序,則評(píng)分為null

語(yǔ)法

GET /索引庫(kù)/映射類型/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "排序字段1": {
        "order": "asc"
      }
    },
    {
      "排序字段2":{
        "order": "desc"
      }
    }
  ]
}
例子:
#手動(dòng)排序
GET /hotal_index/_search
{
  "query": {
    "match": {
      "hotalName": "品牌連鎖酒店"
    }
  },
  "sort": [
    {
      "price": {
        "order": "asc"
      }
    }
  ]
}
//創(chuàng)建查詢構(gòu)建器
QueryBuilder queryBuilder = .........
..................

//執(zhí)行查詢
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder
     .query(queryBuilder)
      //設(shè)置排序
     .sort("star", SortOrder.DESC)
     .sort("type", SortOrder.ASC);

七、高亮

什么是高亮?
許多應(yīng)用都傾向于在每個(gè)搜索結(jié)果中 高亮 顯示搜索的關(guān)鍵詞,比如字體的加粗,改變字體的顏色等.以便讓用戶知道為何該文檔符合查詢條件。在 Elasticsearch 中檢索出高亮片段也很容易。高亮顯示需要一個(gè)字段的實(shí)際內(nèi)容。 如果該字段沒(méi)有被存儲(chǔ)(映射mapping沒(méi)有將存儲(chǔ)設(shè)置為 true),則加載實(shí)際的source,并從source中提取相關(guān)的字段。

語(yǔ)法

GET /索引庫(kù)/映射類型/_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

參數(shù)含義
number_of_fragments: fragment 是指一段連續(xù)的文字。返回結(jié)果最多可以包含幾段不連續(xù)的文字。
默認(rèn)是5。
fragment_size: 某字段的值,長(zhǎng)度是1萬(wàn),但是我們一般不會(huì)在頁(yè)面展示這么長(zhǎng),可能只是展示一部分。設(shè)置要顯示出來(lái)的fragment文本判斷的長(zhǎng)度,默認(rèn)是100
noMatchSize: 搜索出來(lái)的這個(gè)文檔這個(gè)字段已經(jīng)顯示出高亮的情況,可是其它字段并沒(méi)有任何顯示,設(shè)置這個(gè)屬性可以顯示出來(lái)。
pre_tags: 標(biāo)記 highlight 的開(kāi)始標(biāo)簽。
post_tags: 標(biāo)記 highlight 的結(jié)束標(biāo)簽。

JavaAPI

//設(shè)置高亮信息
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);

........     
//獲得高亮結(jié)果
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 地理位置類型的相關(guān)操作

#創(chuàng)建映射類型:
PUT /soufang/_mapping/house
{
  "properties": {
    "name": {
      "type": "text"
    },
    "location": {
      "type": "geo_point"
    }
  }
}
#添加坐標(biāo)點(diǎn)數(shù)據(jù):
PUT /soufang/house/1
{
  "name": "市民中心",
  "location": {
    "lat": 22.54737, #lat代表緯度
    "lon": 114.067531 #lon代表經(jīng)度
  }
}

8.2 通過(guò)geo_distance過(guò)濾器搜索坐標(biāo)

geo_distance:地理距離過(guò)濾器( geo_distance )以給定位置為圓心畫(huà)一個(gè)圓,來(lái)找出那些地理坐標(biāo)落在其中的文檔

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

distance:中心點(diǎn)的半徑距離
distance_type:兩點(diǎn)間的距離計(jì)算的精度算法
arc - 最慢但是最精確是弧形(arc)計(jì)算方式,這種方式把世界當(dāng)作是球體來(lái)處理
plane - 平面(plane)計(jì)算方式,把地球當(dāng)成是平坦的。 這種方式快一些但是精度略遜

8.3 通過(guò)geo_bounding_box過(guò)濾器搜索坐標(biāo)
geo_bounding_box: 查找某個(gè)長(zhǎng)方形區(qū)域內(nèi)的位置

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 通過(guò)geo_polygon過(guò)濾器搜索坐標(biāo)
geo_polygon:查找位于多邊形內(nèi)的地點(diǎn)

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 過(guò)濾結(jié)果通過(guò)距離排序
query:是查詢這個(gè)區(qū)域的house
sort:這個(gè)區(qū)域的house為location里的坐標(biāo)為零點(diǎn),按距離排序返回?cái)?shù)據(jù)

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)為單位,將距離設(shè)置到每個(gè)返回結(jié)果的 sort 鍵中

8.6 JavaApi的執(zhí)行方式

//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)


//根據(jù)距離排序
SortBuilders
    .geoDistanceSort("location", 22.586737, 113.960829)
    .order(SortOrder.DESC)
    .unit(DistanceUnit.KILOMETERS)

九、function_score自定義文檔相關(guān)性

9.1 什么是function_score?

在使用ES進(jìn)行全文搜索時(shí),搜索結(jié)果默認(rèn)會(huì)以文檔的相關(guān)度進(jìn)行排序,而這個(gè) "文檔的相關(guān)度",是可以通過(guò) function_score 自己定義的,也就是說(shuō)我們可以透過(guò)使用function_score,來(lái)控制 "怎么樣的文檔相關(guān)度更高" 這件事

9.2 文檔相關(guān)度評(píng)分默認(rèn)大概規(guī)則

1、關(guān)鍵詞詞頻越高,評(píng)分越高
2、關(guān)鍵詞在所有文檔中出現(xiàn)的頻率越高,評(píng)分越低
3、搜索的關(guān)鍵詞與目標(biāo)文檔中分詞匹配個(gè)數(shù)越多,評(píng)分越高
4、匹配的字段權(quán)重越高,評(píng)分越高

9.3 function_score的基本用法

9.3.1 function_score提供的加強(qiáng)_score的函數(shù)

1、weight:設(shè)置權(quán)重提升值,可以用于任何查詢
2、field_value_factor: 將某個(gè)字段的值乘上old_score
3、random_score : 為每個(gè)用戶都使用一個(gè)不同的隨機(jī)評(píng)分對(duì)結(jié)果排序,但對(duì)某一具體用戶來(lái)說(shuō),看到的順序始終是一致的
4、衰減函數(shù) (linear、exp、guass) : 以某個(gè)字段的值為基準(zhǔn),距離某個(gè)值越近得分越高
5、script_score : 當(dāng)需求超出以上范圍時(shí),可以用自定義腳本完全控制評(píng)分計(jì)算,不過(guò)因?yàn)檫€要額外維護(hù)腳本不好維護(hù),因此盡量使用ES提供的評(píng)分函數(shù),需求真的無(wú)法滿足再使用script_score

image.png

9.3.2 function_score其他輔助的參數(shù)

boost_mode
決定 old_score 和 加強(qiáng)score 如何合并
可選值:

multiply(默認(rèn)) : new_score = old_score * 加強(qiáng)score
sum : new_score = old_score + 加強(qiáng)score
min : old_score 和 加強(qiáng)score 取較小值,new_score = min(old_score, 加強(qiáng)score)
max : old_score 和 加強(qiáng)score 取較大值,new_score = max(old_score, 加強(qiáng)score)
replace : 加強(qiáng)score直接替換掉old_score,new_score = 加強(qiáng)score

score_mode

決定functions里面的加強(qiáng)score們?cè)趺春喜?,?huì)先合并加強(qiáng)score們成一個(gè)總加強(qiáng)score,再使用總加強(qiáng)score去和old_score做合并,換言之就是會(huì)先執(zhí)行score_mode,再執(zhí)行boost_mode

可選值:

multiply (默認(rèn)):將所有加強(qiáng)score相乘
sum:求和
avg:取平均值
first : 使加強(qiáng)首個(gè)函數(shù)(可以有filter,也可以沒(méi)有)的結(jié)果作為最終結(jié)果
max:取最大值
min:取最小值

max_boost
限制加強(qiáng)函數(shù)的最大效果,就是限制加強(qiáng)score最大能多少,但要注意不會(huì)限制old_score

9.3.3 function_score語(yǔ)法
單加強(qiáng)函數(shù)語(yǔ)法

GET /索引庫(kù)/映射類型/_search
{
    "query": {
        "function_score": {
            //主查詢,查詢完后這裡自己會(huì)有一個(gè)評(píng)分,就是old_score
            "query": {.....}, 
            //在old_score的基礎(chǔ)上,給他加強(qiáng)其他字段的評(píng)分,這裡會(huì)產(chǎn)生一個(gè)加強(qiáng)score,如果只有一個(gè)加強(qiáng)function時(shí),直接將加強(qiáng)函數(shù)名寫(xiě)在query下面就可以了
            "field_value_factor": {...}, 
            //指定用哪種方式結(jié)合old_score和加強(qiáng)score成為new_score
            "boost_mode": "multiply", 
            //限制加強(qiáng)score的最高分,但是不會(huì)限制old_score
            "max_boost": 1.5 
        }
    }
}

多加強(qiáng)函數(shù)語(yǔ)法

GET /索引庫(kù)/映射類型/_search
{
    "query": {
        "function_score": {
            "query": {.....},
            "functions": [
                //可以有多個(gè)加強(qiáng)函數(shù)(或是filter+加強(qiáng)函數(shù)),每一個(gè)加強(qiáng)函數(shù)會(huì)產(chǎn)生一個(gè)加強(qiáng)score,因此functions會(huì)有多個(gè)加強(qiáng)score
                { "field_value_factor": ... },
                { "gauss": ... },
                { "filter": {...}, "weight": ... }
            ],
            //決定加強(qiáng)score們?cè)趺春喜?            "score_mode": "sum", 
            //決定總加強(qiáng)score怎么和old_score合并
            "boost_mode": "multiply" 
        }
    }
}

weight加強(qiáng)函數(shù)用法

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

解析:查詢所有文檔,如果某個(gè)文檔的價(jià)格在1000~3000范圍內(nèi),文檔評(píng)分就會(huì)*3,并且new_score會(huì)和old_score相加得到最終評(píng)分

image.png

random_score加強(qiáng)函數(shù)使用案例

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

解析:不同的用戶,可以設(shè)置不同的seed值(比如用戶的id號(hào)),實(shí)現(xiàn)隨機(jī)排序的效果,但是對(duì)同一個(gè)用戶排序結(jié)果又是恒定的

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

解析:查詢所有文檔,并且將所有文檔的old_score,乘以本身的價(jià)格,得到new_score,默認(rèn)將new_score * old_score,得到最終評(píng)分

9.3.4 衰減函數(shù)評(píng)分

什么是衰減函數(shù)?

以某一個(gè)范圍為基準(zhǔn),距離這個(gè)范圍越遠(yuǎn),評(píng)分越低。 比如以100為基準(zhǔn),那么大于100,或者小于100評(píng)分都將變得越來(lái)越低。

為什么需要衰減函數(shù)?

在一次搜索中,某個(gè)條件并不一定是線性增長(zhǎng)或者遞減來(lái)影響最終結(jié)果評(píng)分的。比如搜索商品,并不是價(jià)格越低就意味著越好,用戶就會(huì)越感興趣,往往可能維系在一個(gè)價(jià)格區(qū)間的用戶會(huì)更感興趣一些。比如原來(lái)做過(guò)一個(gè)調(diào)查,如果買(mǎi)車大概會(huì)買(mǎi)什么價(jià)位的,最后有40%的人選擇的是10~15w之間的車型。所以我們會(huì)發(fā)現(xiàn),價(jià)格這個(gè)因素,對(duì)用戶來(lái)說(shuō)并不是越高越好,同時(shí)也不意味著越低越好,而衰減函數(shù)就是為了對(duì)這一類的數(shù)據(jù)進(jìn)行評(píng)分的

衰減函數(shù)的分類

linear、exp 和 gauss,三種衰減函數(shù)的差別只在于衰減曲線的形狀,在DSL的語(yǔ)法上的用法完全一樣
linear : 線性函數(shù)是條直線,一旦直線與橫軸相交,所有其他值的評(píng)分都是0
exp : 指數(shù)函數(shù)是先劇烈衰減然后變緩
gauss(最常用) : 高斯函數(shù)則是鐘形的,他的衰減速率是先緩慢,然后變快,最后又放緩
field_value_factor加強(qiáng)函數(shù)使用案例

image.png

衰減函數(shù)的支持參數(shù)

origin : 中心點(diǎn),或是字段可能的最佳值,落在原點(diǎn)(origin)上的文檔評(píng)分_score為滿分1.0,支持?jǐn)?shù)值、時(shí)間 以及 "經(jīng)緯度地理座標(biāo)點(diǎn)"等類型字段 _
offset : 從 origin 為中心,為他設(shè)置一個(gè)偏移量offset覆蓋一個(gè)范圍,在此范圍內(nèi)所有的評(píng)分_score也都是和origin一樣滿分1.0
scale : 衰減率,即是一個(gè)文檔從origin下落時(shí),_score改變的速度

衰減函數(shù)案例

GET /mytest/doc/_search
{
    "query": {
        "function_score": {
            "functions": [
                //第一個(gè)gauss加強(qiáng)函數(shù),決定距離的衰減率
                {
                    "gauss": {
                        "location": {
                            "origin": {  //origin點(diǎn)設(shè)成酒店的經(jīng)緯度座標(biāo)
                                "lat": 51.5,
                                "lon": 0.12
                            },
                            "offset": "2km", //距離中心點(diǎn)2km以內(nèi)都是滿分1.0,2km外開(kāi)始衰減
                            "scale": "3km"  //衰減率
                        }
                    }
                },
                //第二個(gè)gauss加強(qiáng)函數(shù),決定價(jià)格的衰減率,因?yàn)橛脩魧?duì)價(jià)格更敏感,所以給了這個(gè)gauss                      加強(qiáng)函數(shù)2倍的權(quán)重
                {
                    "gauss": {
                        "price": {
                            "origin": "50", 
                            "offset": "50",
                            "scale": "20"
                        }
                    },
                    "weight": 2
                }
            ]
        }
    }
}

JavaAPI設(shè)置評(píng)分

/**
* 自定義評(píng)分 
*/
@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("查詢結(jié)果:" + hit.getSourceAsString() + " 評(píng)分:" + hit.getScore());
    }
}

例子:
注意:如果price用“scaled_float”會(huì)報(bào)錯(cuò)。需要改成double


image.png

十、分頁(yè)
任何搜索都可以加兩個(gè)參數(shù)“from”/"size",相當(dāng)于數(shù)據(jù)庫(kù)的limit ?,?
總條數(shù)在結(jié)果中的“total”參數(shù)中,可以自己運(yùn)算出分頁(yè)數(shù)


image.png

十一、ElasticSearch集群搭建

1、創(chuàng)建基本目錄/usr/local/es-cluster

image.png

2、在master/conf/elasticsearch.yml添加如下內(nèi)容

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添加如下內(nèi)容

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、編寫(xiě)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、啟動(dòng)docker-compose.yml

#授權(quán)
chmod 777 -R master node1 node2

#啟動(dòng)
docker-compose up -d

安裝中遇到問(wèn)題

問(wèn)題:max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
解決:在宿主機(jī)執(zhí)行sysctl -w vm.max_map_count=262144,重啟docker容器

十二、綜合使用

12.1、案例1、


image.png

image.png

代碼實(shí)現(xiàn)

/**
     * 通過(guò)查詢條件,執(zhí)行相應(yīng)的查詢
     * @param searchParams
     * @return
     * @throws IOException
     */
    @Override
    public List<Hotal> query(SearchParams searchParams) throws IOException {
        //構(gòu)建查詢構(gòu)建器

        //城市查詢
        TermQueryBuilder cityQuery = QueryBuilders.termQuery("cityname", searchParams.getCityName());
        //通過(guò)關(guān)鍵詞匹配多個(gè)字段
        QueryBuilder keywordQuery = null;
        if (StringUtils.isNotEmpty(searchParams.getKeyword())) {
            //用戶關(guān)鍵字不為空
            keywordQuery = QueryBuilders
                    .multiMatchQuery(searchParams.getKeyword())
                    .field("hotalName").boost(2)
                    .field("brand").boost(2)
                    .field("regid")
                    .field("keyword")
                    .field("hotalInfo");
        } else {
            keywordQuery = cityQuery;
        }
        //通過(guò)價(jià)格限制條件查詢
        RangeQueryBuilder priceQuery = QueryBuilders.rangeQuery("price")
                .gte(searchParams.getMinPirce() != null ? searchParams.getMinPirce().doubleValue() : 0)
                .lte(searchParams.getMaxPirce() != null ? searchParams.getMaxPirce().doubleValue() : Integer.MAX_VALUE);

        //bool查詢將以上兩個(gè)查詢整合
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
                .must(keywordQuery)
                .must(priceQuery);

        //降級(jí)查詢 - 符合這個(gè)條件的文檔,評(píng)分會(huì)降低
        //bool對(duì)城市查詢?nèi)》?        BoolQueryBuilder boolQuery2 = QueryBuilders.boolQuery().mustNot(cityQuery);
        //使用boosting查詢將以上兩個(gè)bool查詢組合起來(lái)
        BoostingQueryBuilder execQuery = QueryBuilders.boostingQuery(
                boolQuery,
                boolQuery2
        ).negativeBoost(0.2f);
        return execQuery;
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容