Elasticsearch學習筆記 一

本文版權歸博客園和做者吳雙本人共同全部 轉載和爬蟲請註明原文地址 www.cnblogs.com/tdws。html

本文參考和學習資料 《ES權威指南》node

當年在學校的隨筆 Lucene概念入門:  http://www.cnblogs.com/tdws/p/4263425.htmlweb

一.基本概念

存儲數據到ES中的行爲叫作索引,每一個索引能夠包含多個類型,這些不一樣的類型存儲着多個文檔,每一個文檔有多個屬性。數據庫

索引 index/indexes至關於傳統關係數據庫中的數據庫,是存儲關係型文檔的地方。json

索引在ES作動詞的時候,索引一個文檔就是存儲一個文檔到索引(名詞)中,以便被檢閱和查詢到。相似於insert。數組

默認下,一個文檔中的每一個屬性都是被索引的。沒被索引的屬性是不能搜索到的。網絡

二.ES集羣 主分片 副分片 健康狀態

垂直的硬件擴容是有極限的,真正的擴容能力來自於水平擴容(爲集羣內增長更多節點),集羣是由一個或者多個擁有相同 cluster.name 配置的節點組成, 它們共同承擔數據和負載的壓力。當有節點加入集羣中或者從集羣中移除節點時,集羣將會從新平均分佈全部的數據。併發

主節點負責管理集羣範圍內全部的變動,增長刪除索引和增長刪除節點等。主節點不涉及文檔級別的變動和搜索等操做。任何一個節點均可接受請求,任何一個節點都知道任意文檔所處位置。app

curl -XGET 'localhost:9200/_cluster/health?pretty' 獲取集羣健康狀態負載均衡

status 字段指示着當前集羣在整體上是否工做正常。它的三種顏色含義以下:

green 全部的主分片和副本分片都正常運行。

yellow 全部的主分片都正常運行,但不是全部的副本分片都正常運行。

red 有主分片沒能正常運行。

分片是一個底層的工做單元,是數據的容器,一個分片是一個Lucene實例。它自己就是一個完整的搜索引擎,到本文最後的分頁,相信你能更加理解。應用不會與分片交互。一個分片可使主分片或者副本分片。副本分片只是主分片的一個拷貝。副本分片用於從災難中保護數據並未搜索讀取操做提供服務,注意只是讀操做。

默認狀況下建立一個索引,會被分配5個分片。固然咱們也能夠指定分片數量還有副本數量,好比:

curl -XPUT 'localhost:9200/blogs?pretty' -H 'Content-Type: application/json' -d'
{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}

假設咱們目前集羣中只有一個節點,那麼剛纔三個分片的存儲是這樣的。即便咱們設置了1個副本分片也沒用,由於相同數據的主分片和副本分片在一個節點上沒有任何意義。

若是此時,咱們爲集羣增長一個節點,主分片和副本分片是下圖這樣的。副本分片得以建立,並分配在新節點上。如今咱們的容災能力就能夠容許失去一臺節點的數據,數據讀取服務節點就是兩個。

接下來,集羣中再加入節點,狀況以下圖所示。爲了提升更強的服務能力和容災能力,均勻分佈了主分片和副本分片,如今咱們三個node,失去任意一臺,都能保障數據和服務。

最後,關閉node1,使集羣失去MASTER, 而集羣必須擁有一個主節點來保證正常工做,因此發生的第一件事情就是選舉一個新的主節點: Node 2 。在失去Master的同時,也失去了主分片 1 和 2 ,而且在缺失主分片的時候索引也不能正常工做。 若是此時來檢查集羣的情況,咱們看到的狀態將會爲 red :不是全部主分片都在正常工做。幸運的是,在其它節點上存在着這兩個主分片的完整副本, 因此新的主節點當即將這些分片在 Node 2 和 Node 3 上對應的副本分片提高爲主分片, 此時集羣的狀態將會爲 yellow 。 這個提高主分片的過程是瞬間發生的,如同按下一個開關通常。

若是如今恢復node1,若是 Node 1 依然擁有着以前的分片,它將嘗試去重用它們,同時僅從主分片複製發生了修改的數據文件

三.基本的操做

一個文檔的 _index 、 _type 和 _id 惟一標識一個文檔。

索引僅僅是一個邏輯上的命名空間,這個命名空間由一個或者多個分片組合在一塊兒。對於應用程序而言,無需關注分片,只須要知道一個文檔位於一個索引內。

索引index名稱必須小寫,不能如下劃線開頭,不能有逗號。類型type名稱能夠大寫或者小寫,可是不能如下劃線或者句號開頭,不該該包含逗號而且長度在265字符及之內。

當你建立一個新文檔的時候,要麼主動給id,要麼讓ES自動生成。因此,確保建立一個新文檔的最簡單辦法是,使用索引請求的 POST 形式讓 Elasticsearch 自動生成惟一 _id,好比:

POST /website/blog/ { ... }

如下兩種方式爲等效操做:

PUT /website/blog/123?op_type=create { ... }
PUT /website/blog/123/_create { ... }

在獲取文檔的時候,_source字段是用於篩選咱們只想要的目標字段,好比

GET /website/blog/123?_source=title,text

若是想要獲得http響應頭部,能夠經過傳遞 -i 好比

curl -i -XGET http://localhost:9200/website/blog/124?pretty

若是隻想獲得source字段,不想獲得任何其餘元數據,使用方式以下:

GET /website/blog/123/_source

若是查詢文檔是否存在,使用Http HEAD,好比:

curl -i -XHEAD http://localhost:9200/website/blog/123  //存在則返回200 OK,不存在則是404Not Found

在 Elasticsearch 中文檔是 不可改變 的,不能修改它們。 相反,若是想要更新現有的文檔,須要 重建索引或者進行替換, 咱們可使用相同的 index API 進行實現。和使用建立時PUT是同樣的,ES內部都作了。部分更新,也是如此。使用DELETE刪除文檔,刪除文檔不會當即將文檔從磁盤中刪除,只是將文檔標記爲已刪除狀態,隨着你不斷的索引更多的數據,Elasticsearch 將會在後臺清理標記爲已刪除的文檔。

四.併發衝突的解決

 

假設上圖操做的是庫存數量stock_count ,web_1 對 stock_count 所作的更改已經丟失,由於 web_2 不知道它的 stock_count 的拷貝已通過期。變動越頻繁,讀數據和更新數據的間隙越長,也就越可能丟失變動。在數據庫領域中,有悲觀和樂觀兩種方法一般被用來確保併發更新時變動不會丟失:

悲觀併發控制

悲觀併發控制通常鎖住當前被操做的資源。

樂觀併發控制

Elasticsearch 中使用的這種方法假定衝突是不可能發生的,而且不會阻塞正在嘗試的操做。 然而,若是源數據在讀寫當中被修改,更新將會失敗。應用程序接下來將決定該如何解決衝突。 例如,能夠重試更新、使用新的數據、或者將相關狀況報告給用戶。Elasticsearch 是分佈式的,當建立和更新文檔的時候,若是你是多個node,而且還有replication備份數據,這中間就會有時間差存在,而且還會存在檢索-修改-重建索引間隔。或者說併發更新發生的時候,所獲取並在處理中的數據,可能已經被其餘請求更新掉,這就致使數據的過時,結果就是覆蓋更新,過時數據覆蓋新數據。在ES中 咱們解決此問題的方式就是利用更新中指定_version版本號來確保相互衝突的更新不會丟失。全部文檔的更新或刪除 API,均可以接受 version 參數,好比:

PUT /website/blog/1?version=1

 過指定了version,若是請求到達ES後,該條數據version已經不是1了,這時就會更新失敗並返回409 Conflict響應碼。以後應該怎麼處理,就取決於你的場景了,正如上面所說的能夠重試更新、使用新的數據、或者將相關狀況報告給用戶。另一種方式是使用外部版本號,而非ES內置自增的版本號。外部的版本號源於你本身的系統控制,好比更新一條ES文檔,指定使用外部版本號爲5:

PUT /website/blog/2?version=5&version_type=external

爲了併發控制,下一次你的請求給到了version爲10若是你的請求version小於等於上面的5則會更新失敗,像前面提到的409同樣,只有新version大於ES文檔已有version,更新纔會成功。

PUT /website/blog/2?version=10&version_type=external

ES文檔是不可變的,即便是在部分更新狀況下。好比:

POST /website/blog/1/_update { "doc" : { "tags" : [ "testing" ], "views": 0 } }   //其將會覆蓋現有字段,增長新字段。被稱爲Update API

部分更新的時候,若是被更新的文檔還不存在,這時應該使用upsert參數

POST /website/pageviews/1/_update { "script" : "ctx._source.views+=1", "upsert": { "views": 1 } }

若是你某個場景中常常會出現所更新文檔不存在的狀況下,那麼使用它是明智之選,該方式會在文檔存在的時候,直接執行更新腳本將值應用,不存在的時候,則執行upsert.

ES update API還提供了retry_on_conflict參數,指示了返回失敗以前,要重試多少次。

POST /website/pageviews/1/_update?retry_on_conflict=5 

update API和前面index API所提到的樂觀併發控制同樣,也支持制定version參數來控制衝突。

ES提供了mget API (multi-get),能夠減小網絡傳輸時間等,好比:

GET /_mget { "docs" : [ { "_index" : "website", "_type" : "blog", "_id" : 2 }, { "_index" : "website", "_type" : "pageviews", "_id" : 1, "_source": "views" } ] }

能夠看到這段代碼中index和type 實在docs數組的item項中。所獲得的響應數組中,每一個item和使用單個get拿到的結果是同樣的。

若是想檢索的數據都在相同的 _index 中(甚至相同的 _type 中),則能夠在 URL 中指定默認的 /_index或者默認的 /_index/_type 。

你仍然能夠經過單獨請求覆蓋這些值:

GET /website/blog/_mget { "docs" : [ { "_id" : 2 }, { "_type" : "pageviews", "_id" : 1 } ] }

事實上,若是全部文檔的 _index 和 _type 都是相同的,你能夠只傳一個 ids 數組,而不是整個 docs 數組:

GET /website/blog/_mget { "ids" : [ "2", "1" ] }

若是數組中某個尋找目標未找到結果,則響應數組中,其餘值正常,未找到的found字段則爲false "found" : false,並不妨礙其餘文檔。因此在應用中,應該檢查的不是200仍是404,而是found字段標記。

既然提供了mget, 同時bulk API也提供了單個步驟進行屢次create index, update和delete請求。

POST /_bulk {
"delete": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }} { "title": "My first blog post" }
{ "index": { "_index": "website", "_type": "blog" }} { "title": "My second blog post" }
{ "update": { "_index": "website", "_type": "blog", "_id": "123", "_retry_on_conflict" : 3} } { "doc" : {"title" : "My updated blog post"} }

delete是沒有請求體的,而create,update 請求體是必須的 如上所示。若是其中哪一個操做失敗了,則會在響應中的一個對應的item中給出相應的error. 而成功的則給出200.好比

{ "took": 3, "errors": true, "items": [ 
{ "create": { "_index": "website", "_type": "blog", "_id": "123", "status": 409, "error": "DocumentAlreadyExistsException [[website][4] [blog][123]: document already exists]" }},
{ "index": { "_index": "website", "_type": "blog", "_id": "123", "_version": 5, "status": 200 }} ] }

bulk 不是原子性的,不能用它來實現事務控制。每一個請求是單獨處理的,所以一個請求的成功或失敗不會影響其餘的請求。爲何是下面這種格式的寫法,爲何是每一個請求單獨處理,這是由於不一樣的文檔數據,他們主分片可能不一樣,每一個文檔可能被分配給不一樣節點,因此ES這種設計的方式是最明智的。若是使用json數組,看起來簡便了,實則致使須要ES解析大量數據,佔用更多內存,讓JVM來花大量時間進行回收。相反ES現有的方式,則能夠直接將原始請求轉發到正確的分片上,用最小的內存處理。

也許你正在批量索引日誌數據到相同的 index 和 type 中。 但爲每個文檔指定相同的元數據是一種浪費。相反,能夠像 mget API 同樣,在 bulk 請求的 URL 中接收默認的 /_index 或者 /_index/_type ,好比:

POST /website/_bulk { "index": { "_type": "log" }} { "event": "User logged in" }
POST /website/log/_bulk { "index": {}} { "event": "User logged in" } { "index": { "_type": "blog" }} { "title": "Overriding the default type" }

五.分佈式文檔存儲

 當索引一個文檔的時候,文檔將被存儲到ES的一個主分片當中。ES索引文檔到分片聽從公式:shard = hash(routing) % number_of_primary_shards 。

routing是一個可變值,默認你爲文檔_id。 如上公式hash出的結果是  0 到 number_of_primary_shards-1 之間的餘數,未來咱們尋找數據所在分片,就是這樣找到的。因此在建立索引的時候就要肯定好主分片數量,而且永遠不改變primary_shards ,由於一旦改變了,以前hash路由的值將所有無效,文檔也就不能被正確找到。固然若是你想擴容,能夠有其餘的奇技淫巧,後面將會提到。

好比咱們有三個節點,兩個主分片,每一個主分片有兩個副本。咱們依然能夠將請求指向任意節點,每一個節點都知道任一文檔的位置,並轉發請求到目標節點。爲了擴展負載,更好的作法是輪詢全部節點,以單節點免壓力過大。我想,ES知道任一文檔的位置,經過上一段給出的公式便可。

向node1發送存儲數據請求,路由到node3主分片上,複製給node1和node2,最後返回結果給node1

 

向node1 發送更新數據請求,路由到node3主分片,更改_source,從新索引數據,成功後複製到node1和node2。若是是部分更新的時候,通知副本節點更新的時候,不是轉發部分更新的內容,而是轉發完整文檔的新版本。

 

六.搜索

上面,咱們能夠簡單的把ES看成NOSQL風格的分佈式文檔存儲系統。咱們能夠將文檔扔到ES裏,而後根據ID檢索。但其真正的強大之處並不在於此,而是從無規律的數據找出有意義的信息,從大數據到大信息。

搜索(search) 能夠作到:

1.在相似於 gender 或者 age 這樣的字段 上使用結構化查詢,join_date 這樣的字段上使用排序,就像SQL的結構化查詢同樣。

2.全文檢索,找出全部匹配關鍵字的文檔並按照相關性(relevance) 排序後返回結果。

3.以上兩者兼而有之。

下面分析一則查詢:

GET /_search (curl -XGET 'localhost:9200/_search?pretty')
{ "hits" : { "total" : 14, "hits" : 
[ { "_index": "us", "_type": "tweet", "_id": "7", "_score": 1,
"_source": { "date": "2014-09-17", "name": "John Smith", "tweet": "The Query DSL is really powerful and flexible", "user_id": 2 } },
... 9 RESULTS REMOVED ... ], "max_score" : 1 },
"took" : 4,
"_shards" : { "failed" : 0, "successful" : 10, "total" : 10 }, "timed_out" : false }

 

解釋一下查詢結果:

hits是返回結果最重要的部分,其中total表明匹配總數,每一個文檔都有元數據_index庫,_type類型,_id編號,_source值字段,_score表明相關性,文檔按照相關性倒序返回。

_took字段表明搜索花了多少ms .

_shards告訴咱們參與分片的總數和成功與失敗多少個。正常狀況下不會出現失敗,若是遇到災難故障好比同一分片原始數據和副本都丟失了,那麼對這個分片將沒有可用副本作響應,ES則會報告有失敗的分片,但正確返回剩餘分片結果。

time_out指示了是否查詢超時,在查詢的時候,你也能夠設置超時時間 GET /_search?timeout=10ms 。關於超時須要特別注意的是,超時並非中止執行查詢,而是告知正在協調節點,在所設置的時間內,返回目前已查詢收集到的的結果。在後臺,即便結果已經返回給咱們了,可是查詢仍然在執行。爲何說是返回已收集到的文檔,是由於在集羣內搜索的時候,協調節點轉發請求到其餘節點的主分片或者副分片上,聚集結果。

不記得什麼是協調節點了嗎?Elasticsearch集羣中做爲客戶端接入的節點叫協調節點。協調節點會將客戶端請求路由到集羣中合適的分片上。對於讀請求來講,協調節點每次會選擇不一樣的分片處理請求,以實現負載均衡。

一些在一個或多個特殊的索引而且在一個或者多個特殊的類型中進行搜索查詢的示範:
/_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 類型

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

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

ES返回分頁數據的思路是 : 好比在一個有 5 個主分片的索引中搜索。 當咱們請求結果的第一頁(結果從 1 到 10 ),每個分片產生前 10 的結果,而且返回給 協調節點 ,協調節點對 50 個結果排序獲得所有結果的前 10 個。

這也說明在深度分頁中,所將付出的代價,好比獲取第1000頁,那麼每一個節點查詢出10010條,最後到協調節點中,從50050條拋棄50040條取10條. 補充一句 爲何每一個分片能夠作咱們想要的查詢,想要的數量和結果?是由於每一個分片都是一個Lucene實例,是一個完整的搜索引擎

相關文章
相關標籤/搜索