Elasticsearch是一個分佈式RESTful風格的搜索和數據分析引擎
- 查詢:Elasticsearch允許執行和合並多種類型的搜索——結構化、非結構化、地理位置、度量指標。搜索方式隨心而變
- 分析:找到與查詢最匹配的是個文檔是一回事。但是如果面對的是數億級別的日誌,又該如何解讀呢?Elasticsearch聚合讓你能夠從大處着眼,探索數據 的趨勢和模式
- 速度:Elasticsearch非常快
- 可擴展性:可以在筆記本上運行,同樣也可以在承載了PB級數據的成百上千臺服務器上運行
- 彈性:Elasticsearch運行在一個分佈式的環境中,從設計之初就考慮到了這一點
- 靈活性:數字、文本、地理位置、結構化、非結構化。所有的數據類型都非常受歡迎
- HADOOP&SPARK:Elasticsearch + Hadoop
準備開始
Elasticsearch是一個高度可伸縮的開源全文搜索和分析引擎。它允許你快速和接近實時的存儲、搜索和分析大量的數據
基本概念
- Near Realtime(NRT)
- Elasticsearch是一個近乎實時的搜索平臺。這也就意味着從索引文檔到可以搜索的時間中間只有輕微的延遲(通常是一秒)
- Cluster
- 集羣是一個或多個節點(服務器)的集合,他們共同保存了你的完整的數據,並且提供了橫跨所有節點的聯合索引和搜索功能。一個集羣由一個唯一的名稱標識,默認這個唯一的標識的名稱是"elasticsearch"這個名稱非常重要,因爲如果節點被設置爲按其名稱加入到集羣中的話,那這樣的話節點只是集羣中的一部分
- Node
- 節點是一個單獨的服務器,他也是集羣的一部分,存儲數據,並參與集羣的索引和搜索功能。就像集羣一樣,節點由一個名稱來表示,默認情況下,改名稱是在啓動時分配給節點的隨機通用唯一標識符(UUID)。如果不想用默認的節點名稱,可以定義任何想要的節點。這個名稱對於管理來說很重要,因爲你希望識別網絡中的哪些服務器對應於你的Elasticsearch集羣中的哪些節點
- 一個績點可以通過配置集羣名稱來加入到一個特定的集羣中。默認情況下,每個節點都被設置加入到一個名字叫"elasticsearch"的集羣中,這就意味着如果你啓動了很多個節點,並且假設它們彼此可以互相發現,那麼它們將自動形成並加入到一個名爲’elasticsearch‘的集羣中
- 一個集羣可以有任意數量的節點。此外,如果在你的網絡上當前沒有運行任何節點,那麼此時啓動一個節點將默認形成一個單節點的名字,叫"elasticsearch"的集羣
- Index
- 索引是具有某種相似特徵的文檔的集合。例如你可以有一個顧客數據索引,產品目錄索引和訂單數據索引。索引有一個名稱(這裏必須呀小寫)標識,該名稱用於在對其中的文檔執行索引、搜索、更新和刪除操作時引用索引
- Document
- 文檔是可以被索引的基本信息單元。文檔用JSON表示
- Shards & Replicas
- 一個索引可能存儲大量的數據,這些數據超過單個節點的硬件限制。例如,一個包含10億條文檔佔用1TB磁盤空間的索引可能不適合在單個節點上,或者可能太慢而不能單獨處理來自單個節點的搜索請求
- 爲了解決這個問題,Elasticsearch提供了將你的索引細分爲多個碎片(或者叫分片)的能力。在創建索引的時候,可以簡單的定義所需要的分片的數量,每個分片本身就是一個功能,完全獨立的"索引",可以駐留在集羣中的任何節點上。
- 分片之所以重要主要有兩個原因
- 它允許你水分的分割/擴展內容卷
- 它允許你跨分片(可能在多個節點上)分佈和並行操作,從而提高性能和吞吐量
- 在一個網絡/雲環境中隨時都有可能出現故障,強烈推薦你有一個容災機制,Elasticsearch允許你將一個或者多個索引分片複製到其他地方,這就被稱之爲副本
- 複製之所以重要主要有兩個原因
- 它提供了在一個shard/node失敗的高可用性。所以出於這個原因,很重要的一個點是一個副本從來不會被分配到與它複製的的原始分片相同節點上。也就是說,副本是放到另外的節點上的
- 它允許擴展搜索量/吞吐量,因爲搜索可以在所有副本上並行執行
- 一個索引可能存儲大量的數據,這些數據超過單個節點的硬件限制。例如,一個包含10億條文檔佔用1TB磁盤空間的索引可能不適合在單個節點上,或者可能太慢而不能單獨處理來自單個節點的搜索請求
- 總而言之,每個索引都可以被分割成多個分片。索引也可以被複制零(意味着沒有副本)或更多次。一旦被複制,每個索引都將具有主分片(被複制的原始分片)和副本分片(主分片的副本)。在創建索引的時候,可以爲每個索引定義分片和副本的數量。創建索引後,你可以隨時動態的更改副本的數量,但不能更改事後分片的數量
- 在默認的情況下,Elasticsearch中的每個索引都分配了5個主分片和1個副本,這就意味着如果集羣中至少有兩個節點,那麼索引將有5個主分片和5個副本分片(PS:這5個副本分片組成了一個完整的副本),每個索引總共有10個分片。(副本是針對索引而言的,同時需要注意索引和節點數量沒有關係,我們說2個副本值的是索引被複制了2次,而1個索引可能由5個分片組成,那麼在這種情況下,集羣中的分片數應該是5 ✖️(1 + 2) = 15)
安裝
- 首先需要配置java環境,由於我用homebrew一直下載失敗,所以選擇下載安裝包進行安裝
- 之後就是在利用homebrew安裝Elasticsearch --> brew install ElasticSearch
- 運行elasticsearch,然後在網頁中輸入127.0.0.1:9200
The REST API
集羣健康
請求:
curl -X GET "localhost:9200/_cat/health?v"
響應:
epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1569298977 04:22:57 elasticsearch_mac green 1 1 0 0 0 0 0 0 - 100.0%
在這裏面可以看到我們命名的"elasticsearch_mac"的集羣現在是green狀態。無論什麼時候我們請求集羣健康的時候,我們會得到green、yellow或者是red
- Green:everything is good(一切正常)
- Yellow:所有數據都是可用的,但是有些副本還沒由分配(所有功能正常)
- Red:有些數據不可用(部分功能正常)
上面的這些響應我們可以看到,集羣elasticsearch_mac總共只有一個節點,零個分片,因爲還沒有數據
查看全部所有的索引:
curl -X PUT "localhost:9200/customer?pretty"
ps:pretty的意思是響應(如果有的話)以JSON格式返回
響應:
{ "acknowledged" : true, "shards_acknowledged" : true, "index" : "customer" }
請求:
curl -X GET "localhost:9200/_cat/indices?v"
響應:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open customer j_ybcHPwSdujNhRH6wrZrA 1 1 0 0 230b 230b
結果的第二行告訴我們,我們現在有一個叫customer的索引,他有1個主分片和1個副本(默認是一個副本),有0個文檔
在這裏customer索引的狀態是yellow。也就意味着一些副本尚未被分配
之所以會出現這種情況是因爲Elasticsearch默認情況下爲了這個索引創建了一個副本。但是由於目前我們只有一個節點在運行,所以直到稍後另一個節點加入到集羣中時,纔會分配一個副本(對於高可用性)。一旦該副本分配到第二個節點上,那麼該索引的狀態就會變成green了
索引並查詢一個文檔
現在我們put一些數據到customer索引
請求:
curl -X PUT "localhost:9200/customer/_doc/1?pretty" -H 'Content-Type: application/json' -d'{"name": "1213William"}'
響應:
{ "_index" : "customer", "_type" : "_doc", "_id" : "1", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 0, "_primary_term" : 1 }
從上面的這個響應可以看出來,我們在customer索引下面成功創建了一個文檔,這個文檔還有一個內部id爲1,這是我們在創建的時候指定的
需要注意的是,Elasticsearch並不是要求你在索引文檔之前就先創建索引,然後才能將文檔編入索引。在前面的例子中,如果事先不存在customer索引,那麼Elasticsearch就會自動創建customer索引 --> 也就是說在新建文檔的時候如果指定的索引不存在就會自動創建相應的索引
現在我們重新檢索這個文檔
請求:
curl -X GET "localhost:9200/customer/_doc/1?pretty"
響應:
{ "_index" : "customer", "_type" : "_doc", "_id" : "1", "_version" : 1, "_seq_no" : 0, "_primary_term" : 1, "found" : true, "_source" : { "name" : "1213William" } }
在這個響應中可以看到除了found字段外沒有什麼不同,'_source'字段返回了一個完整的JSON文檔
刪除一個索引
現在讓我們刪除前面創建的索引,然後查看全部的索引
請求:
curl -X DELETE "localhost:9200/customer?pretty"
響應:
{ "acknowledged" : true }
接下來查看一下:
curl -X GET "localhost:9200/_cat/indices?v"
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
curl -X DELETE "localhost:9200/customer" # 刪除索引 curl -X GET "localhost:9200/customer/_doc/1" # 查詢文檔 curl -X PUT "localhost:9200/customer" # 創建索引 curl -X PUT "localhost:9200/customer/_doc/1" -H 'Content-Type: application/json' -d'{"name": "1213William"}' # 索引文檔
我們研究可以發現在Elasticsearch中的訪問數據的模式其實就是:
<REST Verb> /<Index>/<Type>/<ID>
修改數據
更新文檔
每當我們在執行更新的時候,Elasticsearch就會刪除舊的文檔,然後在索引一個新的文檔
下面這個例子展示瞭如何更新一個文檔(文檔的ID爲1),改變name字段爲"John",並且同時添加一個age字段
請求:
curl -X POST "localhost:9200/customer/_doc/1/_update?pretty" -H 'Content-Type: application/json' -d' { "doc": { "name": "John", "age": 18 } } '
響應:
{ "_index" : "customer", "_type" : "_doc", "_id" : "1", "_version" : 3, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 2, "_primary_term" : 2 }
下面在用一個例子用腳本將age增加5
請求:
curl -X POST "localhost:9200/customer/_doc/1/_update?pretty" -H 'Content-Type: application/json' -d' { "script" : "ctx._source.age += 5" } '
在這面"ctx._source.age"表示的是引用當前源文檔
刪除文檔
刪除文檔其實相當簡單。那就掩飾一下如何從customer索引中刪除ID爲2的文檔
請求:
curl -X DELETE "localhost:9200/customer/_doc/2?pretty"
響應:
{ "_index" : "customer", "_type" : "_doc", "_id" : "2", "_version" : 1, "result" : "not_found", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 4, "_primary_term" : 2 }
批處理
除了能夠索引、更新、刪除單個文檔之外,Elasticsearch黑可以使用_bulk API批量執行上述的任何操作
這個功能其實非常重,因爲它提供了一個非常有效的機制,可以在儘可能少的網絡中往返的情況下儘可能快的執行多個操作
下面的例子同時索引兩個文檔
請求:
curl -X POST "localhost:9200/customer/_doc/_bulk?pretty" -H 'Content-Type: application/json' -d' {"index":{"_id":"1"}} {"name": "John Doe" } {"index":{"_id":"2"}} {"name": "Jane Doe" } '
響應:
{ "took" : 17, "errors" : false, "items" : [ { "index" : { "_index" : "customer", "_type" : "_doc", "_id" : "1", "_version" : 5, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 5, "_primary_term" : 2, "status" : 200 } }, { "index" : { "_index" : "customer", "_type" : "_doc", "_id" : "2", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 6, "_primary_term" : 2, "status" : 201 } } ] }
更新第一個文檔,刪除第二個文檔
請求:
curl -X POST "localhost:9200/customer/_doc/_bulk?pretty" -H 'Content-Type: application/json' -d' {"update":{"_id":"1"}} {"doc": { "name": "1213William" } } {"delete":{"_id":"2"}} '
響應:
{ "took" : 50, "errors" : false, "items" : [ { "update" : { "_index" : "customer", "_type" : "_doc", "_id" : "1", "_version" : 6, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 7, "_primary_term" : 2, "status" : 200 } }, { "delete" : { "_index" : "customer", "_type" : "_doc", "_id" : "2", "_version" : 2, "result" : "deleted", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 8, "_primary_term" : 2, "status" : 200 } } ] }
檢索數據
示例數據
新建一個文件夾account.json,然後將數據複製粘貼到該文件中,保存並且退出在這個account.json文件夾所在目錄下面執行如下命令:
curl -H "Content-Type: application/json" -XPOST "localhost:9200/bank/_doc/_bulk?
pretty&refresh" --data-binary "@accounts.json"
這個時候account.json中的文檔數據就已經被索引到"bank"索引下
請求:
curl "localhost:9200/_cat/indices?v"
響應:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size yellow open bank kfOzQHy7Rg-FGzS_MV-6zw 1 1 1000 0 414.2kb 414.2kb yellow open customer SXDtQcKHSyKMwC4PR2proA 1 1 1 1 13.4kb 13.4kb
在這裏可以看到,現在我們的集羣中有兩個索引,分別是"bank", "customer",這裏面customer只有一個我們自己創建的文檔,但是bank卻有1000個
The Search API
運行搜索有兩種基本的方法:
- 通過REST請求URL發送檢索參數
- 通過REST請求體發送檢索參數
一種是把檢索參數放在URL後面,另一種是放在請求體的裏面。 間接的可以看成是HTTP的GET和POST請求
請求體方法允許你更有表現力,也可以用更可讀的JSON格式來定義搜索。用於搜索的REST API可以從_search端點訪問。
下面的例子返回的是bank索引中的所有中文文檔
curl -X GET "localhost:9200/bank/_search?q=*&sort=account_number:asc&pretty"
分析一下這個請求:
我們自bank索引中,q=*表示匹配所有的文檔
sort=account_number:asc並表示每個文檔的account_number字段升序排序;pretty參數表示返回漂亮打印的JSON結果
響應:
{ "took" : 6, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 1000, "relation" : "eq" }, "max_score" : null, "hits" : [ { "_index" : "bank", "_type" : "_doc", "_id" : "0", "_score" : null, "_source" : { "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" }, "sort" : [ 0 ] }, { "_index" : "bank", "_type" : "_doc", "_id" : "1", "_score" : null, "_source" : { "account_number" : 1, "balance" : 39225, "firstname" : "Amber", "lastname" : "Duke", "age" : 32, "gender" : "M", "address" : "880 Holmes Lane", "employer" : "Pyrami", "email" : "[email protected]", "city" : "Brogan", "state" : "IL" }, "sort" : [ 1 ] }, { "_index" : "bank", "_type" : "_doc", "_id" : "2", "_score" : null, "_source" : { "account_number" : 2, "balance" : 28838, "firstname" : "Roberta", "lastname" : "Bender", "age" : 22, "gender" : "F", "address" : "560 Kingsway Place", "employer" : "Chillium", "email" : "[email protected]", "city" : "Bennett", "state" : "LA" }, "sort" : [ 2 ] }, { "_index" : "bank", "_type" : "_doc", "_id" : "3", "_score" : null, "_source" : { "account_number" : 3, "balance" : 44947, "firstname" : "Levine", "lastname" : "Burks", "age" : 26, "gender" : "F", "address" : "328 Wilson Avenue", "employer" : "Amtap", "email" : "[email protected]", "city" : "Cochranville", "state" : "HI" }, "sort" : [ 3 ] }, { "_index" : "bank", "_type" : "_doc", "_id" : "4", "_score" : null, "_source" : { "account_number" : 4, "balance" : 27658, "firstname" : "Rodriquez", "lastname" : "Flores", "age" : 31, "gender" : "F", "address" : "986 Wyckoff Avenue", "employer" : "Tourmania", "email" : "[email protected]", "city" : "Eastvale", "state" : "HI" }, "sort" : [ 4 ] }, { "_index" : "bank", "_type" : "_doc", "_id" : "5", "_score" : null, "_source" : { "account_number" : 5, "balance" : 29342, "firstname" : "Leola", "lastname" : "Stewart", "age" : 30, "gender" : "F", "address" : "311 Elm Place", "employer" : "Diginetic", "email" : "[email protected]", "city" : "Fairview", "state" : "NJ" }, "sort" : [ 5 ] }, { "_index" : "bank", "_type" : "_doc", "_id" : "6", "_score" : null, "_source" : { "account_number" : 6, "balance" : 5686, "firstname" : "Hattie", "lastname" : "Bond", "age" : 36, "gender" : "M", "address" : "671 Bristol Street", "employer" : "Netagy", "email" : "[email protected]", "city" : "Dante", "state" : "TN" }, "sort" : [ 6 ] }, { "_index" : "bank", "_type" : "_doc", "_id" : "7", "_score" : null, "_source" : { "account_number" : 7, "balance" : 39121, "firstname" : "Levy", "lastname" : "Richard", "age" : 22, "gender" : "M", "address" : "820 Logan Street", "employer" : "Teraprene", "email" : "[email protected]", "city" : "Shrewsbury", "state" : "MO" }, "sort" : [ 7 ] }, { "_index" : "bank", "_type" : "_doc", "_id" : "8", "_score" : null, "_source" : { "account_number" : 8, "balance" : 48868, "firstname" : "Jan", "lastname" : "Burns", "age" : 35, "gender" : "M", "address" : "699 Visitation Place", "employer" : "Glasstep", "email" : "[email protected]", "city" : "Wakulla", "state" : "AZ" }, "sort" : [ 8 ] }, { "_index" : "bank", "_type" : "_doc", "_id" : "9", "_score" : null, "_source" : { "account_number" : 9, "balance" : 24776, "firstname" : "Opal", "lastname" : "Meadows", "age" : 39, "gender" : "M", "address" : "963 Neptune Avenue", "employer" : "Cedward", "email" : "[email protected]", "city" : "Olney", "state" : "OH" }, "sort" : [ 9 ] } ] } }
可以看到:
- took:Elasticsearch執行搜索的時間(這裏是以毫秒爲單位的)
- timed_out:告訴我們檢索時間是否超時
- _shards:告訴我們檢索了多少分片,以及成功/失敗的分片數各是多少
- hits:檢索的結果
- hits.total:符合檢索條件的文檔總數
- hits.hits:實際的檢索結果數組(默認是前十個文檔)
- hits.sort:排序的key(如果按照分值排序的話就不顯示)
- hits._score和max_score先忽略這些字段
下面一個是和上面一個相同,但是用請求體的例子:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d' { "query": { "match_all": {} }, "sort": [ { "account_number": "asc" } ] } '
區別:
我們沒有在URL中傳q=*,而是向_search API提供json風格的查詢請求體
很重要的一點就是,一旦返回搜索結果,Elasticsearch就完全完成了對請求的處理,不會在結果中維護任何類型的服務器資源或打開遊標
查詢語言
Elasticsearch提供了一種json風格的語言,你可以使用這種語言執行查詢。這被成爲查詢DSL
查詢語言非常全面,乍一看可能會很嚇人,但是實際上最好的學習方法就是從幾個基本的實例開始學習
我們執行這樣的查詢:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d' { "query": { "match_all": {} } } '
查詢部分告訴我們查詢定義是什麼,match_all部分只是我們想要運行的查詢類型。這裏match_all查詢只是在指定索引中搜索所有文檔
除了查詢參數之外,我們還可以傳遞其他參數來影響搜索的結果。在上面部分的例子中,我們傳的是sort參數,但是在這裏我們傳的是size:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d' { "query": { "match_all": {} }, "size": 1 } '
elasticsearch_mac