ES基礎入門

ElasticSearch是一個高可用的、開源的、全文檢索引擎。它能夠存儲,搜索和實時快速的分析大量數據。html

應用場景:java

電商網站中的商品搜索;
結合Logstash收集日誌和業務數據,注入到ElasticSearch中,再進行搜索;

安裝報錯:node

  1. $./bin/elasticsearch
  2. Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x0000000085330000, 2060255232, 0) failed; error=’Cannot allocate memory’ (errno=12)
  3. #
  4. # There is insufficient memory for the Java Runtime Environment to continue.
  5. # Native memory allocation (mmap) failed to map 2060255232 bytes for committing reserved memory.
  6. # An error report file with more information is saved as:
  7. # /data/elasticsearch-5.2.2/hs_err_pid26945.log

解決方案:調小啓動內存git

  1. # vi /usr/local/elasticsearch/config/jvm.options
  2. #-Xms2g
  3. #-Xmx2g
  4. -Xms256m
  5. -Xmx64m

上面設置的分配的內存的最大值爲256MB和最小值64mb,您能夠根據本身的機器狀況設置內存大小。github

在應用程序中對象不多隻是一個簡單的鍵和值的列表。一般,它們擁有更復雜的數據結構,可能包括日期、地理信息、其餘對象或者數組等。web

也許有一天你想把這些對象存儲在數據庫中。使用關係型數據庫的行和列存儲,這至關因而把一個表現力豐富的對象擠壓到一個很是大的電子表格中:你必須將這個對象扁平化來適應表結構–一般一個字段>對應一列–並且又不得不在每次查詢時從新構造對象。正則表達式

Elasticsearch 是 面向文檔 的,意味着它存儲整個對象或 文檔。Elasticsearch 不只存儲文檔,並且 索引 每一個文檔的內容使之能夠被檢索。在 Elasticsearch 中,你 對文檔進行索引、檢索、排序和過濾–而不是對行列數據。這是一種徹底不一樣的思考數據的方式,也是 Elasticsearch 能支持複雜全文檢索的緣由。算法

Elasticsearch: 權威指南

https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.htmlsql

1、什麼是 ElasticSearch

ElasticSearch是一個基於 Lucene 的搜索服務器。它提供了一個分佈式多用戶能力的全文搜索引擎,基於 RESTful web 接口。Elasticsearch 是用 Java 開發的,並做爲 Apache 許可條款下的開放源碼發佈,是當前流行的企業級搜索引擎。設計用於雲計算中,可以達到實時搜索,穩定,可靠,快速,安裝使用方便。數據庫

1.1 基礎概念

索引:含有相同屬性的文檔集合

類型:索引能夠定義一個或多個類型,文檔必須屬於一個類型

文檔:能夠被索引的基礎數據單位

分片:每一個索引都有多個分片,每一個分片都是 Lucene 索引

備份:拷貝一份分片就完成分片的備份

形象比喻:

百貨大樓裏有各式各樣的商品,例如書籍、筆、水果等。書籍能夠根據內容劃分紅不一樣種類,如科技類、教育類、懸疑推理等。懸疑推理類的小說中比較有名氣的有《福爾摩斯探案集》、《白夜行》等。

百貨大樓 –> ElasticSearch 數據庫

書籍 –> 索引

懸疑推理 –> 類型

白夜行 –> 文檔

1.2 應用場景

  • 海量數據分析引擎
  • 站內搜索引擎
  • 數據倉庫

你也許已經注意到 索引 這個詞在 Elasticsearch 語境中包含多重意思, 因此有必要作一點兒說明:

索引(名詞):

如前所述,一個 索引 相似於傳統關係數據庫中的一個 數據庫 ,是一個存儲關係型文檔的地方。 索引 (index) 的複數詞爲 indices 或 indexes 。

索引(動詞):

索引一個文檔 就是存儲一個文檔到一個 索引 (名詞)中以便它能夠被檢索和查詢到。這很是相似於 SQL 語句中的 INSERT 關鍵詞,除了文檔已存在時新文檔會替換舊文檔狀況以外。

倒排索引:

關係型數據庫經過增長一個 索引 好比一個 B樹(B-tree)索引 到指定的列上,以便提高數據檢索速度。Elasticsearch 和 Lucene 使用了一個叫作 倒排索引 的結構來達到相同的目的。

  • 默認的,一個文檔中的每個屬性都是 被索引 的(有一個倒排索引)和可搜索的。一個沒有倒排索引的屬性是不能被搜索到的

2、安裝和配置

本次測試使用一臺 ip 爲 192.168.2.41 的虛擬機(Centos7),建議使用 7.x 版本,筆者以前使用 6.x 啓動服務時報出各類錯誤

2.1 依賴環境

JDK 和 NodeJS

2.2 下載

登錄 elasticSearch 官網下載文件。

2.3 安裝

tar -zxvf elasticsearch-5.6.1.tar.gz -C /usr

cd elasticsearch-5.6.1

2.4 啓動

啓動以前,請先查看「踩坑提醒」。

1)啓動服務:

bin/elasticsearch 或 bin/elasticsearch -d # -d 表示後臺啓動
  • 1

踩坑提醒 1:

由於 Elasticsearch 能夠執行腳本文件,爲了安全性,默認不容許經過 root 用戶啓動服務。咱們須要新建立用戶名和用戶組啓動服務

#增長 es 組
groupadd es

#增長 es 用戶並附加到 es 組
useradd es -g es -p es

#給目錄權限
chown -R es:es elasticsearch-5.6.1

#使用es用戶
su es

踩坑提醒 2:

默認狀況下,Elasticsearch 只容許本機訪問,若是須要遠程訪問,須要修改其配置文件

vim config/elasticsearch.yml

# 去掉 network.host 前邊的註釋,將它的值改爲0.0.0.0
network.host: 0.0.0.0

踩坑提醒 3:

啓動報錯:max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]

命令行鍵入:

ulimit -n 65536

踩坑提醒 4:

啓動報錯:max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

vim /etc/sysctl.conf

vm.max_map_count=262144

保存,並執行 sysctl -p。

在啓動過程當中,Centos 環境下可能還會報錯,具體解決方案請參照文章末尾提供的資料

2)經過瀏覽器訪問 http://192.168.2.41:9200 ,當出現以下內容說明啓動成功:

{
  "name" : "OwUwJe-",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "vanzxnpaRumdRKiYic3f5A",
  "version" : {
    "number" : "5.6.1",
    "build_hash" : "667b497",
    "build_date" : "2017-09-14T19:22:05.189Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.1"
  },
  "tagline" : "You Know, for Search"
}

2.5 安裝插件

訪問 http://192.168.2.41:9200 查看內容顯示效果不友好,所以,咱們須要安裝一個名爲 elasticsearch-head 的插件,讓內容顯示效果比較溫馨。安裝以前須要先安裝node。

登錄 GitHub 網站,搜索 mobz/elasticsearch-head ,將其下載到本地。

wget https://github.com/mobz/elasticsearch-head/archive/master.zip

unzip master.zip

cd elasticsearch-head-master

npm install

npm run start

 

經過上述命令的操做,咱們已經安裝好 elasticsearch-head 插件。經過瀏覽器訪問 http://192.168.2.41:9100,以下圖:

這裏寫圖片描述

圖中咱們發現 elasticsearch-head 插件和 Elasticsearch 服務並無創建鏈接,因此咱們還須要修改 Elasticsearch 的配置文件:

cd elasticsearch-5.6.1

vim config/elasticsearch.yml

# 在文件末尾添加 2 段配置

http.cors.enabled: true
http.cors.allow-origin: "*"

保存文件後,分別起來 2 個程序:

cd elasticsearch-5.6.1

# 後臺啓動 elasticSearch 服務
bin/elasticsearch -d

cd elasticsearch-head-master

npm run start

經過瀏覽器訪問 http://192.168.2.41:9100,以下圖: 

經過插件建立索引 

查看索引基本狀況 
這裏寫圖片描述

該插件能直接對 Elasticsearch 的數據進行增刪改查,所以存在安全性的問題。建議生產環境下不要使用該插件!

3、簡單使用

不管咱們寫什麼樣的程序,目的都是同樣的:以某種方式組織數據服務咱們的目的 。可是數據不只僅由隨機位和字節組成。咱們創建數據元素之間的關係以便於表示實體,或者現實世界中存在的 事物 。

一個 對象 是基於特定語言的內存的數據結構。 爲了經過網絡發送或者存儲它,咱們須要將它表示成某種標準的格式。 JSON 是一種以人可讀的文本表示對象的方法。 它已經變成 NoSQL 世界交換數據的事實標準。當一個對象被序列化成爲 JSON,它被稱爲一個 JSON 文檔 。

Elastcisearch 是分佈式的 文檔 存儲。它能存儲和檢索複雜的數據結構–序列化成爲JSON文檔–以 實時 的方式。 換句話說,一旦一個文檔被存儲在 Elasticsearch 中,它就是能夠被集羣中的任意節點檢索到。

在 Elasticsearch 中, 每一個字段的全部數據 都是 默認被索引的 。 即每一個字段都有爲了快速檢索設置的專用倒排索引。

文檔

一般狀況下,咱們使用的術語 對象 和 文檔 是能夠互相替換的。不過,有一個區別: 一個對象僅僅是相似於 hash 、 hashmap 、字典或者關聯數組的 JSON 對象,對象中也能夠嵌套其餘的對象。 對象可能包含了另一些對象。在 Elasticsearch 中,術語 文檔 有着特定的含義。它是指最頂層或者根對象, 這個根對象被序列化成 JSON 並存儲到 Elasticsearch 中,指定了惟一 ID。

一個文檔不只僅包含它的數據 ,也包含 元數據 —— 有關 文檔的信息。 三個必須的元數據元素以下:

  • _index

    文檔在哪存放 。一個 索引 應該是因共同的特性被分組到一塊兒的文檔集合。 索引名必須小寫,不能如下劃線開頭,不能包含逗號。

  • _type 
    文檔表示的對象類別

  • _id 
    文檔惟一標識

Elasticsearch 支持 RESTFUL 風格 API,其 API 基本格式以下:

http://<ip>:<port>/<索引>/<類型>/<文檔id>

3.1 建立/刪除索引

爲了方便測試,咱們使用 POSTMAN 工具進行接口的請求。

http://39.106.195.92:9200/megacorp/employee/1

若是咱們不但願建立索引的時候覆蓋以前存在的索引,怎麼搞?

請記住, _index 、 _type 和 _id 的組合能夠惟一標識一個文檔。因此,確保建立一個新文檔的最簡單辦法是,使用索引請求的 POST 形式讓 Elasticsearch 自動生成惟一 _id :

http://39.106.195.92:9200/megacorp/employee

然而,咱們已經有了本身的id的話, 然而,若是已經有本身的 _id ,那麼咱們必須告訴 Elasticsearch ,只有在相同的 _index 、 _type 和 _id 不存在時才接受咱們的索引請求。這裏有兩種方式,他們作的實際是相同的事情。使用哪一種,取決於哪一種使用起來更方便。

第一種方法使用 op_type 查詢 -字符串參數:

http://39.106.195.92:9200/megacorp/employee/1?op_type=create 

http://39.106.195.92:9200/megacorp/employee/1/_create

若是建立新文檔的請求成功執行,Elasticsearch 會返回元數據和一個 201 Created 的 HTTP 響應碼。

另外一方面,若是具備相同的 _index 、 _type 和 _id 的文檔已經存在,Elasticsearch 將會返回 409 Conflict 響應碼,以及以下的錯誤信息:

{
    "error": {
        "root_cause": [
            {
                "type": "version_conflict_engine_exception",
                "reason": "[employee][1]: version conflict, document already exists (current version [4])",
                "index_uuid": "lo-6VdTHQWKAYVuMu3djQg",
                "shard": "3",
                "index": "megacorp"
            }
        ],
        "type": "version_conflict_engine_exception",
        "reason": "[employee][1]: version conflict, document already exists (current version [4])",
        "index_uuid": "lo-6VdTHQWKAYVuMu3djQg",
        "shard": "3",
        "index": "megacorp"
    },
    "status": 409
}

 

刪除一個索引,須要使用 DELETE 請求。執行:

http://39.106.195.92:9200/megacorp/employee/3

 

返回結果:

{
    "found": true,
    "_index": "megacorp",
    "_type": "employee",
    "_id": "3",
    "_version": 2,     //+1
    "result": "deleted",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    }
}

 

3.2 插入數據

插入指定 ID 的數據,須要使用 PUT 請求。以下圖: 
這裏寫圖片描述

megacorp:索引名稱;employee索引類型;1表明數據ID,請求體是JSON數據,包含一些信息 
也能夠不指定 ID ,Elasticsearch 能夠幫咱們自動生成 ID 。須要使用 POST 請求。以下圖: 
這裏寫圖片描述

3.3 查找數據(檢索)

一、GET方式簡單搜索

查詢指定ID的數據,使用 GET 請求。

查詢id是1的僱員信息,執行:

[GET] http://39.106.195.92:9200/megacorp/employee/1

返回結果:

{
    "_index": "megacorp",
    "_type": "employee",
    "_id": "1",
    "_version": 2,
    "found": true,    //表示文檔被找到
    "_source": {
        "first_name": "Douglas",
        "last_name": "Fir",
        "age": 35,
        "about": "I like to build cabinets",
        "interests": [
            "forestry"
        ]
    }
}

 

查詢文檔的一部分(只查詢名字)

[GET] http://39.106.195.92:9200/megacorp/employee/1?_source=first_name,last_name

查詢全部信息,不指定id,使用_search,執行:

[GET] http://39.106.195.92:9200/megacorp/employee/_search

按條件查詢,仍然在請求路徑中使用 _search 端點,並將查詢自己賦值給參數 q= ,執行:

[GET] http://39.106.195.92:9200/megacorp/employee/_search?q=last_name:Smith

查詢文檔是否存在,把GET方式換位HEAD方式就能夠了,HEAD 請求沒有返回體,只返回一個 HTTP 請求報頭: 存在返回200,否在返回404。

二、POST方式搜索

GET條件查詢的方式雖然方便,但有很大的侷限性,ES提供了POST的方式查詢語言–查詢表達式,它支持構建更加複雜和健壯的查詢。 POST方式的搜索url都相同,只是body不一樣,

執行:

[POST] http://39.106.195.92:9200/megacorp/employee/_search

請求參數:使用 JSON 構造,並使用了一個 match 查詢(屬於查詢類型之一,這裏match不能匹配多個字段。匹配多個字段要使用multi_match )

查找 last_name= 「Smith」:

{
    "query" : {
        "match" : {
            "last_name" : "Smith"   //只能寫一個
        }
    }
}

 

返回結果:

{
    "took": 5,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 1,
        "max_score": 0.2876821,
        "hits": [
            {
                "_index": "megacorp",
                "_type": "employee",
                "_id": "2",
                "_score": 0.2876821,
                "_source": {
                    "first_name": "Jane",
                    "last_name": "Smith",
                    "age": 32,
                    "about": "I like to collect rock albums",
                    "interests": [
                        "music"
                    ]
                }
            }
        ]
    }
}

 

三、複雜的搜索

使用過濾器filter搜索姓氏爲 Smith 的僱員 且年齡大於30,使用的是range過濾器,其中 gt 表示_大於 :

{
    "query" : {
        "bool": {
            "must": {
                "match" : {
                    "last_name" : "smith" 
                }
            },
            "filter": {
                "range" : {
                    "age" : { "gt" : 30 } 
                }
            }
        }
    }
}

 

四、全文搜索

使用match全文搜索,Elasticsearch 默認按照相關性得分排序,即每一個文檔跟查詢的匹配程度

{
    "query" : {
        "match" : {
            "about" : "rock climbing"
        }
    }
}

 

五、短語搜索

使用match_phrase短語搜索(準確搜索)

{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    }
}

 

六、高亮搜索

{
    "query" : {
        "match_phrase" : {
            "about" : "rock climbing"
        }
    },
    "highlight": {
        "fields" : {
            "about" : {}
        }
    }
}

 

七、分析

Elasticsearch 有一個功能叫聚合(aggregations),容許咱們基於數據生成一些精細的分析結果。聚合與 SQL 中的 GROUP BY 相似但更強大。

挖掘出僱員中最受歡迎的興趣愛好

{
  "aggs": {
    "all_interests": {
      "terms": { "field": "interests" }
    }
  }
}

 

能夠看到,兩位員工對音樂感興趣,一位對林地感興趣,一位對運動感興趣。這些聚合並不是預先統計,而是從匹配當前查詢的文檔中即時生成的

{
   ...
   "hits": { ... },
   "aggregations": {
      "all_interests": {
         "buckets": [
            {
               "key":       "music",
               "doc_count": 2
            },
            {
               "key":       "forestry",
               "doc_count": 1
            },
            {
               "key":       "sports",
               "doc_count": 1
            }
         ]
      }
   }
}

 

八、mget批量查詢

[POST] http://39.106.195.92:9200/_mget

{
   "docs" : [
      {
         "_index" : "megacorp",
         "_type" :  "employee",
         "_id" :    2
      },
      {
         "_index" : "website",    //沒有索引的話,會報錯搜索不到該索引的內容
         "_type" :  "employee",
         "_id" :    1,
         "_source": "age"
      }
   ]
}

 

3.3 修改數據

更新整個文檔:在 Elasticsearch 中文檔是 不可改變 的,不能修改它們。 相反,若是想要更新現有的文檔,須要 重建索引 或者進行替換, 咱們能夠使用相同的 index API 進行實現 。

[PUT] http://39.106.195.92:9200/megacorp/employee/1

{
    "_index": "megacorp",
    "_type": "employee",
    "_id": "1",
    "_version": 3,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "created": false    //這裏變成了false,表示是更新
}

部分更新,也能夠使用 POST 請求,且 URL 須要添加 _update,它只是與現有的文檔進行合併

執行:

[POST] http://39.106.195.92:9200/megacorp/employee/1/_update

請求參數(修改年齡爲88,也能夠增長參數):

{
    "doc": {
        "age": "88"
    }
}

 

 

返回結果:

{
    "_index": "megacorp",
    "_type": "employee",
    "_id": "1",
    "_version": 4,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    }
}

 

使用腳本部分更新文檔

腳本能夠在 update API中用來改變 _source 的字段內容, 它在更新腳本中稱爲 ctx._source 。 例如,咱們能夠使用腳原本增長博客文章中 views 的數量:

[POST] http://39.106.195.92:9200/megacorp/employee/1/_update

{
   "script" : "ctx._source.age+=1"
}

 

在數據庫領域中,有兩種方法一般被用來確保併發更新時變動不會丟失:

  • 悲觀併發控制 
    這種方法被關係型數據庫普遍使用,它假定有變動衝突可能發生,所以阻塞訪問資源以防止衝突。 一個典型的例子是讀取一行數據以前先將其鎖住,確保只有放置鎖的線程可以對這行數據進行修改。
  • 樂觀併發控制 
    Elasticsearch 中使用的這種方法假定衝突是不可能發生的,而且不會阻塞正在嘗試的操做。 然而,若是源數據在讀寫當中被修改,更新將會失敗。應用程序接下來將決定該如何解決衝突。 例如,能夠重試更新、使用新的數據、或者將相關狀況報告給用戶。

    lasticsearch 是分佈式的。當文檔建立、更新或刪除時, 新版本的文檔必須複製到集羣中的其餘節點。Elasticsearch 也是異步和併發的,這意味着這些複製請求被並行發送,而且到達目的地時也許 順序是亂的 。 Elasticsearch 須要一種方法確保文檔的舊版本不會覆蓋新的版本。

    當咱們以前討論 index , GET 和 delete 請求時,咱們指出每一個文檔都有一個 _version (版本)號,當文檔被修改時版本號遞增。 Elasticsearch 使用這個 _version 號來確保變動以正確順序獲得執行。若是舊版本的文檔在新版本以後到達,它能夠被簡單的忽略。

    咱們能夠利用 _version 號來確保 應用中相互衝突的變動不會致使數據丟失。咱們經過指定想要修改文檔的 version 號來達到這個目的。 若是該版本不是當前版本號,咱們的請求將會失敗。

代價較小的批量操做

與 mget 能夠使咱們一次取回多個文檔一樣的方式, bulk API 容許在單個步驟中進行屢次 create 、 index 、 update 或 delete 請求。 若是你須要索引一個數據流好比日誌事件,它能夠排隊和索引數百或數千批次。

bulk 與其餘的請求體格式稍有不一樣,以下所示:

{ action: { metadata }}\n
{ request body        }\n
{ action: { metadata }}\n
{ request body        }\n
...

 

這種格式相似一個有效的單行 JSON 文檔 流 ,它經過換行符(\n)鏈接到一塊兒。注意兩個要點:

  • 每行必定要以換行符(\n)結尾, 包括最後一行 。這些換行符被用做一個標記,能夠有效分隔行。
  • 這些行不能包含未轉義的換行符,由於他們將會對解析形成干擾。這意味着這個 JSON 不 能使用 pretty 參數打印。

3.4 刪除數據

修改數據,須要使用 DELETE 請求。

執行:

[DELETE] http://192.168.2.41:9200/fruit/apple/1

返回結果:

{
    "found": true,
    "_index": "fruit",
    "_type": "apple",
    "_id": "1",
    "_version": 8,
    "result": "deleted",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    }
}

 

3.5 集羣內原理

ElasticSearch 的主旨是隨時可用和按需擴容

一個運行中的 Elasticsearch 實例稱爲一個 節點,而集羣是由一個或者多個擁有相同 cluster.name 配置的節點組成, 它們共同承擔數據和負載的壓力。當有節點加入集羣中或者從集羣中移除節點時,集羣將會從新平均分佈全部的數據。

當一個節點被選舉成爲 主 節點時, 它將負責管理集羣範圍內的全部變動,例如增長、刪除索引,或者增長、刪除節點等。 而主節點並不須要涉及到文檔級別的變動和搜索等操做,因此當集羣只擁有一個主節點的狀況下,即便流量的增長它也不會成爲瓶頸。 任何節點均可以成爲主節點。咱們的示例集羣就只有一個節點,因此它同時也成爲了主節點。

做爲用戶,咱們能夠將請求發送到 集羣中的任何節點 ,包括主節點。 每一個節點都知道任意文檔所處的位置,而且可以將咱們的請求直接轉發到存儲咱們所需文檔的節點。 不管咱們將請求發送到哪一個節點,它都能負責從各個包含咱們所需文檔的節點收集回數據,並將最終結果返回給客戶端。 Elasticsearch 對這一切的管理都是透明的。

3.5.1 集羣健康

Elasticsearch 的集羣監控信息中包含了許多的統計數據,其中最爲重要的一項就是 集羣健康 , 它在 status 字段中展現爲 green 、 yellow 或者 red 。

3.5.2 添加索引

咱們往 Elasticsearch 添加數據時須要用到 索引 —— 保存相關數據的地方。 索引其實是指向一個或者多個物理 分片 的 邏輯命名空間 。

一個 分片 是一個底層的 工做單元 ,它僅保存了 所有數據中的一部分。 一個分片就是一個 Lucene 的實例,以及它自己就是一個完整的搜索引擎。 咱們的文檔被存儲和索引到分片內,可是應用程序是直接與索引而不是與分片進行交互。

Elasticsearch 是利用分片將數據分發到集羣內各處的。分片是數據的容器,文檔保存在分片內,分片又被分配到集羣內的各個節點裏。 當你的集羣規模擴大或者縮小時, Elasticsearch 會自動的在各節點中遷移分片,使得數據仍然均勻分佈在集羣裏。

一個分片能夠是 主 分片或者 副本 分片。 索引內任意一個文檔都歸屬於一個主分片,因此主分片的數目決定着索引可以保存的最大數據量。

4、 分佈式文檔存儲

路由一個文檔到一個分片中

當索引一個文檔的時候,文檔會被存儲到一個主分片中。 Elasticsearch 如何知道一個文檔應該存放到哪一個分片中呢?當咱們建立文檔時,它如何決定這個文檔應當被存儲在分片 1 仍是分片 2 中呢?

首先這確定不會是隨機的,不然未來要獲取文檔的時候咱們就不知道從何處尋找了。實際上,這個過程是根據下面這個公式決定的:

shard = hash(routing) % number_of_primary_shards

routing 是一個可變值,默認是文檔的 _id ,也能夠設置成一個自定義的值。 routing 經過 hash 函數生成一個數字,而後這個數字再除以 number_of_primary_shards (主分片的數量)後獲得 餘數 。這個分佈在 0 到 number_of_primary_shards-1 之間的餘數,就是咱們所尋求的文檔所在分片的位置。

這就解釋了爲何咱們要在建立索引的時候就肯定好主分片的數量 而且永遠不會改變這個數量:由於若是數量變化了,那麼全部以前路由的值都會無效,文檔也再也找不到了。

全部的文檔 API( get 、 index 、 delete 、 bulk 、 update 以及 mget )都接受一個叫作 routing 的路由參數 ,經過這個參數咱們能夠自定義文檔到分片的映射。一個自定義的路由參數能夠用來確保全部相關的文檔——例如全部屬於同一個用戶的文檔——都被存儲到同一個分片中。

5、 搜索

搜索(search) 能夠作到:

  • 在相似於 gender 或者 age 這樣的字段 上使用結構化查詢,join_date 這樣的字段上使用排序,就像SQL的結構化查詢同樣。
  • 全文檢索,找出全部匹配關鍵字的文檔並按照相關性(relevance) 排序後返回結果。
  • 以上兩者兼而有之。

不少搜索都是開箱即用的,爲了充分挖掘 Elasticsearch 的潛力,你須要理解如下三個概念:

  • 映射(Mapping)

    描述數據在每一個字段內如何存儲

  • 分析(Analysis)

    全文是如何處理使之能夠被搜索的

  • 領域特定查詢語言(Query DSL)

    Elasticsearch 中強大靈活的查詢語言

空搜索

GET /_search      //簡單地返回集羣中全部索引下的全部文檔

然而,常常的狀況下,你 想在一個或多個特殊的索引而且在一個或者多個特殊的類型中進行搜索。咱們能夠經過在URL中指定特殊的索引和類型達到這種效果,以下所示:

  • /_search 
    在全部的索引中搜索全部的類型

  • /gb/_search 
    在 gb 索引中搜索全部的類型

  • /gb,us/_search 
    在 gb 和 us 索引中搜索全部的文檔

  • /g*,u*/_search 
    在任何以 g 或者 u 開頭的索引中搜索全部的類型

  • /gb/user/_search 
    在 gb 索引中搜索 user 類型

  • /gb,us/user,tweet/_search 
    在 gb 和 us 索引中搜索 user 和 tweet 類型

  • /_all/user,tweet/_search 
    在全部的索引中搜索 user 和 tweet 類型

當在單一的索引下進行搜索的時候,Elasticsearch 轉發請求到索引的每一個分片中,能夠是主分片也能夠是副本分片,而後從每一個分片中收集結果。多索引搜索剛好也是用相同的方式工做的–只是會涉及到更多的分片。

分頁

在以前的 空搜索 中說明了集羣中有 14 個文檔匹配了(empty)query 。 可是在 hits 數組中只有 10 個文檔。如何才能看到其餘的文檔?

和 SQL 使用 LIMIT 關鍵字返回單個 page 結果的方法相同,Elasticsearch 接受 from 和 size 參數:

  • size 
    顯示應該返回的結果數量,默認是 10
  • from 
    顯示應該跳過的初始結果數量,默認是 0

若是每頁展現 5 條結果,能夠用下面方式請求獲得 1 到 3 頁的結果:

GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10

考慮到分頁過深以及一次請求太多結果的狀況,結果集在返回以前先進行排序。 但請記住一個請求常常跨越多個分片,每一個分片都產生本身的排序結果,這些結果須要進行集中排序以保證總體順序是正確的。

輕量 搜索

有兩種形式的 搜索 API:一種是 「輕量的」 查詢字符串 版本,要求在查詢字符串中傳遞全部的 參數,另外一種是更完整的 請求體 版本,要求使用 JSON 格式和更豐富的查詢表達式做爲搜索語言。

查詢字符串搜索很是適用於經過命令行作即席查詢。例如,查詢在 tweet 類型中 tweet 字段包含 elasticsearch 單詞的全部文檔:

GET /_all/tweet/_search?q=tweet:elasticsearch

 

下一個查詢在 name 字段中包含 john 而且在 tweet 字段中包含 mary 的文檔。實際的查詢就是這樣

+name:john +tweet:mary

 

可是查詢字符串參數所須要的 百分比編碼 (譯者注:URL編碼)實際上更加難懂:

GET /_search?q=%2Bname%3Ajohn+%2Btweet%3Amary

 

  • 前綴表示必須與查詢條件匹配。相似地, - 前綴表示必定不與查詢條件匹配。沒有 + 或者 - 的全部其餘條件都是可選的——匹配的越多,文檔就越相關。

    _all字段

這個簡單搜索返回包含 mary 的全部文檔:

GET /_search?q=mary

 

以前的例子中,咱們在 tweet 和 name 字段中搜索內容。然而,這個查詢的結果在三個地方提到了 mary :

  • 有一個用戶叫作 Mary
  • 6條微博發自 Mary
  • 一條微博直接 @mary

Elasticsearch 是如何在三個不一樣的字段中查找到結果的呢?

當索引一個文檔的時候,Elasticsearch 取出全部字段的值拼接成一個大的字符串,做爲 _all 字段進行索引。例如,當索引這個文檔時:

{
    "tweet":    "However did I manage before Elasticsearch?",
    "date":     "2014-09-14",
    "name":     "Mary Jones",
    "user_id":  1
}

 

這就好似增長了一個名叫 _all 的額外字段:

"However did I manage before Elasticsearch? 2014-09-14 Mary Jones 1"

 

除非設置特定字段,不然查詢字符串就使用 _all 字段進行搜索。

在剛開始開發一個應用時,_all 字段是一個很實用的特性。以後,你會發現若是搜索時用指定字段來代替 _all 字段,將會更好控制搜索結果。當 _all 字段再也不有用的時候,能夠將它置爲失效,正如在 元數據: _all 字段 中所解釋的。

更復雜的查詢

下面的查詢針對tweents類型,並使用如下的條件:

  • name 字段中包含 mary 或者 john
  • date 值大於 2014-09-10
  • _all 字段包含 aggregations 或者 geo

    +name:(mary john) +date:>2014-09-10 +(aggregations geo)

查詢字符串在作了適當的編碼後,可讀性不好:

?q=%2Bname%3A(mary+john)+%2Bdate%3A%3E2014-09-10+%2B(aggregations+geo)

 

從以前的例子中能夠看出,這種 輕量 的查詢字符串搜索效果仍是挺讓人驚喜的。 它的查詢語法在相關參考文檔中有詳細解釋,以便簡潔的表達很複雜的查詢。對於經過命令作一次性查詢,或者是在開發階段,都很是方便。

但同時也能夠看到,這種精簡讓調試更加晦澀和困難。並且很脆弱,一些查詢字符串中很小的語法錯誤,像 - , : , / 或者 」 不匹配等,將會返回錯誤而不是搜索結果。

最後,查詢字符串搜索容許任何用戶在索引的任意字段上執行可能較慢且重量級的查詢,這可能會暴露隱私信息,甚至將集羣拖垮。

由於這些緣由,不推薦直接向用戶暴露查詢字符串搜索功能,除非對於集羣和數據來講很是信任他們。

相反,咱們常常在生產環境中更多地使用功能全面的 request body 查詢API,除了能完成以上全部功能,還有一些附加功能。但在到達那個階段以前,首先須要瞭解數據在 Elasticsearch 中是如何被索引的。

6、映射和分析

精確值 VS 全文

Elasticsearch 中的數據能夠歸納的分爲兩類:精確值和全文。

精確值 如它們聽起來那樣精確。例如日期或者用戶 ID,但字符串也能夠表示精確值,例如用戶名或郵箱地址。對於精確值來說,Foo 和 foo 是不一樣的,2014 和 2014-09-15 也是不一樣的。

另外一方面,全文 是指文本數據(一般以人類容易識別的語言書寫),例如一個推文的內容或一封郵件的內容。

全文一般是指非結構化的數據,但這裏有一個誤解:天然語言是高度結構化的。問題在於天然語言的規則是複雜的,致使計算機難以正確解析。例如,考慮這條語句:

May is fun but June bores me.

它指的是月份仍是人?

精確值很容易查詢。結果是二進制的:要麼匹配查詢,要麼不匹配。這種查詢很容易用 SQL 表示:

WHERE name    = "John Smith"
  AND user_id = 2
  AND date    > "2014-09-15"

查詢全文數據要微妙的多。咱們問的不僅是「這個文檔匹配查詢嗎」,而是「該文檔匹配查詢的程度有多大?」換句話說,該文檔與給定查詢的相關性如何?

咱們不多對全文類型的域作精確匹配。相反,咱們但願在文本類型的域中搜索。不只如此,咱們還但願搜索可以理解咱們的 意圖 :

  • 搜索 UK ,會返回包含 United Kindom 的文檔。
  • 搜索 jump ,會匹配 jumped , jumps , jumping ,甚至是 leap 。
  • 搜索 johnny walker 會匹配 Johnnie Walker , johnnie depp 應該匹配 Johnny Depp 。
  • fox news hunting 應該返回福克斯新聞( Foxs News )中關於狩獵的故事,同時, fox hunting news 應該返回關於獵狐的故事。

爲了促進這類在全文域中的查詢,Elasticsearch 首先 分析 文檔,以後根據結果建立 倒排索引 。

倒排索引

Elasticsearch 使用一種稱爲 倒排索引 的結構,它適用於快速的全文搜索。一個倒排索引由文檔中全部不重複詞的列表構成,對於其中每一個詞,有一個包含它的文檔列表。

例如,假設咱們有兩個文檔,每一個文檔的 content 域包含以下內容:

  1. The quick brown fox jumped over the lazy dog
  2. Quick brown foxes leap over lazy dogs in summer

爲了建立倒排索引,咱們首先將每一個文檔的 content 域拆分紅單獨的詞(咱們稱它爲 詞條 或 tokens ),建立一個包含全部不重複詞條的排序列表,而後列出每一個詞條出如今哪一個文檔。結果以下所示:

Term      Doc_1  Doc_2
-------------------------
Quick   |       |  X
The     |   X   |
brown   |   X   |  X
dog     |   X   |
dogs    |       |  X
fox     |   X   |
foxes   |       |  X
in      |       |  X
jumped  |   X   |
lazy    |   X   |  X
leap    |       |  X
over    |   X   |  X
quick   |   X   |
summer  |       |  X
the     |   X   |
------------------------

 

如今,若是咱們想搜索 quick brown ,咱們只須要查找包含每一個詞條的文檔:

Term      Doc_1  Doc_2
-------------------------
brown   |   X   |  X
quick   |   X   |
------------------------
Total   |   2   |  1

 

兩個文檔都匹配,可是第一個文檔比第二個匹配度更高。若是咱們使用僅計算匹配詞條數量的簡單 類似性算法 ,那麼,咱們能夠說,對於咱們查詢的相關性來說,第一個文檔比第二個文檔更佳。

可是,咱們目前的倒排索引有一些問題:

  • Quick 和 quick 以獨立的詞條出現,然而用戶可能認爲它們是相同的詞。
  • fox 和 foxes 很是類似, 就像 dog 和 dogs ;他們有相同的詞根。
  • jumped 和 leap, 儘管沒有相同的詞根,但他們的意思很相近。他們是同義詞。

使用前面的索引搜索 +Quick +fox 不會獲得任何匹配文檔。(記住,+ 前綴代表這個詞必須存在。)只有同時出現 Quick 和 fox 的文檔才知足這個查詢條件,可是第一個文檔包含 quick fox ,第二個文檔包含 Quick foxes 。

咱們的用戶能夠合理的指望兩個文檔與查詢匹配。咱們能夠作的更好。

若是咱們將詞條規範爲標準模式,那麼咱們能夠找到與用戶搜索的詞條不徹底一致,但具備足夠相關性的文檔。例如:

  • Quick 能夠小寫化爲 quick 。
  • foxes 能夠 詞幹提取 –變爲詞根的格式– 爲 fox 。相似的, dogs 能夠爲提取爲 dog 。
  • jumped 和 leap 是同義詞,能夠索引爲相同的單詞 jump 。

如今索引看上去像這樣:

Term      Doc_1  Doc_2
-------------------------
brown   |   X   |  X
dog     |   X   |  X
fox     |   X   |  X
in      |       |  X
jump    |   X   |  X
lazy    |   X   |  X
over    |   X   |  X
quick   |   X   |  X
summer  |       |  X
the     |   X   |  X
------------------------

 

這還遠遠不夠。咱們搜索 +Quick +fox 仍然 會失敗,由於在咱們的索引中,已經沒有 Quick 了。可是,若是咱們對搜索的字符串使用與 content 域相同的標準化規則,會變成查詢 +quick +fox ,這樣兩個文檔都會匹配!

這很是重要。你只能搜索在索引中出現的詞條,因此索引文本和查詢字符串必須標準化爲相同的格式。

分詞和標準化的過程稱爲 分析 。

分析與分析器

分析 包含下面的過程:

  • 首先,將一塊文本分紅適合於倒排索引的獨立的 詞條 ,
  • 以後,將這些詞條統一化爲標準格式以提升它們的「可搜索性」,或者 recall

分析器執行上面的工做。 分析器 其實是將三個功能封裝到了一個包裏:

  • 字符過濾器 
    首先,字符串按順序經過每一個 字符過濾器 。他們的任務是在分詞前整理字符串。一個字符過濾器能夠用來去掉HTML,或者將 & 轉化成 and

  • 分詞器 
    其次,字符串被 分詞器 分爲單個的詞條。一個簡單的分詞器遇到空格和標點的時候,可能會將文本拆分紅詞條。

  • Token 過濾器 
    最後,詞條按順序經過每一個 token 過濾器 。這個過程可能會改變詞條(例如,小寫化 Quick ),刪除詞條(例如, 像 a, and, the 等無用詞),或者增長詞條(例如,像 jump 和 leap 這種同義詞)。

Elasticsearch提供了開箱即用的字符過濾器、分詞器和token 過濾器。 這些能夠組合起來造成自定義的分析器以用於不一樣的目的。

內置分析器

可是, Elasticsearch還附帶了能夠直接使用的預包裝的分析器。 接下來咱們會列出最重要的分析器。爲了證實它們的差別,咱們看看每一個分析器會從下面的字符串獲得哪些詞條:

"Set the shape to semi-transparent by calling set_trans(5)"

- 標準分析器 
標準分析器是Elasticsearch默認使用的分析器。它是分析 
各類語言文本最經常使用的選擇。它根據 Unicode 聯盟 定義的 單詞邊界 劃分文本。刪除絕大部分標點。最後,將詞條小寫。它會產生 set, the, shape, to, semi, transparent, by, calling, set_trans, 5 
- 簡單分析器 
簡單分析器在任何不是字母的地方分隔文本,將詞條小寫。它會產生 set, the, shape, to, semi, transparent, by, calling, set, trans 
- 空格分析器 
空格分析器在空格的地方劃分文本。它會產生 Set, the, shape, to, semi-transparent, by, calling, set_trans(5) 
- 語言分析器 
特定語言分析器可用於 不少語言。它們能夠考慮指定語言的特色。例如, 英語 分析器附帶了一組英語無用詞(經常使用單詞,例如 and 或者 the ,它們對相關性沒有多少影響),它們會被刪除。 因爲理解英語語法的規則,這個分詞器能夠提取英語單詞的 詞幹 。 英語 分詞器會產生下面的詞條:set, shape, semi, transpar, call, set_tran, 5注意看 transparent、 calling 和 set_trans 已經變爲詞根格式。

何時使用分析器

當咱們 索引 一個文檔,它的全文域被分析成詞條以用來建立倒排索引。 可是,當咱們在全文域 搜索 的時候,咱們須要將查詢字符串經過 相同的分析過程 ,以保證咱們搜索的詞條格式與索引中的詞條格式一致。

全文查詢,理解每一個域是如何定義的,所以它們能夠作 正確的事:

  • 當你查詢一個 全文 域時, 會對查詢字符串應用相同的分析器,以產生正確的搜索詞條列表。
  • 當你查詢一個 精確值 域時,不會分析查詢字符串, 而是搜索你指定的精確值。

如今你能夠理解以前的查詢爲何返回那樣的結果:

  • date 域包含一個精確值:單獨的詞條 2014-09-15。
  • _all 域是一個全文域,因此分詞進程將日期轉化爲三個詞條: 2014, 09, 和 15。

當咱們在 _all 域查詢 2014,它匹配全部的12條推文,由於它們都含有 2014 :

GET /_search?q=2014              # 12 results

當咱們在 _all 域查詢 2014-09-15,它首先分析查詢字符串,產生匹配 2014, 09, 或 15 中 任意 詞條的查詢。這也會匹配全部12條推文,由於它們都含有 2014 :

GET /_search?q=2014-09-15        # 12 results !

當咱們在 date 域查詢 2014-09-15,它尋找 精確 日期,只找到一個推文:

GET /_search?q=date:2014-09-15   # 1  result

當咱們在 date 域查詢 2014,它找不到任何文檔,由於沒有文檔含有這個精確日誌:

GET /_search?q=date:2014         # 0  results !

測試分析器

有些時候很難理解分詞的過程和實際被存儲到索引中的詞條,特別是你剛接觸 Elasticsearch。爲了理解發生了什麼,你能夠使用 analyze API 來看文本是如何被分析的。在消息體裏,指定分析器和要分析的文本:

GET /_analyze
{
  "analyzer": "standard",
  "text": "Text to analyze"
}

結果中每一個元素表明一個單獨的詞條:

{
   "tokens": [
      {
         "token":        "text",
         "start_offset": 0,
         "end_offset":   4,
         "type":         "<ALPHANUM>",
         "position":     1
      },
      {
         "token":        "to",
         "start_offset": 5,
         "end_offset":   7,
         "type":         "<ALPHANUM>",
         "position":     2
      },
      {
         "token":        "analyze",
         "start_offset": 8,
         "end_offset":   15,
         "type":         "<ALPHANUM>",
         "position":     3
      }
   ]
}

token 是實際存儲到索引中的詞條。 position 指明詞條在原始文本中出現的位置。 start_offset 和 end_offset 指明字符在原始字符串中的位置。

每一個分析器的 type 值都不同,能夠忽略它們。它們在Elasticsearch中的惟一做用在於keep_types token 過濾器。

analyze API 是一個有用的工具,它有助於咱們理解Elasticsearch索引內部發生了什麼,隨着深刻,咱們會進一步討論它。

指定分析器

當Elasticsearch在你的文檔中檢測到一個新的字符串域 ,它會自動設置其爲一個全文 字符串 域,使用 標準 分析器對它進行分析。

你不但願老是這樣。可能你想使用一個不一樣的分析器,適用於你的數據使用的語言。有時候你想要一個字符串域就是一個字符串域–不使用分析,直接索引你傳入的精確值,例如用戶ID或者一個內部的狀態域或標籤。

要作到這一點,咱們必須手動指定這些域的映射。

映射

爲了可以將時間域視爲時間,數字域視爲數字,字符串域視爲全文或精確值字符串, Elasticsearch 須要知道每一個域中數據的類型。這個信息包含在映射中。

索引中每一個文檔都有 類型 。每種類型都有它本身的 映射 ,或者 模式定義 。映射定義了類型中的域,每一個域的數據類型,以及Elasticsearch如何處理這些域。映射也用於配置與類型有關的元數據。

核心簡單域類型

Elasticsearch 支持 以下簡單域類型:

  • 字符串: string
  • 整數 : byte, short, integer, long
  • 浮點數: float, double
  • 布爾型: boolean
  • 日期: date

當你索引一個包含新域的文檔–以前不曾出現– Elasticsearch 會使用 動態映射 ,經過JSON中基本數據類型,嘗試猜想域類型,使用以下規則:

JSON type 域 type 
布爾型: true 或者 false boolean 
整數: 123 long 
浮點數: 123.45 double 
字符串,有效日期: 2014-09-15 date 
字符串: foo bar string

這意味着若是你經過引號( 「123」 )索引一個數字,它會被映射爲 string 類型,而不是 long 。可是,若是這個域已經映射爲 long ,那麼 Elasticsearch 會嘗試將這個字符串轉化爲 long ,若是沒法轉化,則拋出一個異常。

查看映射

經過 /_mapping ,咱們能夠查看 Elasticsearch 在一個或多個索引中的一個或多個類型的映射 。好比取得索引 gb 中類型 tweet 的映射:

GET /gb/_mapping/tweet

Elasticsearch 根據咱們索引的文檔,爲域(稱爲 屬性 )動態生成的映射。

{
   "gb": {
      "mappings": {
         "tweet": {
            "properties": {
               "date": {
                  "type": "date",
                  "format": "strict_date_optional_time||epoch_millis"
               },
               "name": {
                  "type": "string"
               },
               "tweet": {
                  "type": "string"
               },
               "user_id": {
                  "type": "long"
               }
            }
         }
      }
   }
}

錯誤的映射,例如 將 age 域映射爲 string 類型,而不是 integer ,會致使查詢出現使人困惑的結果。

自定義域映射

儘管在不少狀況下基本域數據類型 已經夠用,但你常常須要爲單獨域自定義映射 ,特別是字符串域。自定義映射容許你執行下面的操做:

  • 全文字符串域和精確值字符串域的區別
  • 使用特定語言分析器
  • 優化域以適應部分匹配
  • 指定自定義數據格式
  • 還有更多

域最重要的屬性是 type 。對於不是 string 的域,你通常只須要設置 type :

{
    "number_of_clicks": {
        "type": "integer"
    }
}

默認, string 類型域會被認爲包含全文。就是說,它們的值在索引前,會經過 一個分析器,針對於這個域的查詢在搜索前也會通過一個分析器。

string 域映射的兩個最重要 屬性是 index 和 analyzer 。

index

index 屬性控制怎樣索引字符串。它能夠是下面三個值:

  • analyzed 
    首先分析字符串,而後索引它。換句話說,以全文索引這個域。
  • not_analyzed 
    索引這個域,因此它可以被搜索,但索引的是精確值。不會對它進行分析。
  • no 
    不索引這個域。這個域不會被搜索到。

string 域 index 屬性默認是 analyzed 。若是咱們想映射這個字段爲一個精確值,咱們須要設置它爲 not_analyzed :

{
    "tag": {
        "type":     "string",
        "index":    "not_analyzed"
    }
}

其餘簡單類型(例如 long , double , date 等)也接受 index 參數,但有意義的值只有 no 和 not_analyzed , 由於它們永遠不會被分析。

analyzer

對於 analyzed 字符串域,用 analyzer 屬性指定在搜索和索引時使用的分析器。默認, Elasticsearch 使用 standard 分析器, 但你能夠指定一個內置的分析器替代它,例如 whitespace 、 simple 和 english:

{
    "tweet": {
        "type":     "string",
        "analyzer": "english"
    }
}

更新映射

當你首次 建立一個索引的時候,能夠指定類型的映射。你也能夠使用 /_mapping 爲新類型(或者爲存在的類型更新映射)增長映射。

儘管你能夠 增長_ 一個存在的映射,你不能 _修改 存在的域映射。若是一個域的映射已經存在,那麼該域的數據可能已經被索引。若是你意圖修改這個域的映射,索引的數據可能會出錯,不能被正常的搜索。

咱們能夠更新一個映射來添加一個新域,但不能將一個存在的域從 analyzed 改成 not_analyzed 。

爲了描述指定映射的兩種方式,咱們先刪除 gd 索引:

DELETE /gb

而後建立一個新索引,指定 tweet 域使用 english 分析器:

PUT /gb 
{
  "mappings": {
    "tweet" : {
      "properties" : {
        "tweet" : {
          "type" :    "string",
          "analyzer": "english"
        },
        "date" : {
          "type" :   "date"
        },
        "name" : {
          "type" :   "string"
        },
        "user_id" : {
          "type" :   "long"
        }
      }
    }
  }
}

經過消息體中指定的 mappings 建立了索引。

稍後,咱們決定在 tweet 映射增長一個新的名爲 tag 的 not_analyzed 的文本域,使用 _mapping :

PUT /gb/_mapping/tweet
{
  "properties" : {
    "tag" : {
      "type" :    "string",
      "index":    "not_analyzed"
    }
  }
}

注意,咱們不須要再次列出全部已存在的域,由於不管如何咱們都沒法改變它們。新域已經被合併到存在的映射中。

測試映射

你能夠使用 analyze API 測試字符串域的映射。比較下面兩個請求的輸出:

GET /gb/_analyze
{
  "field": "tweet",
  "text": "Black-cats" 
}

GET /gb/_analyze
{
  "field": "tag",
  "text": "Black-cats" 
}

消息體裏面傳輸咱們想要分析的文本。

tweet 域產生兩個詞條 black 和 cat , tag 域產生單獨的詞條 Black-cats 。換句話說,咱們的映射正常工做。

複雜核心域類型

除了咱們提到的簡單標量數據類型, JSON 還有 null 值,數組,和對象,這些 Elasticsearch 都是支持的。

多值域

頗有可能,咱們但願 tag 域 包含多個標籤。咱們能夠以數組的形式索引標籤:

{ "tag": [ "search", "nosql" ]}

對於數組,沒有特殊的映射需求。任何域均可以包含0、1或者多個值,就像全文域分析獲得多個詞條。

這暗示 數組中全部的值必須是相同數據類型的 。你不能將日期和字符串混在一塊兒。若是你經過索引數組來建立新的域,Elasticsearch 會用數組中第一個值的數據類型做爲這個域的 類型 。

當你從 Elasticsearch 獲得一個文檔,每一個數組的順序和你當初索引文檔時同樣。你獲得的 _source 域,包含與你索引的如出一轍的 JSON 文檔。

可是,數組是以多值域 索引的—能夠搜索,可是無序的。 在搜索的時候,你不能指定 「第一個」 或者 「最後一個」。 更確切的說,把數組想象成 裝在袋子裏的值 。

空域

固然,數組能夠爲空。 這至關於存在零值。 事實上,在 Lucene 中是不能存儲 null 值的,因此咱們認爲存在 null 值的域爲空域。

下面三種域被認爲是空的,它們將不會被索引:

"null_value":               null,
"empty_array":              [],
"array_with_null_value":    [ null ]

多層級對象

咱們討論的最後一個 JSON 原生數據類是 對象 – 在其餘語言中稱爲哈希,哈希 map,字典或者關聯數組。

內部對象 常常用於 嵌入一個實體或對象到其它對象中。例如,與其在 tweet 文檔中包含 user_name 和 user_id 域,咱們也能夠這樣寫:

{
    "tweet":            "Elasticsearch is very flexible",
    "user": {
        "id":           "@johnsmith",
        "gender":       "male",
        "age":          26,
        "name": {
            "full":     "John Smith",
            "first":    "John",
            "last":     "Smith"
        }
    }
}

內部對象的映射

Elasticsearch 會動態 監測新的對象域並映射它們爲 對象 ,在 properties 屬性下列出內部域:

{
  "gb": {
    "tweet": { 
      "properties": {
        "tweet":            { "type": "string" },
        "user": { 
          "type":             "object",
          "properties": {
            "id":           { "type": "string" },
            "gender":       { "type": "string" },
            "age":          { "type": "long"   },
            "name":   { 
              "type":         "object",
              "properties": {
                "full":     { "type": "string" },
                "first":    { "type": "string" },
                "last":     { "type": "string" }
              }
            }
          }
        }
      }
    }
  }
}

user 和 name 域的映射結構與 tweet 類型的相同。事實上, type 映射只是一種特殊的 對象 映射,咱們稱之爲 根對象 。除了它有一些文檔元數據的特殊頂級域,例如 _source 和 _all 域,它和其餘對象同樣。

內部對象是如何索引的

Lucene 不理解內部對象。 Lucene 文檔是由一組鍵值對列表組成的。爲了能讓 Elasticsearch 有效地索引內部類,它把咱們的文檔轉化成這樣:

{
    "tweet":            [elasticsearch, flexible, very],
    "user.id":          [@johnsmith],
    "user.gender":      [male],
    "user.age":         [26],
    "user.name.full":   [john, smith],
    "user.name.first":  [john],
    "user.name.last":   [smith]
}

內部域 能夠經過名稱引用(例如, first )。爲了區分同名的兩個域,咱們能夠使用全 路徑 (例如, user.name.first ) 或 type 名加路徑( tweet.user.name.first )。

在前面簡單扁平的文檔中,沒有 user 和 user.name 域。Lucene 索引只有標量和簡單值,沒有複雜數據結構。

內部對象數組

最後,考慮包含 內部對象的數組是如何被索引的。 假設咱們有個 followers 數組:

{
    "followers": [
        { "age": 35, "name": "Mary White"},
        { "age": 26, "name": "Alex Jones"},
        { "age": 19, "name": "Lisa Smith"}
    ]
}

這個文檔會像咱們以前描述的那樣被扁平化處理,結果以下所示:

{
    "followers.age":    [19, 26, 35],
    "followers.name":   [alex, jones, lisa, smith, mary, white]
}

{age: 35} 和 {name: Mary White} 之間的相關性已經丟失了,由於每一個多值域只是一包無序的值,而不是有序數組。這足以讓咱們問,「有一個26歲的追隨者?」

可是咱們不能獲得一個準確的答案:「是否有一個26歲 名字叫 Alex Jones 的追隨者?」

相關內部對象被稱爲 nested 對象,能夠回答上面的查詢。

7、請求體查詢

1. 空查詢

讓咱們以 最簡單的 search API 的形式開啓咱們的旅程,空查詢將返回全部索引庫(indices)中的全部文檔:

GET /_search
{}

只用一個查詢字符串,你就能夠在一個、多個或者 _all 索引庫(indices)和一個、多個或者全部types中查詢:

GET /index_2014*/type1,type2/_search
{}

同時你能夠使用 from 和 size 參數來分頁:

GET /_search
{
  "from": 30,
  "size": 10
}

2. 查詢表達式

查詢表達式(Query DSL)是一種很是靈活又富有表現力的 查詢語言。 Elasticsearch 使用它能夠以簡單的 JSON 接口來展示 Lucene 功能的絕大部分。在你的應用中,你應該用它來編寫你的查詢語句。它能夠使你的查詢語句更靈活、更精確、易讀和易調試。

要使用這種查詢表達式,只需將查詢語句傳遞給 query 參數:

GET /_search
{
    "query": YOUR_QUERY_HERE
}

空查詢(empty search) —{}— 在功能上等價於使用 match_all 查詢, 正如其名字同樣,匹配全部文檔:

GET /_search
{
    "query": {
        "match_all": {}
    }
}

查詢語句的結構

一個查詢語句 的典型結構:

{
    QUERY_NAME: {
        ARGUMENT: VALUE,
        ARGUMENT: VALUE,...
    }
}

若是是針對某個字段,那麼它的結構以下:

{
    QUERY_NAME: {
        FIELD_NAME: {
            ARGUMENT: VALUE,
            ARGUMENT: VALUE,...
        }
    }
}

舉個例子,你能夠使用 match 查詢語句 來查詢 tweet 字段中包含 elasticsearch 的 tweet:

{
    "match": {
        "tweet": "elasticsearch"
    }
}

完整的查詢請求以下:

GET /_search
{
    "query": {
        "match": {
            "tweet": "elasticsearch"
        }
    }
}

合併查詢語句

查詢語句(Query clauses) 就像一些簡單的組合塊 ,這些組合塊能夠彼此之間合併組成更復雜的查詢。這些語句能夠是以下形式:

  • 葉子語句(Leaf clauses) (就像 match 語句) 被用於將查詢字符串和一個字段(或者多個字段)對比。
  • 複合(Compound) 語句 主要用於 合併其它查詢語句。 好比,一個 bool 語句 容許在你須要的時候組合其它語句,不管是 must 匹配、 must_not 匹配仍是 should 匹配,同時它能夠包含不評分的過濾器(filters):


    「bool」: { 
    「must」: { 「match」: { 「tweet」: 「elasticsearch」 }}, 
    「must_not」: { 「match」: { 「name」: 「mary」 }}, 
    「should」: { 「match」: { 「tweet」: 「full text」 }}, 
    「filter」: { 「range」: { 「age」 : { 「gt」 : 30 }} } 

    }

一條複合語句能夠合併 任何 其它查詢語句,包括複合語句,瞭解這一點是很重要的。這就意味着,複合語句之間能夠互相嵌套,能夠表達很是複雜的邏輯。

例如,如下查詢是爲了找出信件正文包含 business opportunity 的星標郵件,或者在收件箱正文包含 business opportunity 的非垃圾郵件:

{
    "bool": {
        "must": { "match":   { "email": "business opportunity" }},
        "should": [
            { "match":       { "starred": true }},
            { "bool": {
                "must":      { "match": { "folder": "inbox" }},
                "must_not":  { "match": { "spam": true }}
            }}
        ],
        "minimum_should_match": 1
    }
}

到目前爲止,你沒必要太在乎這個例子的細節,咱們會在後面詳細解釋。最重要的是你要理解到,一條複合語句能夠將多條語句 — 葉子語句和其它複合語句 — 合併成一個單一的查詢語句。

3. 查詢與過濾

Elasticsearch 使用的查詢語言(DSL) 擁有一套查詢組件,這些組件能夠以無限組合的方式進行搭配。這套組件能夠在如下兩種狀況下使用:過濾狀況(filtering context)和查詢狀況(query context)。

當使用於 過濾狀況 時,查詢被設置成一個「不評分」或者「過濾」查詢。即,這個查詢只是簡單的問一個問題:「這篇文檔是否匹配?」。回答也是很是的簡單,yes 或者 no ,兩者必居其一。

  • created 時間是否在 2013 與 2014 這個區間?
  • status 字段是否包含 published 這個單詞?
  • lat_lon 字段表示的位置是否在指定點的 10km 範圍內?

當使用於 查詢狀況 時,查詢就變成了一個「評分」的查詢。和不評分的查詢相似,也要去判斷這個文檔是否匹配,同時它還須要判斷這個文檔匹配的有 多好(匹配程度如何)。 此查詢的典型用法是用於查找如下文檔:

  • 查找與 full text search 這個詞語最佳匹配的文檔
  • 包含 run 這個詞,也能匹配 runs 、 running 、 jog 或者 sprint
  • 包含 quick 、 brown 和 fox 這幾個詞 — 詞之間離的越近,文檔相關性越高
  • 標有 lucene 、 search 或者 java 標籤 — 標籤越多,相關性越高

一個評分查詢計算每個文檔與此查詢的 相關程度,同時將這個相關程度分配給表示相關性的字段 _score,而且按照相關性對匹配到的文檔進行排序。這種相關性的概念是很是適合全文搜索的狀況,由於全文搜索幾乎沒有徹底 「正確」 的答案。

自 Elasticsearch 問世以來,查詢與過濾(queries and filters)就獨自成爲 Elasticsearch 的組件。但從 Elasticsearch 2.0 開始,過濾(filters)已經從技術上被排除了,同時全部的查詢(queries)擁有變成不評分查詢的能力。

然而,爲了明確和簡單,咱們用 「filter」 這個詞表示不評分、只過濾狀況下的查詢。你能夠把 「filter」 、 「filtering query」 和 「non-scoring query」 這幾個詞視爲相同的。

類似的,若是單獨地不加任何修飾詞地使用 「query」 這個詞,咱們指的是 「scoring query」 。

性能差別

過濾查詢(Filtering queries)只是簡單的檢查包含或者排除,這就使得計算起來很是快。考慮到至少有一個過濾查詢(filtering query)的結果是 「稀少的」(不多匹配的文檔),而且常用不評分查詢(non-scoring queries),結果會被緩存到內存中以便快速讀取,因此有各類各樣的手段來優化查詢結果。

相反,評分查詢(scoring queries)不只僅要找出 匹配的文檔,還要計算每一個匹配文檔的相關性,計算相關性使得它們比不評分查詢費力的多。同時,查詢結果並不緩存。

多虧倒排索引(inverted index),一個簡單的評分查詢在匹配少許文檔時可能與一個涵蓋百萬文檔的filter表現的同樣好,甚至會更好。可是在通常狀況下,一個filter 會比一個評分的query性能更優異,而且每次都表現的很穩定。

過濾(filtering)的目標是減小那些須要經過評分查詢(scoring queries)進行檢查的文檔。

如何選擇查詢與過濾

一般的規則是,使用 查詢(query)語句來進行 全文 搜索或者其它任何須要影響 相關性得分 的搜索。除此之外的狀況都使用過濾(filters)。

4. 最重要的查詢

雖然 Elasticsearch 自帶了不少的查詢,但常常用到的也就那麼幾個。

match_all 查詢

match_all 查詢簡單的 匹配全部文檔。在沒有指定查詢方式時,它是默認的查詢:

{ "match_all": {}}

它常常與 filter 結合使用–例如,檢索收件箱裏的全部郵件。全部郵件被認爲具備相同的相關性,因此都將得到分值爲 1 的中性 _score。

match 查詢

不管你在任何字段上進行的是全文搜索仍是精確查詢,match 查詢是你可用的標準查詢。

若是你在一個全文字段上使用 match 查詢,在執行查詢前,它將用正確的分析器去分析查詢字符串:

{ "match": { "tweet": "About Search" }}

若是在一個精確值的字段上使用它, 例如數字、日期、布爾或者一個 not_analyzed 字符串字段,那麼它將會精確匹配給定的值:

{ "match": { "age":    26           }}
{ "match": { "date":   "2014-09-01" }}
{ "match": { "public": true         }}
{ "match": { "tag":    "full_text"  }}

對於精確值的查詢,你可能須要使用 filter 語句來取代 query,由於 filter 將會被緩存。接下來,咱們將看到一些關於 filter 的例子。

不像咱們在 輕量 搜索 章節介紹的字符串查詢(query-string search), match 查詢不使用相似 +user_id:2 +tweet:search 的查詢語法。它只是去查找給定的單詞。這就意味着將查詢字段暴露給你的用戶是安全的;你須要控制那些容許被查詢字段,不易於拋出語法異常。

multi_match 查詢

multi_match 查詢能夠在多個字段上執行相同的 match 查詢:

{
    "multi_match": {
        "query":    "full text search",
        "fields":   [ "title", "body" ]
    }
}

range 查詢

range 查詢找出那些落在指定區間內的數字或者時間:

{
    "range": {
        "age": {
            "gte":  20,
            "lt":   30
        }
    }
}

被容許的操做符以下:

  • gt 
    大於
  • gte 
    大於等於
  • lt 
    小於
  • lte 
    小於等於

term 查詢

term 查詢被用於精確值 匹配,這些精確值多是數字、時間、布爾或者那些 not_analyzed 的字符串:

{ "term": { "age":    26           }}
{ "term": { "date":   "2014-09-01" }}
{ "term": { "public": true         }}
{ "term": { "tag":    "full_text"  }}

term 查詢對於輸入的文本不 分析 ,因此它將給定的值進行精確查詢。

terms 查詢

terms 查詢和 term 查詢同樣,但它容許你指定多值進行匹配。若是這個字段包含了指定值中的任何一個值,那麼這個文檔知足條件:

{ "terms": { "tag": [ "search", "full_text", "nosql" ] }}

和 term 查詢同樣,terms 查詢對於輸入的文本不分析。它查詢那些精確匹配的值(包括在大小寫、重音、空格等方面的差別)。

exists 查詢和 missing 查詢

exists 查詢和 missing 查詢被用於查找那些指定字段中有值 (exists) 或無值 (missing) 的文檔。這與SQL中的 IS_NULL (missing) 和 NOT IS_NULL (exists) 在本質上具備共性:

{
    "exists":   {
        "field":    "title"
    }
}

這些查詢常常用於某個字段有值的狀況和某個字段缺值的狀況。

  1. 組合多查詢

現實的查詢需求歷來都沒有那麼簡單;它們須要在多個字段上查詢多種多樣的文本,而且根據一系列的標準來過濾。爲了構建相似的高級查詢,你須要一種可以將多查詢組合成單一查詢的查詢方法。

你能夠用 bool 查詢來實現你的需求。這種查詢將多查詢組合在一塊兒,成爲用戶本身想要的布爾查詢。它接收如下參數:

  • must 
    文檔 必須 匹配這些條件才能被包含進來。
  • must_not 
    文檔 必須不 匹配這些條件才能被包含進來。
  • should 
    若是知足這些語句中的任意語句,將增長 _score ,不然,無任何影響。它們主要用於修正每一個文檔的相關性得分。
  • filter 
    必須 匹配,但它以不評分、過濾模式來進行。這些語句對評分沒有貢獻,只是根據過濾標準來排除或包含文檔。

因爲這是咱們看到的第一個包含多個查詢的查詢,因此有必要討論一下相關性得分是如何組合的。每個子查詢都獨自地計算文檔的相關性得分。一旦他們的得分被計算出來, bool 查詢就將這些得分進行合併而且返回一個表明整個布爾操做的得分。

下面的查詢用於查找 title 字段匹配 how to make millions 而且不被標識爲 spam 的文檔。那些被標識爲 starred 或在2014以後的文檔,將比另外那些文檔擁有更高的排名。若是 二者 都知足,那麼它排名將更高:

{
    "bool": {
        "must":     { "match": { "title": "how to make millions" }},
        "must_not": { "match": { "tag":   "spam" }},
        "should": [
            { "match": { "tag": "starred" }},
            { "range": { "date": { "gte": "2014-01-01" }}}
        ]
    }
}

若是沒有 must 語句,那麼至少須要可以匹配其中的一條 should 語句。但,若是存在至少一條 must 語句,則對 should 語句的匹配沒有要求。

增長帶過濾器(filtering)的查詢

若是咱們不想由於文檔的時間而影響得分,能夠用 filter 語句來重寫前面的例子:

{
    "bool": {
        "must":     { "match": { "title": "how to make millions" }},
        "must_not": { "match": { "tag":   "spam" }},
        "should": [
            { "match": { "tag": "starred" }}
        ],
        "filter": {
          "range": { "date": { "gte": "2014-01-01" }} 
        }
    }
}

range 查詢已經從 should 語句中移到 filter 語句

經過將 range 查詢移到 filter 語句中,咱們將它轉成不評分的查詢,將再也不影響文檔的相關性排名。因爲它如今是一個不評分的查詢,能夠使用各類對 filter 查詢有效的優化手段來提高性能。

全部查詢均可以借鑑這種方式。將查詢移到 bool 查詢的 filter 語句中,這樣它就自動的轉成一個不評分的 filter 了。

若是你須要經過多個不一樣的標準來過濾你的文檔,bool 查詢自己也能夠被用作不評分的查詢。簡單地將它放置到 filter 語句中並在內部構建布爾邏輯:

{
    "bool": {
        "must":     { "match": { "title": "how to make millions" }},
        "must_not": { "match": { "tag":   "spam" }},
        "should": [
            { "match": { "tag": "starred" }}
        ],
        "filter": {
          "bool": { 
              "must": [
                  { "range": { "date": { "gte": "2014-01-01" }}},
                  { "range": { "price": { "lte": 29.99 }}}
              ],
              "must_not": [
                  { "term": { "category": "ebooks" }}
              ]
          }
        }
    }
}

將 bool 查詢包裹在 filter 語句中,咱們能夠在過濾標準中增長布爾邏輯

經過混合布爾查詢,咱們能夠在咱們的查詢請求中靈活地編寫 scoring 和 filtering 查詢邏輯。

constant_score 查詢

儘管沒有 bool 查詢使用這麼頻繁,constant_score 查詢也是你工具箱裏有用的查詢工具。它將一個不變的常量評分應用於全部匹配的文檔。它被常常用於你只須要執行一個 filter 而沒有其它查詢(例如,評分查詢)的狀況下。

能夠使用它來取代只有 filter 語句的 bool 查詢。在性能上是徹底相同的,但對於提升查詢簡潔性和清晰度有很大幫助。

{
    "constant_score":   {
        "filter": {
            "term": { "category": "ebooks" } 
        }
    }
}

term 查詢被放置在 constant_score 中,轉成不評分的 filter。這種方式能夠用來取代只有 filter 語句的 bool 查詢。

6. 驗證查詢

查詢能夠變得很是的複雜,尤爲 和不一樣的分析器與不一樣的字段映射結合時,理解起來就有點困難了。不過 validate-query API 能夠用來驗證查詢是否合法。

GET /gb/tweet/_validate/query
{
   "query": {
      "tweet" : {
         "match" : "really powerful"
      }
   }
}

以上 validate 請求的應答告訴咱們這個查詢是不合法的:

{
  "valid" :         false,
  "_shards" : {
    "total" :       1,
    "successful" :  1,
    "failed" :      0
  }
}

理解錯誤信息

爲了找出 查詢不合法的緣由,能夠將 explain 參數 加到查詢字符串中:

GET /gb/tweet/_validate/query?explain 
{
   "query": {
      "tweet" : {
         "match" : "really powerful"
      }
   }
}

explain 參數能夠提供更多關於查詢不合法的信息。

很明顯,咱們將查詢類型(match)與字段名稱 (tweet)搞混了:

{
  "valid" :     false,
  "_shards" :   { ... },
  "explanations" : [ {
    "index" :   "gb",
    "valid" :   false,
    "error" :   "org.elasticsearch.index.query.QueryParsingException:
                 [gb] No query registered for [tweet]"
  } ]
}

理解查詢語句

對於合法查詢,使用 explain 參數將返回可讀的描述,這對準確理解 Elasticsearch 是如何解析你的 query 是很是有用的:

GET /_validate/query?explain
{
   "query": {
      "match" : {
         "tweet" : "really powerful"
      }
   }
}

咱們查詢的每個 index 都會返回對應的 explanation ,由於每個 index 都有本身的映射和分析器:

{
  "valid" :         true,
  "_shards" :       { ... },
  "explanations" : [ {
    "index" :       "us",
    "valid" :       true,
    "explanation" : "tweet:really tweet:powerful"
  }, {
    "index" :       "gb",
    "valid" :       true,
    "explanation" : "tweet:realli tweet:power"
  } ]
}

從 explanation 中能夠看出,匹配 really powerful 的 match 查詢被重寫爲兩個針對 tweet 字段的 single-term 查詢,一個single-term查詢對應查詢字符串分出來的一個term。

固然,對於索引 us ,這兩個 term 分別是 really 和 powerful ,而對於索引 gb ,term 則分別是 realli 和 power 。之因此出現這個狀況,是因爲咱們將索引 gb 中 tweet 字段的分析器修改成 english 分析器。

8、排序與相關性

默認狀況下,返回的結果是按照 相關性 進行排序的——最相關的文檔排在最前。 在本章的後面部分,咱們會解釋 相關性 意味着什麼以及它是如何計算的, 不過讓咱們首先看看 sort 參數以及如何使用它。

  1. 排序

爲了按照相關性來排序,須要將相關性表示爲一個數值。在 Elasticsearch 中, 相關性得分 由一個浮點數進行表示,並在搜索結果中經過 _score 參數返回, 默認排序是 _score 降序。

有時,相關性評分對你來講並無意義。例如,下面的查詢返回全部 user_id 字段包含 1 的結果:

GET /_search
{
    "query" : {
        "bool" : {
            "filter" : {
                "term" : {
                    "user_id" : 1
                }
            }
        }
    }
}

這裏沒有一個有意義的分數:由於咱們使用的是 filter (過濾),這代表咱們只但願獲取匹配 user_id: 1 的文檔,並無試圖肯定這些文檔的相關性。 實際上文檔將按照隨機順序返回,而且每一個文檔都會評爲零分。

若是評分爲零對你形成了困擾,你能夠使用 constant_score 查詢進行替代:

GET /_search
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "term" : {
                    "user_id" : 1
                }
            }
        }
    }
}

這將讓全部文檔應用一個恆定分數(默認爲 1 )。它將執行與前述查詢相同的查詢,而且全部的文檔將像以前同樣隨機返回,這些文檔只是有了一個分數而不是零分。

按照字段的值排序

在這個案例中,經過時間來對 tweets 進行排序是有意義的,最新的 tweets 排在最前。 咱們能夠使用 sort 參數進行實現:

GET /_search
{
    "query" : {
        "bool" : {
            "filter" : { "term" : { "user_id" : 1 }}
        }
    },
    "sort": { "date": { "order": "desc" }}
}

你會注意到結果中的兩個不一樣點:

"hits" : {
    "total" :           6,
    "max_score" :       null, 
    "hits" : [ {
        "_index" :      "us",
        "_type" :       "tweet",
        "_id" :         "14",
        "_score" :      null, 
        "_source" :     {
             "date":    "2014-09-24",
             ...
        },
        "sort" :        [ 1411516800000 ] 
    },
    ...
}


    _score 不被計算, 由於它並無用於排序。                
    date 字段的值表示爲自 epoch (January 1, 1970 00:00:00 UTC)以來的毫秒數,經過 sort 字段的值進行返回。

首先咱們在每一個結果中有一個新的名爲 sort 的元素,它包含了咱們用於排序的值。 在這個案例中,咱們按照 date 進行排序,在內部被索引爲 自 epoch 以來的毫秒數 。 long 類型數 1411516800000 等價於日期字符串 2014-09-24 00:00:00 UTC 。

其次 _score 和 max_score 字段都是 null 。 計算 _score 的花銷巨大,一般僅用於排序; 咱們並不根據相關性排序,因此記錄 _score 是沒有意義的。若是不管如何你都要計算 _score , 你能夠將 track_scores 參數設置爲 true 。

一個簡便方法是, 你能夠 指定一個字段用來排序:

"sort": "number_of_children"

字段將會默認升序排序 ,而按照 _score 的值進行降序排序。

多級排序

假定咱們想要結合使用 date 和 _score 進行查詢,而且匹配的結果首先按照日期排序,而後按照相關性排序:

GET /_search
{
    "query" : {
        "bool" : {
            "must":   { "match": { "tweet": "manage text search" }},
            "filter" : { "term" : { "user_id" : 2 }}
        }
    },
    "sort": [
        { "date":   { "order": "desc" }},
        { "_score": { "order": "desc" }}
    ]
}

排序條件的順序是很重要的。結果首先按第一個條件排序,僅當結果集的第一個 sort 值徹底相同時纔會按照第二個條件進行排序,以此類推。

多級排序並不必定包含 _score 。你能夠根據一些不一樣的字段進行排序, 如地理距離或是腳本計算的特定值。

Query-string 搜索 也支持自定義排序,能夠在查詢字符串中使用 sort 參數:

GET /_search?sort=date:desc&sort=_score&q=search

多值字段的排序

一種情形是字段有多個值的排序, 須要記住這些值並無固有的順序;一個多值的字段僅僅是多個值的包裝,這時應該選擇哪一個進行排序呢?

對於數字或日期,你能夠將多值字段減爲單值,這能夠經過使用 min 、 max 、 avg 或是 sum 排序模式 。 例如你能夠按照每一個 date 字段中的最先日期進行排序,經過如下方法:

"sort": {
    "dates": {
        "order": "asc",
        "mode":  "min"
    }
}

2. 字符串排序與多字段

被解析的字符串字段也是多值字段, 可是不多會按照你想要的方式進行排序。若是你想分析一個字符串,如 fine old art , 這包含 3 項。咱們極可能想要按第一項的字母排序,而後按第二項的字母排序,諸如此類,可是 Elasticsearch 在排序過程當中沒有這樣的信息。

你能夠使用 min 和 max 排序模式(默認是 min ),可是這會致使排序以 art 或是 old ,任何一個都不是所但願的。

爲了以字符串字段進行排序,這個字段應僅包含一項: 整個 not_analyzed 字符串。 可是咱們仍須要 analyzed 字段,這樣才能以全文進行查詢

一個簡單的方法是用兩種方式對同一個字符串進行索引,這將在文檔中包括兩個字段: analyzed 用於搜索, not_analyzed 用於排序

可是保存相同的字符串兩次在 _source 字段是浪費空間的。 咱們真正想要作的是傳遞一個 單字段 可是卻用兩種方式索引它。全部的 _core_field 類型 (strings, numbers, Booleans, dates) 接收一個 fields 參數

該參數容許你轉化一個簡單的映射如:

"tweet": {
    "type":     "string",
    "analyzer": "english"
}

爲一個多字段映射如:

"tweet": { 
    "type":     "string",
    "analyzer": "english",
    "fields": {
        "raw": { 
            "type":  "string",
            "index": "not_analyzed"
        }
    }
}


    tweet 主字段與以前的同樣: 是一個 analyzed 全文字段。
    新的 tweet.raw 子字段是 not_analyzed.

如今,至少只要咱們從新索引了咱們的數據,使用 tweet 字段用於搜索,tweet.raw 字段用於排序:

GET /_search
{
    "query": {
        "match": {
            "tweet": "elasticsearch"
        }
    },
    "sort": "tweet.raw"
}

以全文 analyzed 字段排序會消耗大量的內存。

3. 什麼是相關性?

咱們曾經講過,默認狀況下,返回結果是按相關性倒序排列的。 可是什麼是相關性? 相關性如何計算?

每一個文檔都有相關性評分,用一個正浮點數字段 _score 來表示 。 _score 的評分越高,相關性越高。

查詢語句會爲每一個文檔生成一個 _score 字段。評分的計算方式取決於查詢類型 不一樣的查詢語句用於不一樣的目的: fuzzy 查詢會計算與關鍵詞的拼寫類似程度,terms 查詢會計算 找到的內容與關鍵詞組成部分匹配的百分比,可是一般咱們說的 relevance 是咱們用來計算全文本字段的值相對於全文本檢索詞類似程度的算法。

Elasticsearch 的類似度算法 被定義爲檢索詞頻率/反向文檔頻率, TF/IDF ,包括如下內容:

  • 檢索詞頻率 
    檢索詞在該字段出現的頻率?出現頻率越高,相關性也越高。 字段中出現過 5 次要比只出現過 1 次的相關性高。
  • 反向文檔頻率 
    每一個檢索詞在索引中出現的頻率?頻率越高,相關性越低。檢索詞出如今多數文檔中會比出如今少數文檔中的權重更低。
  • 字段長度準則 
    字段的長度是多少?長度越長,相關性越低。 檢索詞出如今一個短的 title 要比一樣的詞出如今一個長的 content 字段權重更大。

單個查詢能夠聯合使用 TF/IDF 和其餘方式,好比短語查詢中檢索詞的距離或模糊查詢裏的檢索詞類似度。

相關性並不僅是全文本檢索的專利。也適用於 yes|no 的子句,匹配的子句越多,相關性評分越高。

若是多條查詢子句被合併爲一條複合查詢語句 ,好比 bool 查詢,則每一個查詢子句計算得出的評分會被合併到總的相關性評分中。

理解評分標準

當調試一條複雜的查詢語句時, 想要理解 _score 到底是如何計算是比較困難的。Elasticsearch 在 每一個查詢語句中都有一個 explain 參數,將 explain 設爲 true 就能夠獲得更詳細的信息。

GET /_search?explain 
{
   "query"   : { "match" : { "tweet" : "honeymoon" }}
}

explain 參數可讓返回結果添加一個 _score 評分的得來依據。

增長一個 explain 參數會爲每一個匹配到的文檔產生一大堆額外內容,可是花時間去理解它是頗有意義的。 若是如今看不明白也不要緊 — 等你須要的時候再來回顧這一節就行。下面咱們來一點點的瞭解這塊知識點。

首先,咱們看一下普通查詢返回的元數據:

{
    "_index" :      "us",
    "_type" :       "tweet",
    "_id" :         "12",
    "_score" :      0.076713204,
    "_source" :     { ... trimmed ... },

這裏加入了該文檔來自於哪一個節點哪一個分片上的信息,這對咱們是比較有幫助的,由於詞頻率和 文檔頻率是在每一個分片中計算出來的,而不是每一個索引中:

"_shard" :      1,
    "_node" :       "mzIVYCsqSWCG_M_ZffSs9Q",

而後它提供了 _explanation 。每一個 入口都包含一個 description 、 value 、 details 字段,它分別告訴你計算的類型、計算結果和任何咱們須要的計算細節。

"_explanation": { 
   "description": "weight(tweet:honeymoon in 0)
                  [PerFieldSimilarity], result of:",
   "value":       0.076713204,
   "details": [
      {
         "description": "fieldWeight in 0, product of:",
         "value":       0.076713204,
         "details": [
            {  
               "description": "tf(freq=1.0), with freq of:",
               "value":       1,
               "details": [
                  {
                     "description": "termFreq=1.0",
                     "value":       1
                  }
               ]
            },
            { 
               "description": "idf(docFreq=1, maxDocs=1)",
               "value":       0.30685282
            },
            { 
               "description": "fieldNorm(doc=0)",
               "value":        0.25,
            }
         ]
      }
   ]
}


    honeymoon 相關性評分計算的總結
    檢索詞頻率               
    反向文檔頻率              
    字段長度準則

輸出 explain 結果代價是十分昂貴的,它只能用做調試工具 。千萬不要用於生產環境。

第一部分是關於計算的總結。告訴了咱們 honeymoon 在 tweet 字段中的檢索詞頻率/反向文檔頻率或 TF/IDF, (這裏的文檔 0 是一個內部的 ID,跟咱們沒有關係,能夠忽略。)

而後它提供了權重是如何計算的細節:

檢索詞頻率:

檢索詞 `honeymoon` 在這個文檔的 `tweet` 字段中的出現次數。

反向文檔頻率:

檢索詞 `honeymoon` 在索引上全部文檔的 `tweet` 字段中出現的次數。

字段長度準則:

在這個文檔中, `tweet` 字段內容的長度 -- 內容越長,值越小。

複雜的查詢語句解釋也很是複雜,可是包含的內容與上面例子大體相同。 經過這段信息咱們能夠了解搜索結果是如何產生的。

JSON 形式的 explain 描述是難以閱讀的, 可是轉成 YAML 會好不少,只須要在參數中加上 format=yaml 。

理解文檔是如何被匹配到的

當 explain 選項加到某一文檔上時, explain api 會幫助你理解爲什麼這個文檔會被匹配,更重要的是,一個文檔爲什麼沒有被匹配。

請求路徑爲 /index/type/id/_explain ,以下所示:

GET /us/tweet/12/_explain
{
   "query" : {
      "bool" : {
         "filter" : { "term" :  { "user_id" : 2           }},
         "must" :  { "match" : { "tweet" :   "honeymoon" }}
      }
   }
}

不僅是咱們以前看到的充分解釋 ,咱們如今有了一個 description 元素,它將告訴咱們:

"failure to match filter: cache(user_id:[2 TO 2])"

也就是說咱們的 user_id 過濾子句使該文檔不能匹配到。

  1. Doc Values 介紹

本章的最後一個話題是關於 Elasticsearch 內部的一些運行狀況。在這裏咱們先不介紹新的知識點,因此咱們應該意識到,Doc Values 是咱們須要反覆提到的一個重要話題。

當你對一個字段進行排序時,Elasticsearch 須要訪問每一個匹配到的文檔獲得相關的值。倒排索引的檢索性能是很是快的,可是在字段值排序時卻不是理想的結構。

  • 在搜索的時候,咱們能經過搜索關鍵詞快速獲得結果集。
  • 當排序的時候,咱們須要倒排索引裏面某個字段值的集合。換句話說,咱們須要 轉置 倒排索引。

轉置 結構在其餘系統中常常被稱做 列存儲 。實質上,它將全部單字段的值存儲在單數據列中,這使得對其進行操做是十分高效的,例如排序。

在 Elasticsearch 中,Doc Values 就是一種列式存儲結構,默認狀況下每一個字段的 Doc Values 都是激活的,Doc Values 是在索引時建立的,當字段索引時,Elasticsearch 爲了可以快速檢索,會把字段的值加入倒排索引中,同時它也會存儲該字段的 Doc Values。

Elasticsearch 中的 Doc Values 常被應用到如下場景:

  • 對一個字段進行排序
  • 對一個字段進行聚合
  • 某些過濾,好比地理位置過濾
  • 某些與字段相關的腳本計算

由於文檔值被序列化到磁盤,咱們能夠依靠操做系統的幫助來快速訪問。當 working set 遠小於節點的可用內存,系統會自動將全部的文檔值保存在內存中,使得其讀寫十分高速; 當其遠大於可用內存,操做系統會自動把 Doc Values 加載到系統的頁緩存中,從而避免了 jvm 堆內存溢出異常。

咱們稍後會深刻討論 Doc Values。如今全部你須要知道的是排序發生在索引時創建的平行數據結構中。

9、執行分佈式檢索

在繼續以前,咱們將繞道討論一下在分佈式環境中搜索是怎麼執行的。 這比咱們在 分佈式文檔存儲 章節討論的基本的 增-刪-改-查 (CRUD)請求要複雜一些。

內容提示

你能夠根據興趣閱讀本章內容。你並不須要爲了使用 Elasticsearch 而理解和記住全部的細節。

這章的閱讀目的只爲初步瞭解下工做原理,以便未來須要時能夠及時找到這些知識, 可是不要被細節所困擾。

一個 CRUD 操做只對單個文檔進行處理,文檔的惟一性由 _index, _type, 和 routing values (一般默認是該文檔的 _id )的組合來肯定。 這表示咱們確切的知道集羣中哪一個分片含有此文檔。

搜索須要一種更加複雜的執行模型由於咱們不知道查詢會命中哪些文檔: 這些文檔有可能在集羣的任何分片上。 一個搜索請求必須詢問咱們關注的索引(index or indices)的全部分片的某個副原本肯定它們是否含有任何匹配的文檔。

可是找到全部的匹配文檔僅僅完成事情的一半。 在 search 接口返回一個 page 結果以前,多分片中的結果必須組合成單個排序列表。 爲此,搜索被執行成一個兩階段過程,咱們稱之爲 query then fetch 。

  1. 查詢階段

在初始 查詢階段 時, 查詢會廣播到索引中每個分片拷貝(主分片或者副本分片)。 每一個分片在本地執行搜索並構建一個匹配文檔的 優先隊列。

優先隊列

一個 優先隊列 僅僅是一個存有 top-n 匹配文檔的有序列表。優先隊列的大小取決於分頁參數 from 和 size 。例如,以下搜索請求將須要足夠大的優先隊列來放入100條文檔。

GET /_search
{
    "from": 90,
    "size": 10
}

這個查詢階段的過程如圖 圖 14 「查詢過程分佈式搜索」 所示。

圖 14. 查詢過程分佈式搜索

查詢階段包含如下三個步驟:

  1. 客戶端發送一個 search 請求到 Node 3 , Node 3 會建立一個大小爲 from + size 的空優先隊列。
  2. Node 3 將查詢請求轉發到索引的每一個主分片或副本分片中。每一個分片在本地執行查詢並添加結果到大小爲 from + size 的本地有序優先隊列中。
  3. 每一個分片返回各自優先隊列中全部文檔的 ID 和排序值給協調節點,也就是 Node 3 ,它合併這些值到本身的優先隊列中來產生一個全局排序後的結果列表。

當一個搜索請求被髮送到某個節點時,這個節點就變成了協調節點。 這個節點的任務是廣播查詢請求到全部相關分片並將它們的響應整合成全局排序後的結果集合,這個結果集合會返回給客戶端。

第一步是廣播請求到索引中每個節點的分片拷貝。就像 document GET requests 所描述的, 查詢請求能夠被某個主分片或某個副本分片處理, 這就是爲何更多的副本(當結合更多的硬件)可以增長搜索吞吐率。 協調節點將在以後的請求中輪詢全部的分片拷貝來分攤負載。

每一個分片在本地執行查詢請求而且建立一個長度爲 from + size 的優先隊列—也就是說,每一個分片建立的結果集足夠大,都可以知足全局的搜索請求。 分片返回一個輕量級的結果列表到協調節點,它僅包含文檔 ID 集合以及任何排序須要用到的值,例如 _score 。

協調節點將這些分片級的結果合併到本身的有序優先隊列裏,它表明了全局排序結果集合。至此查詢過程結束。

一個索引能夠由一個或幾個主分片組成, 因此一個針對單個索引的搜索請求須要可以把來自多個分片的結果組合起來。 針對 multiple 或者 all 索引的搜索工做方式也是徹底一致的–僅僅是包含了更多的分片而已。

  1. 取回階段

查詢階段標識哪些文檔知足 搜索請求,可是咱們仍然須要取回這些文檔。這是取回階段的任務, 正如 圖 15 「分佈式搜索的取回階段」 所展現的。

圖 15. 分佈式搜索的取回階段

分佈式階段由如下步驟構成:

  1. 協調節點辨別出哪些文檔須要被取回並向相關的分片提交多個 GET 請求。
  2. 每一個分片加載並 豐富 文檔,若是有須要的話,接着返回文檔給協調節點。
  3. 一旦全部的文檔都被取回了,協調節點返回結果給客戶端。

協調節點首先決定哪些文檔 確實 須要被取回。例如,若是咱們的查詢指定了 { 「from」: 90, 「size」: 10 } ,最初的90個結果會被丟棄,只有從第91個開始的10個結果須要被取回。這些文檔可能來自和最初搜索請求有關的一個、多個甚至所有分片。

協調節點給持有相關文檔的每一個分片建立一個 multi-get request ,併發送請求給一樣處理查詢階段的分片副本。

分片加載文檔體– _source 字段–若是有須要,用元數據和 search snippet highlighting 豐富結果文檔。 一旦協調節點接收到全部的結果文檔,它就組裝這些結果爲單個響應返回給客戶端。

深分頁(Deep Pagination)

先查後取的過程支持用 from 和 size 參數分頁,可是這是 有限制的 。 要記住須要傳遞信息給協調節點的每一個分片必須先建立一個 from + size 長度的隊列,協調節點須要根據 number_of_shards * (from + size) 排序文檔,來找到被包含在 size 裏的文檔。

取決於你的文檔的大小,分片的數量和你使用的硬件,給 10,000 到 50,000 的結果文檔深分頁( 1,000 到 5,000 頁)是徹底可行的。可是使用足夠大的 from 值,排序過程可能會變得很是沉重,使用大量的CPU、內存和帶寬。由於這個緣由,咱們強烈建議你不要使用深分頁。

實際上, 「深分頁」 不多符合人的行爲。當2到3頁過去之後,人會中止翻頁,而且改變搜索標準。會不知疲倦地一頁一頁的獲取網頁直到你的服務崩潰的罪魁禍首通常是機器人或者web spider。

若是你 確實 須要從你的集羣取回大量的文檔,你能夠經過用 scroll 查詢禁用排序使這個取回行爲更有效率,咱們會在 later in this chapter 進行討論。

  1. 搜索選項

有幾個 查詢參數能夠影響搜索過程。

偏好

偏好這個參數 preference 容許 用來控制由哪些分片或節點來處理搜索請求。 它接受像 _primary, _primary_first, _local, _only_node:xyz, _prefer_node:xyz, 和 _shards:2,3 這樣的值, 這些值在 search preference 文檔頁面被詳細解釋。

可是最有用的值是某些隨機字符串,它能夠避免 bouncing results 問題。

Bouncing Results

想象一下有兩個文檔有一樣值的時間戳字段,搜索結果用 timestamp 字段來排序。 因爲搜索請求是在全部有效的分片副本間輪詢的,那就有可能發生主分片處理請求時,這兩個文檔是一種順序, 而副本分片處理請求時又是另外一種順序。

這就是所謂的 bouncing results 問題: 每次用戶刷新頁面,搜索結果表現是不一樣的順序。 讓同一個用戶始終使用同一個分片,這樣能夠避免這種問題, 能夠設置 preference 參數爲一個特定的任意值好比用戶會話ID來解決。

超時問題

一般分片處理完它全部的數據後再把結果返回給協同節點,協同節點把收到的全部結果合併爲最終結果。

這意味着花費的時間是最慢分片的處理時間加結果合併的時間。若是有一個節點有問題,就會致使全部的響應緩慢。

參數 timeout 告訴 分片容許處理數據的最大時間。若是沒有足夠的時間處理全部數據,這個分片的結果能夠是部分的,甚至是空數據。

搜索的返回結果會用屬性 timed_out 標明分片是否返回的是部分結果:

...
    "timed_out":     true,  
    ...

這個搜索請求超時了。

超時仍然是一個最有效的操做,知道這一點很重要; 極可能查詢會超過設定的超時時間。這種行爲有兩個緣由:

  1. 超時檢查是基於每文檔作的。 可是某些查詢類型有大量的工做在文檔評估以前須要完成。 這種 「setup」 階段並不考慮超時設置,因此太長的創建時間會致使超過超時時間的總體延遲。
  2. 由於時間檢查是基於每一個文檔的,一次長時間查詢在單個文檔上執行而且在下個文檔被評估以前不會超時。 這也意味着差的腳本(好比帶無限循環的腳本)將會永遠執行下去。

路由

在 路由一個文檔到一個分片中 中, 咱們解釋過如何定製參數 routing ,它可以在索引時提供來確保相關的文檔,好比屬於某個用戶的文檔被存儲在某個分片上。 在搜索的時候,不用搜索索引的全部分片,而是經過指定幾個 routing 值來限定只搜索幾個相關的分片:

GET /_search?routing=user_1,user2

這個技術在設計大規模搜索系統時就會派上用場,咱們在 擴容設計 中詳細討論它。

搜索類型

缺省的搜索類型是 query_then_fetch 。 在某些狀況下,你可能想明確設置 search_type 爲 dfs_query_then_fetch 來改善相關性精確度:

GET /_search?search_type=dfs_query_then_fetch

搜索類型 dfs_query_then_fetch 有預查詢階段,這個階段能夠從全部相關分片獲取詞頻來計算全局詞頻。 咱們在 被破壞的相關度! 會再討論它。

  1. 遊標查詢 Scroll

scroll 查詢 能夠用來對 Elasticsearch 有效地執行大批量的文檔查詢,而又不用付出深度分頁那種代價。

遊標查詢容許咱們 先作查詢初始化,而後再批量地拉取結果。 這有點兒像傳統數據庫中的 cursor 。

遊標查詢會取某個時間點的快照數據。 查詢初始化以後索引上的任何變化會被它忽略。 它經過保存舊的數據文件來實現這個特性,結果就像保留初始化時的索引 視圖 同樣。

深度分頁的代價根源是結果集全局排序,若是去掉全局排序的特性的話查詢結果的成本就會很低。 遊標查詢用字段 _doc 來排序。 這個指令讓 Elasticsearch 僅僅從還有結果的分片返回下一批結果。

啓用遊標查詢能夠經過在查詢的時候設置參數 scroll 的值爲咱們指望的遊標查詢的過時時間。 遊標查詢的過時時間會在每次作查詢的時候刷新,因此這個時間只須要足夠處理當前批的結果就能夠了,而不是處理查詢結果的全部文檔的所需時間。 這個過時時間的參數很重要,由於保持這個遊標查詢窗口須要消耗資源,因此咱們指望若是再也不須要維護這種資源就該早點兒釋放掉。 設置這個超時可以讓 Elasticsearch 在稍後空閒的時候自動釋放這部分資源。

GET /old_index/_search?scroll=1m 
{
    "query": { "match_all": {}},
    "sort" : ["_doc"], 
    "size":  1000
}


    保持遊標查詢窗口一分鐘。       
    關鍵字 _doc 是最有效的排序順序。

這個查詢的返回結果包括一個字段 _scroll_id, 它是一個base64編碼的長字符串 (((「scroll_id」))) 。 如今咱們能傳遞字段 _scroll_id 到 _search/scroll 查詢接口獲取下一批結果:

GET /_search/scroll
{
    "scroll": "1m", 
    "scroll_id" : "cXVlcnlUaGVuRmV0Y2g7NTsxMDk5NDpkUmpiR2FjOFNhNnlCM1ZDMWpWYnRROzEwOTk1OmRSamJHYWM4U2E2eUIzVkMxalZidFE7MTA5OTM6ZFJqYkdhYzhTYTZ5QjNWQzFqVmJ0UTsxMTE5MDpBVUtwN2lxc1FLZV8yRGVjWlI2QUVBOzEwOTk2OmRSamJHYWM4U2E2eUIzVkMxalZidFE7MDs="
}


    注意再次設置遊標查詢過時時間爲一分鐘。

這個遊標查詢返回的下一批結果。 儘管咱們指定字段 size 的值爲1000,咱們有可能取到超過這個值數量的文檔。 當查詢的時候, 字段 size 做用於單個分片,因此每一個批次實際返回的文檔數量最大爲 size * number_of_primary_shards 。

注意遊標查詢每次返回一個新字段 _scroll_id。每次咱們作下一次遊標查詢, 咱們必須把前一次查詢返回的字段 _scroll_id 傳遞進去。 當沒有更多的結果返回的時候,咱們就處理完全部匹配的文檔了。

提示:某些官方的 Elasticsearch 客戶端好比 Python 客戶端 和 Perl 客戶端 提供了這個功能易用的封裝。

10、索引管理

咱們已經看到 Elasticsearch 讓開發一個新的應用變得簡單,不須要任何預先計劃或設置。 不過,要不了多久你就會開始想要優化索引和搜索過程,以便更好地適合您的特定用例。 這些定製幾乎圍繞着索引和類型的方方面面,在本章,咱們將介紹管理索引和類型映射的 API 以及一些最重要的設置。

  1. 建立一個索引

到目前爲止, 咱們已經經過索引一篇文檔建立了一個新的索引 。這個索引採用的是默認的配置,新的字段經過動態映射的方式被添加到類型映射。如今咱們須要對這個創建索引的過程作更多的控制:咱們想要確保這個索引有數量適中的主分片,而且在咱們索引任何數據 以前 ,分析器和映射已經被創建好。

爲了達到這個目的,咱們須要手動建立索引,在請求體裏面傳入設置或類型映射,以下所示:

PUT /my_index
{
    "settings": { ... any settings ... },
    "mappings": {
        "type_one": { ... any mappings ... },
        "type_two": { ... any mappings ... },
        ...
    }
}

若是你想禁止自動建立索引,你 能夠經過在 config/elasticsearch.yml 的每一個節點下添加下面的配置:

action.auto_create_index: false

咱們會在以後討論你怎麼用 索引模板 來預配置開啓自動建立索引。這在索引日誌數據的時候尤爲有用:你將日誌數據索引在一個以日期結尾命名的索引上,子夜時分,一個預配置的新索引將會自動進行建立。

  1. 刪除一個索引

用如下的請求來 刪除索引:

DELETE /my_index

你也能夠這樣刪除多個索引:

DELETE /index_one,index_two
DELETE /index_*

你甚至能夠這樣刪除 所有 索引:

DELETE /_all
DELETE /*

對一些人來講,可以用單個命令來刪除全部數據可能會致使可怕的後果。若是你想要避免意外的大量刪除, 你能夠在你的 elasticsearch.yml 作以下配置:

action.destructive_requires_name: true

這個設置使刪除只限於特定名稱指向的數據, 而不容許經過指定 _all 或通配符來刪除指定索引庫。你一樣能夠經過 Cluster State API 動態的更新這個設置。

  1. 索引設置

你能夠經過修改配置來自定義索引行爲,詳細配置參照 索引模塊

Elasticsearch 提供了優化好的默認配置。 除非你理解這些配置的做用而且知道爲何要去修改,不然不要隨意修改。

下面是兩個最重要的設置:

  • number_of_shards 
    每一個索引的主分片數,默認值是 5 。這個配置在索引建立後不能修改。

  • number_of_replicas 
    每一個主分片的副本數,默認值是 1 。對於活動的索引庫,這個配置能夠隨時修改。

例如,咱們能夠建立只有 一個主分片,沒有副本的小索引:

PUT /my_temp_index
{
    "settings": {
        "number_of_shards" :   1,
        "number_of_replicas" : 0
    }
}

而後,咱們能夠用 update-index-settings API 動態修改副本數:

PUT /my_temp_index/_settings
{
    "number_of_replicas": 1
}
  1. 配置分析器

第三個重要的索引設置是 analysis 部分, 用來配置已存在的分析器或針對你的索引建立新的自定義分析器。

在 分析與分析器 ,咱們介紹了一些內置的 分析器,用於將全文字符串轉換爲適合搜索的倒排索引。

standard 分析器是用於全文字段的默認分析器, 對於大部分西方語系來講是一個不錯的選擇。 它包括瞭如下幾點:

  • standard 分詞器,經過單詞邊界分割輸入的文本。
  • standard 語彙單元過濾器,目的是整理分詞器觸發的語彙單元(可是目前什麼都沒作)。
  • lowercase 語彙單元過濾器,轉換全部的語彙單元爲小寫。
  • stop 語彙單元過濾器,刪除停用詞–對搜索相關性影響不大的經常使用詞,如 a , the , and , is 。

默認狀況下,停用詞過濾器是被禁用的。如需啓用它,你能夠經過建立一個基於 standard 分析器的自定義分析器並設置 stopwords 參數。 能夠給分析器提供一個停用詞列表,或者告知使用一個基於特定語言的預約義停用詞列表。

在下面的例子中,咱們建立了一個新的分析器,叫作 es_std , 並使用預約義的 西班牙語停用詞列表:

PUT /spanish_docs
{
    "settings": {
        "analysis": {
            "analyzer": {
                "es_std": {
                    "type":      "standard",
                    "stopwords": "_spanish_"
                }
            }
        }
    }
}

es_std 分析器不是全局的–它僅僅存在於咱們定義的 spanish_docs 索引中。 爲了使用 analyze API來對它進行測試,咱們必須使用特定的索引名:

GET /spanish_docs/_analyze?analyzer=es_std
El veloz zorro marrón

簡化的結果顯示西班牙語停用詞 El 已被正確的移除:

{
  "tokens" : [
    { "token" :    "veloz",   "position" : 2 },
    { "token" :    "zorro",   "position" : 3 },
    { "token" :    "marrón",  "position" : 4 }
  ]
}
  1. 自定義分析器

雖然Elasticsearch帶有一些現成的分析器,然而在分析器上Elasticsearch真正的強大之處在於,你能夠經過在一個適合你的特定數據的設置之中組合字符過濾器、分詞器、詞彙單元過濾器來建立自定義的分析器。

在 分析與分析器 咱們說過,一個 分析器 就是在一個包裏面組合了三種函數的一個包裝器, 三種函數按照順序被執行:

  • 字符過濾器

    字符過濾器 用來 整理 一個還沒有被分詞的字符串。例如,若是咱們的文本是HTML格式的,它會包含像

    或者

    這樣的HTML標籤,這些標籤是咱們不想索引的。咱們能夠使用 html清除 字符過濾器 來移除掉全部的HTML標籤,而且像把 Á 轉換爲相對應的Unicode字符 Á 這樣,轉換HTML實體。一個分析器可能有0個或者多個字符過濾器。

     

  • 分詞器

    一個分析器 必須 有一個惟一的分詞器。 分詞器把字符串分解成單個詞條或者詞彙單元。 標準 分析器裏使用的 標準 分詞器 把一個字符串根據單詞邊界分解成單個詞條,而且移除掉大部分的標點符號,然而還有其餘不一樣行爲的分詞器存在。例如, 關鍵詞 分詞器 完整地輸出 接收到的一樣的字符串,並不作任何分詞。 空格 分詞器 只根據空格分割文本 。 正則 分詞器 根據匹配正則表達式來分割文本 。

  • 詞單元過濾器

    通過分詞,做爲結果的 詞單元流 會按照指定的順序經過指定的詞單元過濾器 。詞單元過濾器能夠修改、添加或者移除詞單元。咱們已經提到過 lowercase 和 stop 詞過濾器 ,可是在 Elasticsearch 裏面還有不少可供選擇的詞單元過濾器。 詞幹過濾器 把單詞 遏制 爲 詞幹。 ascii_folding 過濾器移除變音符,把一個像 「très」 這樣的詞轉換爲 「tres」 。 ngram 和 edge_ngram 詞單元過濾器 能夠產生 適合用於部分匹配或者自動補全的詞單元。

在 深刻搜索,咱們討論了在哪裏使用,以及怎樣使用分詞器和過濾器。可是首先,咱們須要解釋一下怎樣建立自定義的分析器。

建立一個自定義分析器

和咱們以前配置 es_std 分析器同樣,咱們能夠在 analysis 下的相應位置設置字符過濾器、分詞器和詞單元過濾器:

PUT /my_index
{
    "settings": {
        "analysis": {
            "char_filter": { ... custom character filters ... },
            "tokenizer":   { ...    custom tokenizers     ... },
            "filter":      { ...   custom token filters   ... },
            "analyzer":    { ...    custom analyzers      ... }
        }
    }
}

做爲示範,讓咱們一塊兒來建立一個自定義分析器吧,這個分析器能夠作到下面的這些事:

  1. 使用 html清除 字符過濾器移除HTML部分。
  2. 使用一個自定義的 映射 字符過濾器把 & 替換爲 」 and 」 :

    「char_filter」: { 
    「&_to_and」: { 
    「type」: 「mapping」, 
    「mappings」: [ 「&=> and 「] 

    }

  3. 使用 標準 分詞器分詞。

  4. 小寫詞條,使用 小寫 詞過濾器處理。
  5. 使用自定義 中止 詞過濾器移除自定義的中止詞列表中包含的詞:

    「filter」: { 
    「my_stopwords」: { 
    「type」: 「stop」, 
    「stopwords」: [ 「the」, 「a」 ] 

    }

咱們的分析器定義用咱們以前已經設置好的自定義過濾器組合了已經定義好的分詞器和過濾器:

"analyzer": {
    "my_analyzer": {
        "type":           "custom",
        "char_filter":  [ "html_strip", "&_to_and" ],
        "tokenizer":      "standard",
        "filter":       [ "lowercase", "my_stopwords" ]
    }
}

彙總起來,完整的 建立索引 請求 看起來應該像這樣:

PUT /my_index
{
    "settings": {
        "analysis": {
            "char_filter": {
                "&_to_and": {
                    "type":       "mapping",
                    "mappings": [ "&=> and "]
            }},
            "filter": {
                "my_stopwords": {
                    "type":       "stop",
                    "stopwords": [ "the", "a" ]
            }},
            "analyzer": {
                "my_analyzer": {
                    "type":         "custom",
                    "char_filter":  [ "html_strip", "&_to_and" ],
                    "tokenizer":    "standard",
                    "filter":       [ "lowercase", "my_stopwords" ]
            }}
}}}

索引被建立之後,使用 analyze API 來 測試這個新的分析器:

GET /my_index/_analyze?analyzer=my_analyzer
The quick & brown fox

下面的縮略結果展現出咱們的分析器正在正確地運行:

{
  "tokens" : [
      { "token" :   "quick",    "position" : 2 },
      { "token" :   "and",      "position" : 3 },
      { "token" :   "brown",    "position" : 4 },
      { "token" :   "fox",      "position" : 5 }
    ]
}

這個分析器如今是沒有多大用處的,除非咱們告訴 Elasticsearch在哪裏用上它。咱們能夠像下面這樣把這個分析器應用在一個 string 字段上:

PUT /my_index/_mapping/my_type
{
    "properties": {
        "title": {
            "type":      "string",
            "analyzer":  "my_analyzer"
        }
    }
}
  1. 類型和映射

類型 在 Elasticsearch 中表示一類類似的文檔。 類型由 名稱 —好比 user 或 blogpost —和 映射 組成。

映射, 就像數據庫中的 schema ,描述了文檔可能具備的字段或 屬性 、 每一個字段的數據類型—好比 string, integer 或 date —以及Lucene是如何索引和存儲這些字段的。

類型能夠很好的抽象劃分類似但不相同的數據。但因爲 Lucene 的處理方式,類型的使用有些限制。

Lucene 如何處理文檔

在 Lucene 中,一個文檔由一組簡單的鍵值對組成。 每一個字段均可以有多個值,但至少要有一個值。 相似的,一個字符串能夠經過分析過程轉化爲多個值。Lucene 不關心這些值是字符串、數字或日期–全部的值都被當作 不透明字節 。

當咱們在 Lucene 中索引一個文檔時,每一個字段的值都被添加到相關字段的倒排索引中。你也能夠將未處理的原始數據 存儲 起來,以便這些原始數據在以後也能夠被檢索到。

類型是如何實現的

Elasticsearch 類型是 以 Lucene 處理文檔的這個方式爲基礎來實現的。一個索引能夠有多個類型,這些類型的文檔能夠存儲在相同的索引中。

Lucene 沒有文檔類型的概念,每一個文檔的類型名被存儲在一個叫 _type 的元數據字段上。 當咱們要檢索某個類型的文檔時, Elasticsearch 經過在 _type 字段上使用過濾器限制只返回這個類型的文檔。

Lucene 也沒有映射的概念。 映射是 Elasticsearch 將複雜 JSON 文檔 映射 成 Lucene 須要的扁平化數據的方式。

例如,在 user 類型中, name 字段的映射能夠聲明這個字段是 string 類型,而且它的值被索引到名叫 name 的倒排索引以前,須要經過 whitespace 分詞器分析:

"name": {
    "type":     "string",
    "analyzer": "whitespace"
}

避免類型陷阱

這致使了一個有趣的思想實驗: 若是有兩個不一樣的類型,每一個類型都有同名的字段,但映射不一樣(例如:一個是字符串一個是數字),將會出現什麼狀況?

簡單回答是,Elasticsearch 不會容許你定義這個映射。當你配置這個映射時,將會出現異常。

詳細回答是,每一個 Lucene 索引中的全部字段都包含一個單一的、扁平的模式。一個特定字段能夠映射成 string 類型也能夠是 number 類型,可是不能二者兼具。由於類型是 Elasticsearch 添加的 優於 Lucene 的額外機制(以元數據 _type 字段的形式),在 Elasticsearch 中的全部類型最終都共享相同的映射。

以 data 索引中兩種類型的映射爲例:

{
   "data": {
      "mappings": {
         "people": {
            "properties": {
               "name": {
                  "type": "string",
               },
               "address": {
                  "type": "string"
               }
            }
         },
         "transactions": {
            "properties": {
               "timestamp": {
                  "type": "date",
                  "format": "strict_date_optional_time"
               },
               "message": {
                  "type": "string"
               }
            }
         }
      }
   }
}

每一個類型定義兩個字段 (分別是 「name」/」address」 和 「timestamp」/」message」 )。它們看起來是相互獨立的,但在後臺 Lucene 將建立一個映射,如:

{
   "data": {
      "mappings": {
        "_type": {
          "type": "string",
          "index": "not_analyzed"
        },
        "name": {
          "type": "string"
        }
        "address": {
          "type": "string"
        }
        "timestamp": {
          "type": "long"
        }
        "message": {
          "type": "string"
        }
      }
   }
}

注: 這不是真實有效的映射語法,只是用於演示

對於整個索引,映射在本質上被 扁平化 成一個單一的、全局的模式。這就是爲何兩個類型不能定義衝突的字段:當映射被扁平化時,Lucene 不知道如何去處理。

類型結論

那麼,這個討論的結論是什麼?技術上講,多個類型能夠在相同的索引中存在,只要它們的字段不衝突(要麼由於字段是互爲獨佔模式,要麼由於它們共享相同的字段)。

重要的一點是: 類型能夠很好的區分同一個集合中的不一樣細分。在不一樣的細分中數據的總體模式是相同的(或類似的)。

類型不適合 徹底不一樣類型的數據 。若是兩個類型的字段集是互不相同的,這就意味着索引中將有一半的數據是空的(字段將是 稀疏的 ),最終將致使性能問題。在這種狀況下,最好是使用兩個單獨的索引。

總結:

  • 正確: 將 kitchen 和 lawn-care 類型放在 products 索引中, 由於這兩種類型基本上是相同的模式
  • 錯誤: 將 products 和 logs 類型放在 data 索引中, 由於這兩種類型互不相同。應該將它們放在不一樣的索引中。

    1. 根對象

映射的最高一層被稱爲 根對象 ,它可能包含下面幾項:

  • 一個 properties 節點,列出了文檔中可能包含的每一個字段的映射
  • 各類元數據字段,它們都以一個下劃線開頭,例如 _type 、 _id 和 _source
  • 設置項,控制如何動態處理新的字段,例如 analyzer 、 dynamic_date_formats 和 dynamic_templates
  • 其餘設置,能夠同時應用在根對象和其餘 object 類型的字段上,例如 enabled 、 dynamic 和 include_in_all

屬性

咱們已經在 核心簡單域類型 和 複雜核心域類型 章節中介紹過文檔字段和屬性的三個 最重要的設置:

  • type 
    字段的數據類型,例如 string 或 date
  • index 
    字段是否應當被當成全文來搜索( analyzed ),或被當成一個準確的值( not_analyzed ),仍是徹底不可被搜索( no )
  • analyzer 
    肯定在索引和搜索時全文字段使用的 analyzer

咱們將在本書的後續部分討論其餘字段類型,例如 ip 、 geo_point 和 geo_shape 。

元數據: _source 字段

默認地,Elasticsearch 在 _source 字段存儲表明文檔體的JSON字符串。和全部被存儲的字段同樣, _source 字段在被寫入磁盤以前先會被壓縮。

這個字段的存儲幾乎老是咱們想要的,由於它意味着下面的這些:

  • 搜索結果包括了整個可用的文檔——不須要額外的從另外一個的數據倉庫來取文檔。
  • 若是沒有 _source 字段,部分 update 請求不會生效。
  • 當你的映射改變時,你須要從新索引你的數據,有了_source字段你能夠直接從Elasticsearch這樣作,而沒必要從另外一個(一般是速度更慢的)數據倉庫取回你的全部文檔。
  • 當你不須要看到整個文檔時,單個字段能夠從 _source 字段提取和經過 get 或者 search 請求返回。
  • 調試查詢語句更加簡單,由於你能夠直接看到每一個文檔包括什麼,而不是從一列id猜想它們的內容。

然而,存儲 _source 字段的確要使用磁盤空間。若是上面的緣由對你來講沒有一個是重要的,你能夠用下面的映射禁用 _source 字段:

PUT /my_index
{
    "mappings": {
        "my_type": {
            "_source": {
                "enabled":  false
            }
        }
    }
}

在一個搜索請求裏,你能夠經過在請求體中指定 _source 參數,來達到只獲取特定的字段的效果:

GET /_search
{
    "query":   { "match_all": {}},
    "_source": [ "title", "created" ]
}

這些字段的值會從 _source 字段被提取和返回,而不是返回整個 _source 。

Stored Fields 被存儲字段

爲了以後的檢索,除了索引一個字段的值,你 還能夠選擇 存儲 原始字段值。有 Lucene 使用背景的用戶使用被存儲字段來選擇他們想要在搜索結果裏面返回的字段。事實上, _source 字段就是一個被存儲的字段。

在Elasticsearch中,對文檔的個別字段設置存儲的作法一般不是最優的。整個文檔已經被存儲爲 _source 字段。使用 _source 參數提取你須要的字段老是更好的。

元數據: _all 字段

在 輕量 搜索 中,咱們介紹了 _all 字段:一個把其它字段值 看成一個大字符串來索引的特殊字段。 query_string 查詢子句(搜索 ?q=john )在沒有指定字段時默認使用 _all 字段。

_all 字段在新應用的探索階段,當你還不清楚文檔的最終結構時是比較有用的。你能夠使用這個字段來作任何查詢,而且有很大可能找到須要的文檔:

GET /_search
{
    "match": {
        "_all": "john smith marketing"
    }
}

隨着應用的發展,搜索需求變得更加明確,你會發現本身愈來愈少使用 _all 字段。 _all 字段是搜索的應急之策。經過查詢指定字段,你的查詢更加靈活、強大,你也能夠對相關性最高的搜索結果進行更細粒度的控制。

relevance algorithm 考慮的一個最重要的原則是字段的長度:字段越短越重要。 在較短的 title 字段中出現的短語可能比在較長的 content 字段中出現的短語更加劇要。字段長度的區別在 _all 字段中不會出現。

若是你再也不須要 _all 字段,你能夠經過下面的映射來禁用:

PUT /my_index/_mapping/my_type
{
    "my_type": {
        "_all": { "enabled": false }
    }
}

經過 include_in_all 設置來逐個控制字段是否要包含在 _all 字段中,默認值是 true。在一個對象(或根對象)上設置 include_in_all 能夠修改這個對象中的全部字段的默認行爲。

你可能想要保留 _all 字段做爲一個只包含某些特定字段的全文字段,例如只包含 title,overview,summary 和 tags。 相對於徹底禁用 _all 字段,你能夠爲全部字段默認禁用 include_in_all 選項,僅在你選擇的字段上啓用:

PUT /my_index/my_type/_mapping
{
    "my_type": {
        "include_in_all": false,
        "properties": {
            "title": {
                "type":           "string",
                "include_in_all": true
            },
            ...
        }
    }
}

記住,_all 字段僅僅是一個 通過分詞的 string 字段。它使用默認分詞器來分析它的值,無論這個值本來所在字段指定的分詞器。就像全部 string 字段,你能夠配置 _all 字段使用的分詞器:

PUT /my_index/my_type/_mapping
{
    "my_type": {
        "_all": { "analyzer": "whitespace" }
    }
}

元數據:文檔標識

文檔標識與四個元數據字段 相關:

  • _id 
    文檔的 ID 字符串
  • _type 
    文檔的類型名
  • _index 
    文檔所在的索引
  • _uid 
    _type 和 _id 鏈接在一塊兒構形成 type#id

默認狀況下, _uid 字段是被存儲(可取回)和索引(可搜索)的。 _type 字段被索引可是沒有存儲, _id 和 _index 字段則既沒有被索引也沒有被存儲,這意味着它們並非真實存在的。

儘管如此,你仍然能夠像真實字段同樣查詢 _id 字段。Elasticsearch 使用 _uid 字段來派生出 _id 。 雖然你能夠修改這些字段的 index 和 store 設置,可是基本上不須要這麼作。

  1. 動態映射

當 Elasticsearch 遇到文檔中之前 未遇到的字段,它用 dynamic mapping 來肯定字段的數據類型並自動把新的字段添加到類型映射。

有時這是想要的行爲有時又不但願這樣。一般沒有人知道之後會有什麼新字段加到文檔,可是又但願這些字段被自動的索引。也許你只想忽略它們。若是Elasticsearch是做爲重要的數據存儲,可能就會指望遇到新字段就會拋出異常,這樣能及時發現問題。

幸運的是能夠用 dynamic 配置來控制這種行爲 ,可接受的選項以下:

  • true 
    動態添加新的字段–缺省
  • false 
    忽略新的字段
  • strict 
    若是遇到新字段拋出異常

配置參數 dynamic 能夠用在根 object 或任何 object 類型的字段上。你能夠將 dynamic 的默認值設置爲 strict , 而只在指定的內部對象中開啓它, 例如:

PUT /my_index
{
    "mappings": {
        "my_type": {
            "dynamic":      "strict", 
            "properties": {
                "title":  { "type": "string"},
                "stash":  {
                    "type":     "object",
                    "dynamic":  true 
                }
            }
        }
    }
}


    若是遇到新字段,對象 my_type 就會拋出異常。 
    而內部對象 stash 遇到新字段就會動態建立新字段。

使用上述動態映射, 你能夠給 stash 對象添加新的可檢索的字段:

PUT /my_index/my_type/1
{
    "title":   "This doc adds a new field",
    "stash": { "new_field": "Success!" }
}

可是對根節點對象 my_type 進行一樣的操做會失敗:

PUT /my_index/my_type/1
{
    "title":     "This throws a StrictDynamicMappingException",
    "new_field": "Fail!"
}

把 dynamic 設置爲 false 一點兒也不會改變 _source 的字段內容。 _source 仍然包含被索引的整個JSON文檔。只是新的字段不會被加到映射中也不可搜索。

  1. 自定義動態映射

若是你想在運行時增長新的字段,你可能會啓用動態映射。 然而,有時候,動態映射 規則 可能不太智能。幸運的是,咱們能夠經過設置去自定義這些規則,以便更好的適用於你的數據。

日期檢測

當 Elasticsearch 遇到一個新的字符串字段時,它會檢測這個字段是否包含一個可識別的日期,好比 2014-01-01 。 若是它像日期,這個字段就會被做爲 date 類型添加。不然,它會被做爲 string 類型添加。

有些時候這個行爲可能致使一些問題。想象下,你有以下這樣的一個文檔:

{ "note": "2014-01-01" }

假設這是第一次識別 note 字段,它會被添加爲 date 字段。可是若是下一個文檔像這樣:

{ "note": "Logged out" }

這顯然不是一個日期,但爲時已晚。這個字段已是一個日期類型,這個 不合法的日期 將會形成一個異常。

日期檢測能夠經過在根對象上設置 date_detection 爲 false 來關閉:

PUT /my_index
{
    "mappings": {
        "my_type": {
            "date_detection": false
        }
    }
}

使用這個映射,字符串將始終做爲 string 類型。若是你須要一個 date 字段,你必須手動添加。

Elasticsearch 判斷字符串爲日期的規則能夠經過 dynamic_date_formats setting 來設置。

動態模板

使用 dynamic_templates ,你能夠徹底控制 新檢測生成字段的映射。你甚至能夠經過字段名稱或數據類型來應用不一樣的映射。

每一個模板都有一個名稱, 你能夠用來描述這個模板的用途, 一個 mapping 來指定映射應該怎樣使用,以及至少一個參數 (如 match) 來定義這個模板適用於哪一個字段。

模板按照順序來檢測;第一個匹配的模板會被啓用。例如,咱們給 string 類型字段定義兩個模板:

  • es :以 _es 結尾的字段名須要使用 spanish 分詞器。
  • en :全部其餘字段使用 english 分詞器。

咱們將 es 模板放在第一位,由於它比匹配全部字符串字段的 en 模板更特殊:

PUT /my_index
{
    "mappings": {
        "my_type": {
            "dynamic_templates": [
                { "es": {
                      "match":              "*_es", 
                      "match_mapping_type": "string",
                      "mapping": {
                          "type":           "string",
                          "analyzer":       "spanish"
                      }
                }},
                { "en": {
                      "match":              "*", 
                      "match_mapping_type": "string",
                      "mapping": {
                          "type":           "string",
                          "analyzer":       "english"
                      }
                }}
            ]
}}}


    匹配字段名以 _es 結尾的字段。
    匹配其餘全部字符串類型字段。

match_mapping_type 容許你應用模板到特定類型的字段上,就像有標準動態映射規則檢測的同樣, (例如 string 或 long)。

match 參數只匹配字段名稱, path_match 參數匹配字段在對象上的完整路徑,因此 address.*.name 將匹配這樣的字段:

{
    "address": {
        "city": {
            "name": "New York"
        }
    }
}

unmatch 和 path_unmatch將被用於未被匹配的字段。

  1. 缺省映射

一般,一個索引中的全部類型共享相同的字段和設置。 default 映射更加方便地指定通用設置,而不是每次建立新類型時都要重複設置。 default 映射是新類型的模板。在設置 default 映射以後建立的全部類型都將應用這些缺省的設置,除非類型在本身的映射中明確覆蓋這些設置。

例如,咱們能夠使用 default 映射爲全部的類型禁用 _all 字段, 而只在 blog 類型啓用:

PUT /my_index
{
    "mappings": {
        "_default_": {
            "_all": { "enabled":  false }
        },
        "blog": {
            "_all": { "enabled":  true  }
        }
    }
}

default 映射也是一個指定索引 dynamic templates 的好方法。

  1. 從新索引你的數據

儘管能夠增長新的類型到索引中,或者增長新的字段到類型中,可是不能添加新的分析器或者對現有的字段作改動。 若是你那麼作的話,結果就是那些已經被索引的數據就不正確, 搜索也不能正常工做。

對現有數據的這類改變最簡單的辦法就是從新索引:用新的設置建立新的索引並把文檔從舊的索引複製到新的索引。

字段 _source 的一個優勢是在Elasticsearch中已經有整個文檔。你沒必要從源數據中重建索引,並且那樣一般比較慢。

爲了有效的從新索引全部在舊的索引中的文檔,用 scroll 從舊的索引檢索批量文檔 , 而後用 bulk API 把文檔推送到新的索引中。

從Elasticsearch v2.3.0開始, Reindex API 被引入。它可以對文檔重建索引而不須要任何插件或外部工具。

批量從新索引

同時並行運行多個重建索引任務,可是你顯然不但願結果有重疊。正確的作法是按日期或者時間 這樣的字段做爲過濾條件把大的重建索引分紅小的任務:

GET /old_index/_search?scroll=1m
{
    "query": {
        "range": {
            "date": {
                "gte":  "2014-01-01",
                "lt":   "2014-02-01"
            }
        }
    },
    "sort": ["_doc"],
    "size":  1000
}

若是舊的索引持續會有變化,你但願新的索引中也包括那些新加的文檔。那就能夠對新加的文檔作從新索引, 但仍是要用日期類字段過濾來匹配那些新加的文檔。

  1. 索引別名和零停機

在前面提到的,重建索引的問題是必須更新應用中的索引名稱。 索引別名就是用來解決這個問題的!

索引 別名 就像一個快捷方式或軟鏈接,能夠指向一個或多個索引,也能夠給任何一個須要索引名的API來使用。別名 帶給咱們極大的靈活性,容許咱們作下面這些:

  • 在運行的集羣中能夠無縫的從一個索引切換到另外一個索引
  • 給多個索引分組 (例如, last_three_months)
  • 給索引的一個子集建立 視圖

在後面咱們會討論更多關於別名的使用。如今,咱們將解釋怎樣使用別名在零停機下從舊索引切換到新索引。

有兩種方式管理別名: _alias 用於單個操做, _aliases 用於執行多個原子級操做。

在本章中,咱們假設你的應用有一個叫 my_index 的索引。事實上, my_index 是一個指向當前真實索引的別名。真實索引包含一個版本號: my_index_v1 , my_index_v2 等等。

首先,建立索引 my_index_v1 ,而後將別名 my_index 指向它:

PUT /my_index_v1 
PUT /my_index_v1/_alias/my_index 


    建立索引 my_index_v1 。            
    設置別名 my_index 指向 my_index_v1 。

你能夠檢測這個別名指向哪個索引:

GET /*/_alias/my_index

或哪些別名指向這個索引:

GET /my_index_v1/_alias/*

二者都會返回下面的結果:

{
    "my_index_v1" : {
        "aliases" : {
            "my_index" : { }
        }
    }
}

而後,咱們決定修改索引中一個字段的映射。固然,咱們不能修改現存的映射,因此咱們必須從新索引數據。 首先, 咱們用新映射建立索引 my_index_v2 :

PUT /my_index_v2
{
    "mappings": {
        "my_type": {
            "properties": {
                "tags": {
                    "type":   "string",
                    "index":  "not_analyzed"
                }
            }
        }
    }
}

而後咱們將數據從 my_index_v1 索引到 my_index_v2 ,下面的過程在 從新索引你的數據 中已經描述過。一旦咱們肯定文檔已經被正確地重索引了,咱們就將別名指向新的索引。

一個別名能夠指向多個索引,因此咱們在添加別名到新索引的同時必須從舊的索引中刪除它。這個操做須要原子化,這意味着咱們須要使用 _aliases 操做:

POST /_aliases
{
    "actions": [
        { "remove": { "index": "my_index_v1", "alias": "my_index" }},
        { "add":    { "index": "my_index_v2", "alias": "my_index" }}
    ]
}

你的應用已經在零停機的狀況下從舊索引遷移到新索引了。

即便你認爲如今的索引設計已經很完美了,在生產環境中,仍是有可能須要作一些修改的。

作好準備:在你的應用中使用別名而不是索引名。而後你就能夠在任什麼時候候重建索引。別名的開銷很小,應該普遍使用。

11、分片內部原理

在 集羣內的原理, 咱們介紹了 分片, 並將它 描述成最小的 工做單元。可是究竟什麼 是 一個分片,它是如何工做的? 在這個章節,咱們回答如下問題:

  • 爲何搜索是 近 實時的?
  • 爲何文檔的 CRUD (建立-讀取-更新-刪除) 操做是 實時 的?
  • Elasticsearch 是怎樣保證更新被持久化在斷電時也不丟失數據?
  • 爲何刪除文檔不會馬上釋放空間?
  • refresh, flush, 和 optimize API 都作了什麼, 你什麼狀況下應該是用他們?

最簡單的理解一個分片如何工做的方式是上一堂歷史課。 咱們將要審視提供一個帶近實時搜索和分析的 分佈式持久化數據存儲須要解決的問題。

內容警告

本章展現的這些信息僅供您興趣閱讀。爲了使用 Elasticsearch 您並不須要理解和記憶全部的細節。 讀這個章節是爲了瞭解工做機制,而且爲了未來您須要這些信息時,知道這些信息在哪裏。可是不要被這些細節所累。

  1. 使文本可被搜索

必須解決的第一個挑戰是如何 使文本可被搜索。 傳統的數據庫每一個字段存儲單個值,但這對全文檢索並不夠。文本字段中的每一個單詞須要被搜索,對數據庫意味着須要單個字段有索引多值(這裏指單詞)的能力。

最好的支持 一個字段多個值 需求的數據結構是咱們在 倒排索引 章節中介紹過的 倒排索引 。 倒排索引包含一個有序列表,列表包含全部文檔出現過的不重複個體,或稱爲 詞項 ,對於每個詞項,包含了它全部曾出現過文檔的列表。

Term  | Doc 1 | Doc 2 | Doc 3 | ...
------------------------------------
brown |   X   |       |  X    | ...
fox   |   X   |   X   |  X    | ...
quick |   X   |   X   |       | ...
the   |   X   |       |  X    | ...

當討論倒排索引時,咱們會談到 文檔 標引,由於歷史緣由,倒排索引被用來對整個非結構化文本文檔進行標引。 Elasticsearch 中的 文檔 是有字段和值的結構化 JSON 文檔。事實上,在 JSON 文檔中, 每一個被索引的字段都有本身的倒排索引。

這個倒排索引相比特定詞項出現過的文檔列表,會包含更多其它信息。它會保存每個詞項出現過的文檔總數, 在對應的文檔中一個具體詞項出現的總次數,詞項在文檔中的順序,每一個文檔的長度,全部文檔的平均長度,等等。這些統計信息容許 Elasticsearch 決定哪些詞比其它詞更重要,哪些文檔比其它文檔更重要,這些內容在 什麼是相關性? 中有描述。

爲了可以實現預期功能,倒排索引須要知道集合中的 全部 文檔,這是須要認識到的關鍵問題。

早期的全文檢索會爲整個文檔集合創建一個很大的倒排索引並將其寫入到磁盤。 一旦新的索引就緒,舊的就會被其替換,這樣最近的變化即可以被檢索到。

不變性

倒排索引被寫入磁盤後是 不可改變 的:它永遠不會修改。 不變性有重要的價值:

  • 不須要鎖。若是你歷來不更新索引,你就不須要擔憂多進程同時修改數據的問題。
  • 一旦索引被讀入內核的文件系統緩存,便會留在哪裏,因爲其不變性。只要文件系統緩存中還有足夠的空間,那麼大部分讀請求會直接請求內存,而不會命中磁盤。這提供了很大的性能提高。
  • 其它緩存(像filter緩存),在索引的生命週期內始終有效。它們不須要在每次數據改變時被重建,由於數據不會變化。
  • 寫入單個大的倒排索引容許數據被壓縮,減小磁盤 I/O 和 須要被緩存到內存的索引的使用量。

固然,一個不變的索引也有很差的地方。主要事實是它是不可變的! 你不能修改它。若是你須要讓一個新的文檔 可被搜索,你須要重建整個索引。這要麼對一個索引所能包含的數據量形成了很大的限制,要麼對索引可被更新的頻率形成了很大的限制。

  1. 動態更新索引

下一個須要被解決的問題是怎樣在保留不變性的前提下實現倒排索引的更新? 答案是: 用更多的索引。

經過增長新的補充索引來反映新近的修改,而不是直接重寫整個倒排索引。每個倒排索引都會被輪流查詢到–從最先的開始–查詢完後再對結果進行合併。

Elasticsearch 基於 Lucene, 這個 java 庫引入了 按段搜索 的概念。 每一 段 自己都是一個倒排索引, 但 索引 在 Lucene 中除表示全部 段 的集合外, 還增長了 提交點 的概念 — 一個列出了全部已知段的文件,就像在 圖 16 「一個 Lucene 索引包含一個提交點和三個段」 中描繪的那樣。 如 圖 17 「一個在內存緩存中包含新文檔的 Lucene 索引」 所示,新的文檔首先被添加到內存索引緩存中,而後寫入到一個基於磁盤的段,如 圖 18 「在一次提交後,一個新的段被添加到提交點並且緩存被清空。」 所示。

圖 16. 一個 Lucene 索引包含一個提交點和三個段

索引與分片的比較

被混淆的概念是,一個 Lucene 索引 咱們在 Elasticsearch 稱做 分片 。 一個 Elasticsearch 索引 是分片的集合。 當 Elasticsearch 在索引中搜索的時候, 他發送查詢到每個屬於索引的分片(Lucene 索引),而後像 執行分佈式檢索 提到的那樣,合併每一個分片的結果到一個全局的結果集。

逐段搜索會以以下流程進行工做:

  1. 新文檔被收集到內存索引緩存, 見 圖 17 「一個在內存緩存中包含新文檔的 Lucene 索引」 。
  2. 不時地, 緩存被 提交 :

    • 一個新的段–一個追加的倒排索引–被寫入磁盤。
    • 一個新的包含新段名字的 提交點 被寫入磁盤。
    • 磁盤進行 同步 — 全部在文件系統緩存中等待的寫入都刷新到磁盤,以確保它們被寫入物理文件。
  3. 新的段被開啓,讓它包含的文檔可見以被搜索。

  4. 內存緩存被清空,等待接收新的文檔。

圖 17. 一個在內存緩存中包含新文檔的 Lucene 索引

圖 18. 在一次提交後,一個新的段被添加到提交點並且緩存被清空。

當一個查詢被觸發,全部已知的段按順序被查詢。詞項統計會對全部段的結果進行聚合,以保證每一個詞和每一個文檔的關聯都被準確計算。 這種方式能夠用相對較低的成本將新文檔添加到索引。

刪除和更新

段是不可改變的,因此既不能從把文檔從舊的段中移除,也不能修改舊的段來進行反映文檔的更新。 取而代之的是,每一個提交點會包含一個 .del 文件,文件中會列出這些被刪除文檔的段信息。

當一個文檔被 「刪除」 時,它實際上只是在 .del 文件中被 標記 刪除。一個被標記刪除的文檔仍然能夠被查詢匹配到, 但它會在最終結果被返回前從結果集中移除。

文檔更新也是相似的操做方式:當一個文檔被更新時,舊版本文檔被標記刪除,文檔的新版本被索引到一個新的段中。 可能兩個版本的文檔都會被一個查詢匹配到,但被刪除的那個舊版本文檔在結果集返回前就已經被移除。

在 段合併 , 咱們展現了一個被刪除的文檔是怎樣被文件系統移除的。

  1. 近實時搜索

隨着按段(per-segment)搜索的發展, 一個新的文檔從索引到可被搜索的延遲顯著下降了。新文檔在幾分鐘以內便可被檢索,但這樣仍是不夠快。

磁盤在這裏成爲了瓶頸。 提交(Commiting)一個新的段到磁盤須要一個 fsync 來確保段被物理性地寫入磁盤,這樣在斷電的時候就不會丟失數據。 可是 fsync 操做代價很大; 若是每次索引一個文檔都去執行一次的話會形成很大的性能問題。

咱們須要的是一個更輕量的方式來使一個文檔可被搜索,這意味着 fsync 要從整個過程當中被移除。

在Elasticsearch和磁盤之間是文件系統緩存。 像以前描述的同樣, 在內存索引緩衝區( 圖 19 「在內存緩衝區中包含了新文檔的 Lucene 索引」 )中的文檔會被寫入到一個新的段中( 圖 20 「緩衝區的內容已經被寫入一個可被搜索的段中,但尚未進行提交」 )。 可是這裏新段會被先寫入到文件系統緩存–這一步代價會比較低,稍後再被刷新到磁盤–這一步代價比較高。不過只要文件已經在緩存中, 就能夠像其它文件同樣被打開和讀取了。

圖 19. 在內存緩衝區中包含了新文檔的 Lucene 索引

Lucene 容許新段被寫入和打開–使其包含的文檔在未進行一次完整提交時便對搜索可見。 這種方式比進行一次提交代價要小得多,而且在不影響性能的前提下能夠被頻繁地執行。

圖 20. 緩衝區的內容已經被寫入一個可被搜索的段中,但尚未進行提交

refresh API

在 Elasticsearch 中,寫入和打開一個新段的輕量的過程叫作 refresh 。 默認狀況下每一個分片會每秒自動刷新一次。這就是爲何咱們說 Elasticsearch 是 近 實時搜索: 文檔的變化並非當即對搜索可見,但會在一秒以內變爲可見。

這些行爲可能會對新用戶形成困惑: 他們索引了一個文檔而後嘗試搜索它,但卻沒有搜到。這個問題的解決辦法是用 refresh API 執行一次手動刷新:

POST /_refresh 
POST /blogs/_refresh 


    刷新(Refresh)全部的索引。     
    只刷新(Refresh) blogs 索引。

儘管刷新是比提交輕量不少的操做,它仍是會有性能開銷。 當寫測試的時候, 手動刷新頗有用,可是不要在生產環境下每次索引一個文檔都去手動刷新。 相反,你的應用須要意識到 Elasticsearch 的近實時的性質,並接受它的不足。

並非全部的狀況都須要每秒刷新。可能你正在使用 Elasticsearch 索引大量的日誌文件, 你可能想優化索引速度而不是近實時搜索, 能夠經過設置 refresh_interval , 下降每一個索引的刷新頻率:

PUT /my_logs
{
  "settings": {
    "refresh_interval": "30s" 
  }
}


    每30秒刷新 my_logs 索引。

refresh_interval 能夠在既存索引上進行動態更新。 在生產環境中,當你正在創建一個大的新索引時,能夠先關閉自動刷新,待開始使用該索引時,再把它們調回來:

PUT /my_logs/_settings
{ "refresh_interval": -1 } 

PUT /my_logs/_settings
{ "refresh_interval": "1s" } 


    關閉自動刷新。
    每秒自動刷新。

refresh_interval 須要一個 持續時間 值, 例如 1s (1 秒) 或 2m (2 分鐘)。 一個絕對值 1 表示的是 1毫秒 –無疑會使你的集羣陷入癱瘓。

  1. 持久化變動

若是沒有用 fsync 把數據從文件系統緩存刷(flush)到硬盤,咱們不能保證數據在斷電甚至是程序正常退出以後依然存在。爲了保證 Elasticsearch 的可靠性,須要確保數據變化被持久化到磁盤。

在 動態更新索引,咱們說一次完整的提交會將段刷到磁盤,並寫入一個包含全部段列表的提交點。Elasticsearch 在啓動或從新打開一個索引的過程當中使用這個提交點來判斷哪些段隸屬於當前分片。

即便經過每秒刷新(refresh)實現了近實時搜索,咱們仍然須要常常進行完整提交來確保能從失敗中恢復。但在兩次提交之間發生變化的文檔怎麼辦?咱們也不但願丟失掉這些數據。

Elasticsearch 增長了一個 translog ,或者叫事務日誌,在每一次對 Elasticsearch 進行操做時均進行了日誌記錄。經過 translog ,整個流程看起來是下面這樣:

  1. 一個文檔被索引以後,就會被添加到內存緩衝區,而且 追加到了 translog ,正如 圖 21 「新的文檔被添加到內存緩衝區而且被追加到了事務日誌」 描述的同樣。

    圖 21. 新的文檔被添加到內存緩衝區而且被追加到了事務日誌

  2. 刷新(refresh)使分片處於 圖 22 「刷新(refresh)完成後, 緩存被清空可是事務日誌不會」 描述的狀態,分片每秒被刷新(refresh)一次:

    • 這些在內存緩衝區的文檔被寫入到一個新的段中,且沒有進行 fsync 操做。
    • 這個段被打開,使其可被搜索。
    • 內存緩衝區被清空。

    圖 22. 刷新(refresh)完成後, 緩存被清空可是事務日誌不會

  3. 這個進程繼續工做,更多的文檔被添加到內存緩衝區和追加到事務日誌(見 圖 23 「事務日誌不斷積累文檔」 )。

    圖 23. 事務日誌不斷積累文檔

  4. 每隔一段時間–例如 translog 變得愈來愈大–索引被刷新(flush);一個新的 translog 被建立,而且一個全量提交被執行(見 圖 24 「在刷新(flush)以後,段被全量提交,而且事務日誌被清空」 ):

    • 全部在內存緩衝區的文檔都被寫入一個新的段。
    • 緩衝區被清空。
    • 一個提交點被寫入硬盤。
    • 文件系統緩存經過 fsync 被刷新(flush)。
    • 老的 translog 被刪除。

translog 提供全部尚未被刷到磁盤的操做的一個持久化紀錄。當 Elasticsearch 啓動的時候, 它會從磁盤中使用最後一個提交點去恢復已知的段,而且會重放 translog 中全部在最後一次提交後發生的變動操做。

translog 也被用來提供實時 CRUD 。當你試着經過ID查詢、更新、刪除一個文檔,它會在嘗試從相應的段中檢索以前, 首先檢查 translog 任何最近的變動。這意味着它老是可以實時地獲取到文檔的最新版本。

圖 24. 在刷新(flush)以後,段被全量提交,而且事務日誌被清空

flush API

這個執行一個提交而且截斷 translog 的行爲在 Elasticsearch 被稱做一次 flush 。 分片每30分鐘被自動刷新(flush),或者在 translog 太大的時候也會刷新。請查看 translog 文檔 來設置,它能夠用來 控制這些閾值:

flush API 能夠 被用來執行一個手工的刷新(flush):

POST /blogs/_flush 

POST /_flush?wait_for_ongoing 


    刷新(flush) blogs 索引。            
    刷新(flush)全部的索引而且而且等待全部刷新在返回前完成。

你不多須要本身手動執行一個的 flush 操做;一般狀況下,自動刷新就足夠了。

這就是說,在重啓節點或關閉索引以前執行 flush 有益於你的索引。當 Elasticsearch 嘗試恢復或從新打開一個索引, 它須要重放 translog 中全部的操做,因此若是日誌越短,恢復越快。

Translog 有多安全?

translog 的目的是保證操做不會丟失。這引出了這個問題: Translog 有多安全 ?

在文件被 fsync 到磁盤前,被寫入的文件在重啓以後就會丟失。默認 translog 是每 5 秒被 fsync 刷新到硬盤, 或者在每次寫請求完成以後執行(e.g. index, delete, update, bulk)。這個過程在主分片和複製分片都會發生。最終, 基本上,這意味着在整個請求被 fsync 到主分片和複製分片的translog以前,你的客戶端不會獲得一個 200 OK 響應。

在每次請求後都執行一個 fsync 會帶來一些性能損失,儘管實踐代表這種損失相對較小(特別是bulk導入,它在一次請求中平攤了大量文檔的開銷)。

可是對於一些大容量的偶爾丟失幾秒數據問題也並不嚴重的集羣,使用異步的 fsync 仍是比較有益的。好比,寫入的數據被緩存到內存中,再每5秒執行一次 fsync 。

這個行爲能夠經過設置 durability 參數爲 async 來啓用:

PUT /my_index/_settings
{
    "index.translog.durability": "async",
    "index.translog.sync_interval": "5s"
}

這個選項能夠針對索引單獨設置,而且能夠動態進行修改。若是你決定使用異步 translog 的話,你須要 保證 在發生crash時,丟失掉 sync_interval 時間段的數據也無所謂。請在決定前知曉這個特性。

若是你不肯定這個行爲的後果,最好是使用默認的參數( 「index.translog.durability」: 「request」 )來避免數據丟失。

  1. 段合併

因爲自動刷新流程每秒會建立一個新的段 ,這樣會致使短期內的段數量暴增。而段數目太多會帶來較大的麻煩。 每個段都會消耗文件句柄、內存和cpu運行週期。更重要的是,每一個搜索請求都必須輪流檢查每一個段;因此段越多,搜索也就越慢。

Elasticsearch經過在後臺進行段合併來解決這個問題。小的段被合併到大的段,而後這些大的段再被合併到更大的段。

段合併的時候會將那些舊的已刪除文檔 從文件系統中清除。 被刪除的文檔(或被更新文檔的舊版本)不會被拷貝到新的大段中。

啓動段合併不須要你作任何事。進行索引和搜索時會自動進行。這個流程像在 圖 25 「兩個提交了的段和一個未提交的段正在被合併到一個更大的段」 中提到的同樣工做:

一、 當索引的時候,刷新(refresh)操做會建立新的段並將段打開以供搜索使用。

二、 合併進程選擇一小部分大小類似的段,而且在後臺將它們合併到更大的段中。這並不會中斷索引和搜索。

圖 25. 兩個提交了的段和一個未提交的段正在被合併到一個更大的段

三、 圖 26 「一旦合併結束,老的段被刪除」 說明合併完成時的活動:

  • 新的段被刷新(flush)到了磁盤。 ** 寫入一個包含新段且排除舊的和較小的段的新提交點。
  • 新的段被打開用來搜索。
  • 老的段被刪除。

圖 26. 一旦合併結束,老的段被刪除

合併大的段須要消耗大量的I/O和CPU資源,若是任其發展會影響搜索性能。Elasticsearch在默認狀況下會對合並流程進行資源限制,因此搜索仍然 有足夠的資源很好地執行。

查看 段和合並 來爲你的實例獲取關於合併調整的建議。

optimize API

optimize API大可看作是 強制合併 API 。它會將一個分片強制合併到 max_num_segments 參數指定大小的段數目。 這樣作的意圖是減小段的數量(一般減小到一個),來提高搜索性能。

optimize API 不該該 被用在一個動態索引————一個正在被活躍更新的索引。後臺合併流程已經能夠很好地完成工做。 optimizing 會阻礙這個進程。不要干擾它!

在特定狀況下,使用 optimize API 很有益處。例如在日誌這種用例下,天天、每週、每個月的日誌被存儲在一個索引中。 老的索引實質上是隻讀的;它們也並不太可能會發生變化。

在這種狀況下,使用optimize優化老的索引,將每個分片合併爲一個單獨的段就頗有用了;這樣既能夠節省資源,也能夠使搜索更加快速:

POST /logstash-2014-10/_optimize?max_num_segments=1 


    合併索引中的每一個分片爲一個單獨的段

請注意,使用 optimize API 觸發段合併的操做一點也不會受到任何資源上的限制。這可能會消耗掉你節點上所有的I/O資源, 使其沒有餘裕來處理搜索請求,從而有可能使集羣失去響應。 若是你想要對索引執行 optimize,你須要先使用分片分配(查看 遷移舊索引)把索引移到一個安全的節點,再執行。

相關文章
相關標籤/搜索