Elasticsearch基礎教程

基礎概念html

Elasticsearch有幾個核心概念。從一開始理解這些概念會對整個學習過程有莫大的幫助。

接近實時(NRT)
    Elasticsearch是一個接近實時的搜索平臺。這意味着,從索引一個文檔直到這個文檔可以被搜索到有一個輕微的延遲(一般是1秒)。

集羣(cluster)
    一個集羣就是由一個或多個節點組織在一塊兒,它們共同持有你整個的數據,並一塊兒提供索引和搜索功能。一個集羣由一個惟一的名字標識,這個名字默認就是「elasticsearch」。這個名字是重要的,由於一個節點只能經過指定某個集羣的名字,來加入這個集羣。在產品環境中顯式地設定這個名字是一個好習慣,可是使用默認值來進行測試/開發也是不錯的。

節點(node)
    一個節點是你集羣中的一個服務器,做爲集羣的一部分,它存儲你的數據,參與集羣的索引和搜索功能。和集羣相似,一個節點也是由一個名字來標識的,默認狀況下,這個名字是一個隨機的漫威漫畫角色的名字,這個名字會在啓動的時候賦予節點。這個名字對於管理工做來講挺重要的,由於在這個管理過程當中,你會去肯定網絡中的哪些服務器對應於Elasticsearch集羣中的哪些節點。

    一個節點能夠經過配置集羣名稱的方式來加入一個指定的集羣。默認狀況下,每一個節點都會被安排加入到一個叫作「elasticsearch」的集羣中,這意味着,若是你在你的網絡中啓動了若干個節點,並假定它們可以相互發現彼此,它們將會自動地造成並加入到一個叫作「elasticsearch」的集羣中。

    在一個集羣裏,只要你想,能夠擁有任意多個節點。並且,若是當前你的網絡中沒有運行任何Elasticsearch節點,這時啓動一個節點,會默認建立並加入一個叫作「elasticsearch」的集羣。

索引(index)

    一個索引就是一個擁有幾分類似特徵的文檔的集合。好比說,你能夠有一個客戶數據的索引,另外一個產品目錄的索引,還有一個訂單數據的索引。一個索引由一個名字來標識(必須所有是小寫字母的),而且當咱們要對對應於這個索引中的文檔進行索引、搜索、更新和刪除的時候,都要使用到這個名字。

    在一個集羣中,若是你想,能夠定義任意多的索引。

類型(type)

    在一個索引中,你能夠定義一種或多種類型。一個類型是你的索引的一個邏輯上的分類/分區,其語義徹底由你來定。一般,會爲具備一組共同字段的文檔定義一個類型。好比說,咱們假設你運營一個博客平臺而且將你全部的數據存儲到一個索引中。在這個索引中,你能夠爲用戶數據定義一個類型,爲博客數據定義另外一個類型,固然,也能夠爲評論數據定義另外一個類型。

文檔(document)

    一個文檔是一個可被索引的基礎信息單元。好比,你能夠擁有某一個客戶的文檔,某一個產品的一個文檔,固然,也能夠擁有某個訂單的一個文檔。文檔以JSON(Javascript Object Notation)格式來表示,而JSON是一個處處存在的互聯網數據交互格式。

    在一個index/type裏面,只要你想,你能夠存儲任意多的文檔。注意,儘管一個文檔,物理上存在於一個索引之中,文檔必須被索引/賦予一個索引的type。

分片和複製(shards & replicas)

    一個索引能夠存儲超出單個結點硬件限制的大量數據。好比,一個具備10億文檔的索引佔據1TB的磁盤空間,而任一節點都沒有這樣大的磁盤空間;或者單個節點處理搜索請求,響應太慢。

    爲了解決這個問題,Elasticsearch提供了將索引劃分紅多份的能力,這些份就叫作分片。當你建立一個索引的時候,你能夠指定你想要的分片的數量。每一個分片自己也是一個功能完善而且獨立的「索引」,這個「索引」能夠被放置到集羣中的任何節點上。

    分片之因此重要,主要有兩方面的緣由:

        - 容許你水平分割/擴展你的內容容量
        - 容許你在分片(潛在地,位於多個節點上)之上進行分佈式的、並行的操做,進而提升性能/吞吐量

    至於一個分片怎樣分佈,它的文檔怎樣聚合回搜索請求,是徹底由Elasticsearch管理的,對於做爲用戶的你來講,這些都是透明的。

    在一個網絡/雲的環境裏,失敗隨時均可能發生,在某個分片/節點不知怎麼的就處於離線狀態,或者因爲任何緣由消失了,這種狀況下,有一個故障轉移機制是很是有用而且是強烈推薦的。爲此目的,Elasticsearch容許你建立分片的一份或多份拷貝,這些拷貝叫作複製分片,或者直接叫複製。

    複製之因此重要,有兩個主要緣由:
        - 在分片/節點失敗的狀況下,提供了高可用性。由於這個緣由,注意到複製分片從不與原/主要(original/primary)分片置於同一節點上是很是重要的。
        - 擴展你的搜索量/吞吐量,由於搜索能夠在全部的複製上並行運行

    總之,每一個索引能夠被分紅多個分片。一個索引也能夠被複制0次(意思是沒有複製)或屢次。一旦複製了,每一個索引就有了主分片(做爲複製源的原來的分片)和複製分片(主分片的拷貝)之別。分片和複製的數量能夠在索引建立的時候指定。在索引建立以後,你能夠在任什麼時候候動態地改變複製的數量,可是你過後不能改變分片的數量。

    默認狀況下,Elasticsearch中的每一個索引被分片5個主分片和1個複製,這意味着,若是你的集羣中至少有兩個節點,你的索引將會有5個主分片和另外5個複製分片(1個徹底拷貝),這樣的話每一個索引總共就有10個分片。

    這些問題搞清楚以後,咱們就要進入好玩的部分了...

安裝java

Elasticsearch依賴Java 7。在本文寫做的時候,推薦使用Oracle JDK 1.7.0_55版本。Java的安裝,在各個平臺上都有差別,因此咱們不想在這裏深刻太多細節。我只想說,在你安裝Elasticsearch以前,你能夠經過如下命令來檢查你的Java版本(若是有須要,安裝或者升級):

    java -version
    echo $JAVA_HOME

一旦咱們將Java安裝完成,咱們就能夠下載並安裝Elasticsearch了。其二進制文件能夠從www.elasticsearch.org/download這裏下載,你也能夠從這裏下載之前發佈的版本。對於每一個版本,你能夠在zip、tar、DEB、RPM類型的包中選擇下載。簡單起見,咱們使用tar包。

咱們像下面同樣下載Elasticsearch 1.1.1 tar包(Windows用戶應該下載zip包):

    curl -L -O https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.1.1.tar.gz

而後,以下將其解壓(Windows下須要unzip響應的zip包):

    tar -xzvf elasticsearch-1.1.1.tar.gz

這將在你的當前目錄下建立不少文件和目錄。而後,咱們進入到bin目錄下:

    cd elasticsearch-1.1.1/bin

至此,咱們已經準備好開啓咱們的節點和單節點集羣(Windows用戶應該運行elasticsearch.bat文件):

    ./elasticsearch

若是一切順利,你將看到大量的以下信息:

    ./elasticsearch
    [2014-03-13 13:42:17,218][INFO ][node           ] [New Goblin] version[1.1.1], pid[2085], build[5c03844/2014-02-25T15:52:53Z]
    [2014-03-13 13:42:17,219][INFO ][node           ] [New Goblin] initializing ...
    [2014-03-13 13:42:17,223][INFO ][plugins        ] [New Goblin] loaded [], sites []
    [2014-03-13 13:42:19,831][INFO ][node           ] [New Goblin] initialized
    [2014-03-13 13:42:19,832][INFO ][node           ] [New Goblin] starting ...
    [2014-03-13 13:42:19,958][INFO ][transport      ] [New Goblin] bound_address {inet[/0:0:0:0:0:0:0:0:9300]}, publish_address {inet[/192.168.8.112:9300]}
    [2014-03-13 13:42:23,030][INFO ][cluster.service] [New Goblin] new_master [New Goblin][rWMtGj3dQouz2r6ZFL9v4g][mwubuntu1][inet[/192.168.8.112:9300]], reason: zen-disco-join (elected_as_master)
    [2014-03-13 13:42:23,100][INFO ][discovery      ] [New Goblin] elasticsearch/rWMtGj3dQouz2r6ZFL9v4g
    [2014-03-13 13:42:23,125][INFO ][http           ] [New Goblin] bound_address {inet[/0:0:0:0:0:0:0:0:9200]}, publish_address {inet[/192.168.8.112:9200]}
    [2014-03-13 13:42:23,629][INFO ][gateway        ] [New Goblin] recovered [1] indices into cluster_state
    [2014-03-13 13:42:23,630][INFO ][node           ] [New Goblin] started

不去涉及太多細節,咱們能夠看到,一叫作「New Goblin」(你會見到一個不一樣的漫威漫畫角色)的節點啓動而且將本身選作單結點集羣的master。如今不用關心master是什麼東西。這裏重要的就是,咱們在一個集羣中開啓了一個節點。

正如先前提到的,咱們能夠覆蓋集羣或者節點的名字。咱們能夠在啓動Elasticsearch的時候經過命令行來指定,以下:

    ./elasticsearch --cluster.name my_cluster_name --node.name my_node_name

也要注意一下有http標記的那一行,它提供了有關HTTP地址(192.168.8.112)和端口(9200)的信息,經過這個地址和端口咱們就能夠訪問咱們的節點了。默認狀況下,Elasticsearch使用9200來提供對其REST API的訪問。若是有必要,這個端口是能夠配置的。

探索你的集羣node

rest接口

    如今咱們已經有一個正常運行的節點(和集羣)了,下一步就是要去理解怎樣與其通訊了。幸運的是,Elasticsearch提供了很是全面和強大的REST API,利用這個REST API你能夠同你的集×××互。下面是利用這個API,能夠作的幾件事情:

        - 檢查你的集羣、節點和索引的健康狀態、和各類統計信息
        - 管理你的集羣、節點、索引數據和元數據
        - 對你的索引進行CRUD(建立、讀取、更新和刪除)和搜索操做
        - 執行高級的查詢操做,像是分頁、排序、過濾、腳本編寫(scripting)、小平面刻畫(faceting)、聚合(aggregations)和許多其它操做

集羣健康

    讓咱們以基本的健康檢查做爲開始,咱們能夠利用它來查看咱們集羣的狀態。此過程當中,咱們使用curl,固然,你也可使用任何能夠建立HTTP/REST調用的工具。咱們假設咱們還在咱們啓動Elasticsearch的節點上並打開另一個shell窗口。

    要檢查集羣健康,咱們將使用_cat API。須要事先記住的是,咱們的節點HTTP的端口是9200:

        curl 'localhost:9200/_cat/health?v'

    相應的響應是:

        epoch      timestamp cluster       status node.total node.data shards pri relo init unassign
        1394735289 14:28:09  elasticsearch green           1         1      0   0    0    0        0

    能夠看到,咱們集羣的名字是「elasticsearch」,正常運行,而且狀態是綠色。

    當咱們詢問集羣狀態的時候,咱們要麼獲得綠色、×××或紅色。綠色表明一切正常(集羣功能齊全),×××意味着全部的數據都是可用的,可是某些複製沒有被分配(集羣功能齊全),紅色則表明由於某些緣由,某些數據不可用。注意,即便是集羣狀態是紅色的,集羣仍然是部分可用的(它仍然會利用可用的分片來響應搜索請求),可是可能你須要儘快修復它,由於你有丟失的數據。

    也是從上面的響應中,咱們能夠看到,一共有一個節點,因爲裏面沒有數據,咱們有0個分片。注意,因爲咱們使用默認的集羣名字(elasticsearch),而且因爲Elasticsearch默認使用網絡多播(multicast)發現其它節點,若是你在你的網絡中啓動了多個節點,你就已經把她們加入到一個集羣中了。在這種情形下,你可能在上面的響應中看到多個節點。

    咱們也能夠得到節集羣中的節點列表:

        curl 'localhost:9200/_cat/nodes?v'

    對應的響應是:

        curl 'localhost:9200/_cat/nodes?v'
        host         ip        heap.percent ram.percent load node.role master name
        mwubuntu1    127.0.1.1            8           4 0.00 d         *      New Goblin

    這兒,咱們能夠看到咱們叫作「New Goblin」的節點,這個節點是咱們集羣中的惟一節點。

列出全部的索引
    讓咱們看一下咱們的索引:

        curl 'localhost:9200/_cat/indices?v'

    響應是:

        curl 'localhost:9200/_cat/indices?v'
        health index pri rep docs.count docs.deleted store.size pri.store.size

    這個結果意味着,在咱們的集羣中,咱們沒有任何索引。

建立一個索引

    如今讓咱們建立一個叫作「customer」的索引,而後再列出全部的索引:

        curl -XPUT 'localhost:9200/customer?pretty'
        curl 'localhost:9200/_cat/indices?v'

    第一個命令使用PUT建立了一個叫作「customer」的索引。咱們簡單地將pretty附加到調用的尾部,使其以美觀的形式打印出JSON響應(若是有的話)。
    響應以下:

        curl -XPUT 'localhost:9200/customer?pretty'
        {
          "acknowledged" : true
        }

        curl 'localhost:9200/_cat/indices?v'
        health index    pri rep docs.count docs.deleted store.size pri.store.size
        yellow customer   5   1          0            0       495b           495b

    第二個命令的結果告知咱們,咱們如今有一個叫作customer的索引,而且它有5個主分片和1份複製(都是默認值),其中包含0個文檔。

    你可能也注意到了這個customer索引有一個×××健康標籤。回顧咱們以前的討論,×××意味着某些複製沒有(或者還未)被分配。這個索引之因此這樣,是由於Elasticsearch默認爲這個索引建立一份複製。因爲如今咱們只有一個節點在運行,那一份複製就分配不了了(爲了高可用),直到當另一個節點加入到這個集羣后,才能分配。一旦那份複製在第二個節點上被複制,這個節點的健康狀態就會變成綠色。

索引並查詢一個文檔
如今讓咱們放一些東西到customer索引中。首先要知道的是,爲了索引一個文檔,咱們必須告訴Elasticsearch這個文檔要到這個索引的哪一個類型(type)下。git

讓咱們將一個簡單的客戶文檔索引到customer索引、「external」類型中,這個文檔的ID是1,操做以下:

    curl -XPUT 'localhost:9200/customer/external/1?pretty' -d '
    {
      "name": "John Doe"
    }'

響應以下:

    curl -XPUT 'localhost:9200/customer/external/1?pretty' -d '
    {
      "name": "John Doe"
    }'
    {
      "_index" : "customer",
      "_type" : "external",
      "_id" : "1",
      "_version" : 1,
      "created" : true
    }

從上面的響應中,咱們能夠看到,一個新的客戶文檔在customer索引和external類型中被成功建立。文檔也有一個內部id 1, 這個id是咱們在索引的時候指定的。

有一個關鍵點須要注意,Elasticsearch在你想將文檔索引到某個索引的時候,並不強制要求這個索引被顯式地建立。在前面這個例子中,若是customer索引不存在,Elasticsearch將會自動地建立這個索引。

如今,讓咱們把剛剛索引的文檔取出來:

    curl -XGET 'localhost:9200/customer/external/1?pretty'

響應以下:

    curl -XGET 'localhost:9200/customer/external/1?pretty'
    {
      "_index" : "customer",
      "_type" : "external",
      "_id" : "1",
      "_version" : 1,
      "found" : true, "_source" : { "name": "John Doe" }
    }

除了一個叫作found的字段來指明咱們找到了一個ID爲1的文檔,和另一個字段——_source——返回咱們前一步中索引的完整JSON文檔以外,其它的都沒有什麼特別之處。

刪除一個文檔github

如今讓咱們刪除咱們剛剛建立的索引,並再次列出全部的索引:

    curl -XDELETE 'localhost:9200/customer?pretty'
    curl 'localhost:9200/_cat/indices?v'

響應以下:

    curl -XDELETE 'localhost:9200/customer?pretty'
    {
      "acknowledged" : true
    }
    curl 'localhost:9200/_cat/indices?v'
    health index pri rep docs.count docs.deleted store.size pri.store.size

這代表咱們成功地刪除了這個索引,如今咱們回到了集羣中空無全部的狀態。

在更進一步以前,咱們再細看一下一些咱們學過的API命令:

    curl -XPUT 'localhost:9200/customer'
    curl -XPUT 'localhost:9200/customer/external/1' -d '
    {
      "name": "John Doe"
    }'
    curl 'localhost:9200/customer/external/1'
    curl -XDELETE 'localhost:9200/customer'

若是咱們仔細研究以上的命令,咱們能夠發現訪問Elasticsearch中數據的一個模式。這個模式能夠被總結爲:

    curl -<REST Verb> <Node>:<Port>/<Index>/<Type><ID>

這個REST訪問模式廣泛適用於全部的API命令,若是你能記住它,你就會爲掌握Elasticsearch開一個好頭。

修改你的數據shell

Elasticsearch提供了近乎實時的數據操做和搜索功能。默認狀況下,從你索引/更新/刪除你的數據動做開始到它出如今你的搜索結果中,大概會有1秒鐘的延遲。這和其它相似SQL的平臺不一樣,數據在一個事務完成以後就會當即可用。

索引/替換文檔

    咱們先前看到,怎樣索引一個文檔。如今咱們再次調用那個命令:
        curl -XPUT 'localhost:9200/customer/external/1?pretty' -d '
        {
          "name": "John Doe"
        }'

    再次,以上的命令將會把這個文檔索引到customer索引、external類型中,其ID是1。若是咱們對一個不一樣(或相同)的文檔應用以上的命令,Elasticsearch將會用一個新的文檔來替換(從新索引)當前ID爲1的那個文檔。

        curl -XPUT 'localhost:9200/customer/external/1?pretty' -d '
        {
          "name": "Jane Doe"
        }'

    以上的命令將ID爲1的文檔的name字段的值從「John Doe」改爲了「Jane Doe」。若是咱們使用一個不一樣的ID,一個新的文檔將會被索引,當前已經在索引中的文檔不會受到影響。

        curl -XPUT 'localhost:9200/customer/external/2?pretty' -d '
        {
          "name": "Jane Doe"
        }'

    以上的命令,將會索引一個ID爲2的新文檔。

    在索引的時候,ID部分是可選的。若是不指定,Elasticsearch將產生一個隨機的ID來索引這個文檔。Elasticsearch生成的ID會做爲索引API調用的一部分被返回。

    如下的例子展現了怎樣在沒有指定ID的狀況下來索引一個文檔:

        curl -XPOST 'localhost:9200/customer/external?pretty' -d '
        {
          "name": "Jane Doe"
        }'

    注意,在上面的情形中,因爲咱們沒有指定一個ID,咱們使用的是POST而不是PUT。

更新文檔json

除了能夠索引、替換文檔以外,咱們也能夠更新一個文檔。但要注意,Elasticsearch底層並不支持原地更新。在咱們想要作一次更新的時候,Elasticsearch先刪除舊文檔,而後在索引一個更新過的新文檔。

下面的例子展現了怎樣將咱們ID爲1的文檔的name字段改爲「Jane Doe」:

    curl -XPOST 'localhost:9200/customer/external/1/_update?pretty' -d '
    {
      "doc": { "name": "Jane Doe" }
    }'

下面的例子展現了怎樣將咱們ID爲1的文檔的name字段改爲「Jane Doe」的同時,給它加上age字段:

    curl -XPOST 'localhost:9200/customer/external/1/_update?pretty' -d '
    {
      "doc": { "name": "Jane Doe", "age": 20 }
    }'

更新也能夠經過使用簡單的腳原本進行。這個例子使用一個腳本將age加5:

    curl -XPOST 'localhost:9200/customer/external/1/_update?pretty' -d '
    {
      "script" : "ctx._source.age += 5"
    }'

在上面的例子中,ctx._source指向當前要被更新的文檔。

注意,在寫做本文時,更新操做只能一次應用在一個文檔上。未來,Elasticsearch將提供同時更新符合指定查詢條件的多個文檔的功能(相似於SQL的UPDATE-WHERE語句)。

刪除文檔ubuntu

刪除文檔是至關直觀的。如下的例子展現了咱們怎樣刪除ID爲2的文檔:

    curl -XDELETE 'localhost:9200/customer/external/2?pretty'

咱們也可以一次刪除符合某個查詢條件的多個文檔。如下的例子展現瞭如何刪除名字中包含「John」的全部的客戶:

    curl -XDELETE 'localhost:9200/customer/external/_query?pretty' -d '
    {
      "query": { "match": { "name": "John" } }
    }'

注意,以上的URI變成了/_query,以此來代表這是一個「查詢刪除」API,其中刪除查詢標準放在請求體中,可是咱們仍然使用DELETE。如今先不要擔憂查詢語法,咱們將會在本教程後面的部分中涉及。

批處理:緩存

除了可以對單個的文檔進行索引、更新和刪除以外,Elasticsearch也提供了以上操做的批量處理功能,這是經過使用_bulk API實現的。這個功能之因此重要,在於它提供了很是高效的機制來儘量快的完成多個操做,與此同時使用盡量少的網絡往返。

做爲一個快速的例子,如下調用在一次bulk操做中索引了兩個文檔(ID 1 - John Doe and ID 2 - Jane Doe):

    curl -XPOST 'localhost:9200/customer/external/_bulk?pretty' -d '
    {"index":{"_id":"1"}}
    {"name": "John Doe" }
    {"index":{"_id":"2"}}
    {"name": "Jane Doe" }
    '

如下例子在一個bulk操做中,首先更新第一個文檔(ID爲1),而後刪除第二個文檔(ID爲2):

    curl -XPOST 'localhost:9200/customer/external/_bulk?pretty' -d '
    {"update":{"_id":"1"}}
    {"doc": { "name": "John Doe becomes Jane Doe" } }
    {"delete":{"_id":"2"}}
    '

注意上面的delete動做,因爲刪除動做只須要被刪除文檔的ID,因此並無對應的源文檔。

bulk API按順序執行這些動做。若是其中一個動做由於某些緣由失敗了,將會繼續處理它後面的動做。當bulk API返回時,它將提供每一個動做的狀態(按照一樣的順序),因此你可以看到某個動做成功與否。

探索你的數據服務器

樣本數據集
    如今咱們對於基本的東西已經有了一些感受,如今讓咱們嘗試使用一些更加貼近現實的數據集。我已經準備了一些假想的客戶的銀行帳戶信息的JSON文檔的樣本。文檔具備如下的模式(schema):

        {
            "account_number": 0,
            "balance": 16623,
            "firstname": "Bradshaw",
            "lastname": "Mckenzie",
            "age": 29,
            "gender": "F",
            "address": "244 Columbus Place",
            "employer": "Euron",
            "email": "bradshawmckenzie@euron.com",
            "city": "Hobucken",
            "state": "CO"
        }

    我是在http://www.json-generator.com/上生成這些數據的。

載入樣本數據

    你能夠從https://github.com/bly2k/files/blob/master/accounts.zip?raw=true下載這個樣本數據集。將其解壓到當前目錄下,以下,將其加載到咱們的集羣裏:

        curl -XPOST 'localhost:9200/bank/account/_bulk?pretty' --data-binary @accounts.json
        curl 'localhost:9200/_cat/indices?v'

    響應是:
        curl 'localhost:9200/_cat/indices?v'
        health index pri rep docs.count docs.deleted store.size pri.store.size
        yellow bank    5   1       1000            0    424.4kb        424.4kb

    這意味着咱們成功批量索引了1000個文檔到銀行索引中(account類型)。

搜索API

    如今,讓咱們以一些簡單的搜索來開始。有兩種基本的方式來運行搜索:一種是在REST請求的URI中發送搜索參數,另外一種是將搜索參數發送到REST請求體中。請求體方法的表達能力更好,而且你可使用更加可讀的JSON格式來定義搜索。咱們將嘗試使用一次請求URI做爲例子,可是教程的後面部分,咱們將僅僅使用請求體方法。

    搜索的REST API能夠經過_search端點來訪問。下面這個例子返回bank索引中的全部的文檔:

        curl 'localhost:9200/bank/_search?q=*&pretty'

    咱們仔細研究一下這個查詢調用。咱們在bank索引中搜索(_search端點),而且q=*參數指示Elasticsearch去匹配這個索引中全部的文檔。pretty參數,和之前同樣,僅僅是告訴Elasticsearch返回美觀的JSON結果。

    如下是響應(部分列出):

        curl 'localhost:9200/bank/_search?q=*&pretty'
        {
          "took" : 63,
          "timed_out" : false,
          "_shards" : {
            "total" : 5,
            "successful" : 5,
            "failed" : 0
          },
          "hits" : {
            "total" : 1000,
            "max_score" : 1.0,
            "hits" : [ {
              "_index" : "bank",
              "_type" : "account",
              "_id" : "1",
              "_score" : 1.0, "_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
            }, {
              "_index" : "bank",
              "_type" : "account",
              "_id" : "6",
              "_score" : 1.0, "_source" : {"account_number":6,"balance":5686,"firstname":"Hattie","lastname":"Bond","age":36,"gender":"M","address":"671 Bristol Street","employer":"Netagy","email":"hattiebond@netagy.com","city":"Dante","state":"TN"}
            }, {
              "_index" : "bank",
              "_type" : "account",

    對於這個響應,咱們看到了如下的部分:
      - took —— Elasticsearch執行這個搜索的耗時,以毫秒爲單位
      - timed_out —— 指明這個搜索是否超時
      - _shards —— 指出多少個分片被搜索了,同時也指出了成功/失敗的被搜索的shards的數量
      - hits —— 搜索結果
      - hits.total —— 可以匹配咱們查詢標準的文檔的總數目
      - hits.hits —— 真正的搜索結果數據(默認只顯示前10個文檔)
      - _score和max_score —— 如今先忽略這些字段

    使用請求體方法的等價搜索是:

        curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
        {
          "query": { "match_all": {} }
        }'

    這裏的不一樣之處在於,並非向URI中傳遞q=*,取而代之的是,咱們在_search API的請求體中POST了一個JSON格式請求體。咱們將在下一部分中討論這個JSON查詢。

    響應是:

        curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
        {
          "query": { "match_all": {} }
        }'
        {
          "took" : 26,
          "timed_out" : false,
          "_shards" : {
            "total" : 5,
            "successful" : 5,
            "failed" : 0
          },
          "hits" : {
            "total" : 1000,
            "max_score" : 1.0,
            "hits" : [ {
              "_index" : "bank",
              "_type" : "account",
              "_id" : "1",
              "_score" : 1.0, "_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
            }, {
              "_index" : "bank",
              "_type" : "account",
              "_id" : "6",
              "_score" : 1.0, "_source" : {"account_number":6,"balance":5686,"firstname":"Hattie","lastname":"Bond","age":36,"gender":"M","address":"671 Bristol Street","employer":"Netagy","email":"hattiebond@netagy.com","city":"Dante","state":"TN"}
            }, {
              "_index" : "bank",
              "_type" : "account",
              "_id" : "13",

    有一點須要重點理解一下,一旦你取回了你的搜索結果,Elasticsearch就完成了使命,它不會維護任何服務器端的資源或者在你的結果中打開遊標。這是和其它相似SQL的平臺的一個鮮明的對比, 在那些平臺上,你能夠在前面先獲取你查詢結果的一部分,而後若是你想獲取結果的剩餘部分,你必須繼續返回服務端去取,這個過程使用一種有狀態的服務器端遊標技術。

介紹查詢語言

Elasticsearch提供一種JSON風格的特定領域語言,利用它你能夠執行查詢。這杯稱爲查詢DSL。這個查詢語言至關全面,第一眼看上去可能有些咄咄逼人,可是最好的學習方法就是以幾個基礎的例子來開始。

回到咱們上一個例子,咱們執行了這個查詢:

    {
      "query": { "match_all": {} }
    }

分解以上的這個查詢,其中的query部分告訴我查詢的定義,match_all部分就是咱們想要運行的查詢的類型。match_all查詢,就是簡單地查詢一個指定索引下的全部的文檔。

除了這個query參數以外,咱們也能夠經過傳遞其它的參數來影響搜索結果。好比,下面作了一次match_all並只返回第一個文檔:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "query": { "match_all": {} },
      "size": 1
    }'

注意,若是沒有指定size的值,那麼它默認就是10。

下面的例子,作了一次match_all而且返回第11到第20個文檔:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "query": { "match_all": {} },
      "from": 10,
      "size": 10
    }'

其中的from參數(0-based)從哪一個文檔開始,size參數指明從from參數開始,要返回多少個文檔。這個特性對於搜索結果分頁來講很是有幫助。注意,若是不指定from的值,它默認就是0。

下面這個例子作了一次match_all而且以帳戶餘額降序排序,最後返前十個文檔:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "query": { "match_all": {} },
      "sort": { "balance": { "order": "desc" } }
    }'

執行搜索

如今咱們已經知道了幾個基本的參數,讓咱們進一步發掘查詢語言吧。首先咱們看一下返回文檔的字段。默認狀況下,是返回完整的JSON文檔的。這能夠經過source來引用(搜索hits中的_sourcei字段)。若是咱們不想返回完整的源文檔,咱們能夠指定返回的幾個字段。

下面這個例子說明了怎樣返回兩個字段account_number和balance(固然,這兩個字段都是指_source中的字段),如下是具體的搜索:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "query": { "match_all": {} },
      "_source": ["account_number", "balance"]
    }'

注意到上面的例子僅僅是簡化了_source字段。它仍將會返回一個叫作_source的字段,可是僅僅包含account_number和balance來年改革字段。

若是你有SQL背景,上述查詢在概念上有些像SQL的SELECT FROM。

如今讓咱們進入到查詢部分。以前,咱們看到了match_all查詢是怎樣匹配到全部的文檔的。如今咱們介紹一種新的查詢,叫作match查詢,這能夠當作是一個簡單的字段搜索查詢(好比對應於某個或某些特定字段的搜索)。

下面這個例子返回帳戶編號爲20的文檔:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "query": { "match": { "account_number": 20 } }
    }'

下面這個例子返回地址中包含「mill」的全部帳戶:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "query": { "match": { "address": "mill" } }
    }'

下面這個例子返回地址中包含「mill」或者包含「lane」的帳戶:

   curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "query": { "match": { "address": "mill lane" } }
    }' 

下面這個例子是match的變體(match_phrase),它會去匹配短語「mill lane」:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "query": { "match_phrase": { "address": "mill lane" } }
    }'

如今,讓咱們介紹一下布爾查詢。布爾查詢容許咱們利用布爾邏輯將較小的查詢組合成較大的查詢。

如今這個例子組合了兩個match查詢,這個組合查詢返回包含「mill」和「lane」的全部的帳戶:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "query": {
        "bool": {
          "must": [
            { "match": { "address": "mill" } },
            { "match": { "address": "lane" } }
          ]
        }
      }
    }'

在上面的例子中,bool must語句指明瞭,對於一個文檔,全部的查詢都必須爲真,這個文檔纔可以匹配成功。

相反的,下面的例子組合了兩個match查詢,它返回的是地址中包含「mill」或者「lane」的全部的帳戶:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "query": {
        "bool": {
          "should": [
            { "match": { "address": "mill" } },
            { "match": { "address": "lane" } }
          ]
        }
      }
    }'

在上面的例子中,bool should語句指明,對於一個文檔,查詢列表中,只要有一個查詢匹配,那麼這個文檔就被當作是匹配的。

如今這個例子組合了兩個查詢,它返回地址中既不包含「mill」,同時也不包含「lane」的全部的帳戶信息:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "query": {
        "bool": {
          "must_not": [
            { "match": { "address": "mill" } },
            { "match": { "address": "lane" } }
          ]
        }
      }
    }'

在上面的例子中, bool must_not語句指明,對於一個文檔,查詢列表中的的全部查詢都必須都不爲真,這個文檔才被認爲是匹配的。

咱們能夠在一個bool查詢裏一塊兒使用must、should、must_not。此外,咱們能夠將bool查詢放到這樣的bool語句中來模擬複雜的、多等級的布爾邏輯。

下面這個例子返回40歲以上而且不生活在ID(daho)的人的帳戶:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "query": {
        "bool": {
          "must": [
            { "match": { "age": "40" } }
          ],
          "must_not": [
            { "match": { "state": "ID" } }
          ]
        }
      }
    }'

執行過濾器

在先前的章節中,咱們跳過了文檔得分的細節(搜索結果中的_score字段)。這個得分是與咱們指定的搜索查詢匹配程度的一個相對度量。得分越高,文檔越相關,得分越低文檔的相關度越低。

Elasticsearch中的全部的查詢都會觸發相關度得分的計算。對於那些咱們不須要相關度得分的場景下,Elasticsearch以過濾器的形式提供了另外一種查詢功能。過濾器在概念上相似於查詢,可是它們有很是快的執行速度,這種快的執行速度主要有如下兩個緣由

    - 過濾器不會計算相關度的得分,因此它們在計算上更快一些
    - 過濾器能夠被緩存到內存中,這使得在重複的搜索查詢上,其要比相應的查詢快出許多。

爲了理解過濾器,咱們先來介紹「被過濾」的查詢,這使得你能夠將一個查詢(像是match_all,match,bool等)和一個過濾器結合起來。做爲一個例子,咱們介紹一下範圍過濾器,它容許咱們經過一個區間的值來過濾文檔。這一般被用在數字和日期的過濾上。

這個例子使用一個被過濾的查詢,其返回值是越在20000到30000之間(閉區間)的帳戶。換句話說,咱們想要找到越大於等於20000而且小於等於30000的帳戶。

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "query": {
        "filtered": {
          "query": { "match_all": {} },
          "filter": {
            "range": {
              "balance": {
                "gte": 20000,
                "lte": 30000
              }
            }
          }
        }
      }
    }'

分解上面的例子,被過濾的查詢包含一個match_all查詢(查詢部分)和一個過濾器(filter部分)。咱們能夠在查詢部分中放入其餘查詢,在filter部分放入其它過濾器。在上面的應用場景中,因爲全部的在這個範圍以內的文檔都是平等的(或者說相關度都是同樣的),沒有一個文檔比另外一個文檔更相關,因此這個時候使用範圍過濾器就很是合適了。

一般狀況下,要決定是使用過濾器仍是使用查詢,你就須要問本身是否須要相關度得分。若是相關度是不重要的,使用過濾器,不然使用查詢。若是你有SQL背景,查詢和過濾器在概念上相似於SELECT WHERE語句, although more so for filters than queries。

除了match_all, match, bool,filtered和range查詢,還有不少其它類型的查uxn/過濾器,咱們這裏不會涉及。因爲咱們已經對它們的工做原理有了基本的理解,將其應用到其它類型的查詢、過濾器上也不是件難事。

執行聚合

聚合提供了分組並統計數據的能力。理解聚合的最簡單的方式是將其粗略地等同爲SQL的GROUP BY和SQL聚合函數。在Elasticsearch中,你能夠在一個響應中同時返回命中的數據和聚合結果。你可使用簡單的API同時運行查詢和多個聚合,並以一次返回,這避免了來回的網絡通訊,這是很是強大和高效的。

做爲開始的一個例子,咱們按照state分組,按照州名的計數倒序排序:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "size": 0,
      "aggs": {
        "group_by_state": {
          "terms": {
            "field": "state"
          }
        }
      }
    }'

在SQL中,上面的聚合在概念上相似於:
   SELECT COUNT(*) from bank GROUP BY state ORDER BY COUNT(*) DESC

響應(其中一部分)是:

"hits" : {
        "total" : 1000,
        "max_score" : 0.0,
        "hits" : [ ]
      },
      "aggregations" : {
        "group_by_state" : {
          "buckets" : [ {
            "key" : "al",
            "doc_count" : 21
          }, {
            "key" : "tx",
            "doc_count" : 17
          }, {
            "key" : "id",
            "doc_count" : 15
          }, {
            "key" : "ma",
            "doc_count" : 15
          }, {
            "key" : "md",
            "doc_count" : 15
          }, {
            "key" : "pa",
            "doc_count" : 15
          }, {
            "key" : "dc",
            "doc_count" : 14
          }, {
            "key" : "me",
            "doc_count" : 14
          }, {
            "key" : "mo",
            "doc_count" : 14
          }, {
            "key" : "nd",
            "doc_count" : 14
          } ]
        }
      }
    }

咱們能夠看到AL(abama)有21個帳戶,TX有17個帳戶,ID(daho)有15個帳戶,依此類推。

注意咱們將size設置成0,這樣咱們就能夠只看到聚合結果了,而不會顯示命中的結果。

在先前聚合的基礎上,如今這個例子計算了每一個州的帳戶的平均餘額(仍是按照帳戶數量倒序排序的前10個州):

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "size": 0,
      "aggs": {
        "group_by_state": {
          "terms": {
            "field": "state"
          },
          "aggs": {
            "average_balance": {
              "avg": {
                "field": "balance"
              }
            }
          }
        }
      }
    }'

注意,咱們把average_balance聚合嵌套在了group_by_state聚合之中。這是全部聚合的一個經常使用模式。你能夠任意的聚合之中嵌套聚合,這樣你就能夠從你的數據中抽取出想要的概述。

基於前面的聚合,如今讓咱們按照平均餘額進行排序:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "size": 0,
      "aggs": {
        "group_by_state": {
          "terms": {
            "field": "state",
            "order": {
              "average_balance": "desc"
            }
          },
          "aggs": {
            "average_balance": {
              "avg": {
                "field": "balance"
              }
            }
          }
        }
      }
    }'

下面的例子顯示瞭如何使用年齡段(20-29,30-39,40-49)分組,而後在用性別分組,而後爲每個年齡段的每個性別計算平均帳戶餘額:

    curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
    {
      "size": 0,
      "aggs": {
        "group_by_age": {
          "range": {
            "field": "age",
            "ranges": [
              {
                "from": 20,
                "to": 30
              },
              {
                "from": 30,
                "to": 40
              },
              {
                "from": 40,
                "to": 50
              }
            ]
          },
          "aggs": {
            "group_by_gender": {
              "terms": {
                "field": "gender"
              },
              "aggs": {
                "average_balance": {
                  "avg": {
                    "field": "balance"
                  }
                }
              }
            }
          }
        }
      }
    }'

有不少關於聚合的細節,咱們沒有涉及。若是你想作更進一步的實驗,http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations.html是一個很是好的起點。

總結

Elasticsearch既是一個簡單的產品,也是一個複雜的產品。咱們如今已經學習到了基礎部分,它的一些原理,以及怎樣用REST API來作一些工做。我但願這個教程已經使你對Elasticsearch是什麼有了一個更好的理解,跟重要的是,可以激發你繼續實驗Elasticsearch的其它特性。
相關文章
相關標籤/搜索