最完整的Elasticsearch 基礎教程
基礎概念
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個分片。
這些問題搞清楚之後,我們就要進入好玩的部分了...
安裝
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的訪問。如果有必要,這個端口是可以配置的。
探索你的集群
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)下。
讓我們將一個簡單的客戶文檔索引到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文檔之外,其它的都沒有什麽特別之處。
刪除一個文檔
現在讓我們刪除我們剛剛創建的索引,並再次列出所有的索引:
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開一個好頭。
修改你的數據
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。
更新文檔
除了可以索引、替換文檔之外,我們也可以更新一個文檔。但要註意,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語句)。
刪除文檔
刪除文檔是相當直觀的。以下的例子展示了我們怎樣刪除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": "[email protected]
"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":"[email protected]