1. 程式人生 > 其它 >ElasticSearch - 全文檢索服務 - RestHightLevel版

ElasticSearch - 全文檢索服務 - RestHightLevel版

技術標籤:分散式微服務elasticsearch大資料資料庫java

Author:Allen_Huang

Version:1.0.0

ElasticSearch - 全文檢索服務 - RestHightLevel版本

文章目錄

一、引言


1.1 資料庫查詢為什麼還要ElasticSearch?

資料庫一般只適合儲存搜尋結構化的資料,對於非結構化的資料( 比如文章內容),只能通過like%%模糊查詢,但是在大量的資料面前,like%%有兩個弊端:

1)搜尋效率會很差,因為是做一個全表掃描(like%%會讓索引失效)

2)搜尋沒辦法通過相關度匹配排序(可能返回的是使用者不關心的結果)

ElasticSearch就可以解決這些問題

1.2 什麼是全文檢索?

全文檢索 將非結構化資料中的一部分資訊提取出來,重新組織,使其變得具有一定結構,然後對此有一定結構的資料進行搜尋,從而達到搜尋相對較快的目的。這部分從非結構化資料中提取出的然後重新組織的資訊,我們稱之索引。

例如字典的拼音表和部首檢字表就相當於字典的索引,通過查詢拼音表或者部首檢字表就可以快速的查詢到我們要查的字。

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

1.3 全文檢索的流程

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wGmZhV8J-1607930409012)(img/image-20200521210532891.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-20200521223831851
1.5.2 反向(倒排)索引

倒排索引和正向索引剛好相反,是根據內容(詞語)找文件

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-UJ9tMTG0-1607930409014)(img/image-20200521224043288.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-20200516112615263
7)測試IK分詞器
POST _analyze
{
  "analyzer":"ik_smart",
  "text":"殲10系列戰鬥機"
}


POST _analyze
{
  "analyzer":"ik_max_word",
  "text":"殲10系列戰鬥機"
}
image-20200516113121815

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 /索引庫名稱

結果:
{
“qf” : {
“aliases” : { },
“mappings” : { },
“settings” : {
“index” : {
“creation_date” : “1582083142738”,
“number_of_shards” : “3”,
“number_of_replicas” : “2”,
“uuid” : “RaRHf6zISkqFh3a10FDc6A”,
“version” : {
“created” : “6050499”
},
“provided_name” : “qf”
}
}
}
}

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表示這個欄位在搜尋中的權重,值越高權重越大,可以不設定。

#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"...]
    }
  }
}

4.4 prefix字首查詢

什麼是prefix查詢?

字首查詢,可以使我們找到某個欄位以給定字首開頭的文件。最典型的使用場景,一般是在文字框錄入的時候的聯想功能

語法

GET /索引庫/對映型別/_search
{
  "query": {
    "prefix": {
      "欄位名稱": {
        "value": "字首"
      }
    }
  }
}

注意:字首查詢並不是和搜尋欄位的內容字首匹配,而是和搜尋欄位的所有分詞的字首匹配,匹配上一個分詞後,就會查詢出該文件,建議和keyword型別的欄位結合使用

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 ,你可能夠顯著降低匹配的詞項數量。

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,
      }
    }
  }
}

注意:gte表示>=,lte表示<=,gt表示>,lt表示<

4.8 regexp查詢

什麼是regexp查詢?

正則表示式查詢,wildcard和regexp查詢的工作方式和prefix查詢完全一樣。它們也需要遍歷倒排索引中的詞條列表來找到所有的匹配詞條,然後逐個詞條地收集對應的文件ID。它們和prefix查詢的唯一區別在於它們能夠支援更加複雜的模式。

語法

GET /索引庫/對映型別/_search
{
  "query": {
    "regexp": {
      "欄位名稱": "正則表示式"
    }
  }
}

注意:
1、prefix(字首)wildcard(萬用字元)以及regexp(正則)查詢基於詞條進行操作。如果你在一個analyzed欄位上使用了它們,它們會檢查欄位中的每個詞條,而不是整個欄位。
2、對一個含有很多不同詞條的欄位執行這類查詢是非常消耗資源的。應該避免使用一個以萬用字元開頭的模式(比如,*foo)

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", "愛麗絲");
//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": "美的"
        }
      }
    }
  }
}

屬性含義

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

運用場景

例如,在網際網路上搜索"蘋果"也許會返回,水果或者各種食譜的結果。但是使用者可能只想搜尋到蘋果手機等電子產品,當然我們可以通過排除“水果 喬木 維生素”和這類單詞,結合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);

六、排序

ElasticSearch預設會有一套相關性分數計算,分數越高,說明文件相關性越大,也就越會排在前面。除了相關性排序之外,開發者也可以通過自己的需要,通過某些規則設定查詢文件的排序

語法

GET /索引庫/對映型別/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "排序欄位1": {
        "order": "asc"
      }
    },
    {
      "排序欄位2":{
        "order": "desc"
      }
    }
  ]
}
//建立查詢構建器
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
  }
}

引數含義

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 過濾結果通過距離排序

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

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相加得到最終評分

random_score加強函式使用案例

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

解析:不同的使用者,可以設定不同的seed值(比如使用者的id號),實現隨機排序的效果,但是對同一個使用者排序結果又是恆定的

field_value_factor加強函式使用案例

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(最常用) : 高斯函式則是鐘形的,他的衰減速率是先緩慢,然後變快,最後又放緩

image-20200519021855136

衰減函式的支援引數

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

十、ElasticSearch叢集搭建

1、建立基本目錄/usr/local/es-cluster

image-20200519023041601

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容器

ces:
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

```shell
#授權
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容器