1. 基本概念回顧
1.1. Node
節點是一個伺服器,它是叢集的一部分,儲存資料,並參與叢集的索引和搜尋功能
節點有一個名稱標識,該名稱在預設情況下是在啟動時分配給節點的隨機全域性惟一識別符號(UUID)
這個名稱對於管理非常重要,因為你希望識別網路中的哪些伺服器與Elasticsearch叢集中的哪些節點相對應
預設情況下,每個節點都被設定為連線一個名為elasticsearch的叢集,這意味著如果您在網路上啟動多個節點,並且假設它們可以發現彼此,那麼它們都會自動形成並連線一個名為elasticsearch的叢集。
1.2. Index
文件(document)的集合就是索引(Index)
1.3. Type
當你想要在同一個index中儲存不同型別的documents時,type用作這個index的一個邏輯分類/分割槽。比如,在一個索引中,使用者資料是一個type,帖子是另一個type。在後續的版本中,一個index將不再允許建立多個types,而且整個types這個概念都將被刪除。
(PS:type是index的一個邏輯分類(或者叫分割槽),在當前的版本中,它仍然用於在一個索引下區分不同型別的資料。但是,不建議這樣做,因為在後續的版本中type這個概念將會被移除,也不允許一個索引中有多個型別。)
1.4. Document
一個document就是index中的一條記錄,它是JSON格式的
1.5. Shards & Replicas (分片與副本)
索引可能儲存大量資料,這些資料可能超過單個節點的硬體限制。例如,一個包含10億個文件、佔用1TB磁碟空間的索引可能不適用於單個節點的磁碟,或者速度太慢,無法單獨處理來自單個節點的搜尋請求。為了解決這個問題,Elasticsearch提供了將索引細分為多個碎片的功能,每個碎片稱之為 shard。
建立索引時,您可以簡單地定義所需的分片數量。每個分片本身就是一個功能完整且獨立的“index”,可以駐留在叢集中的任何節點上。
分片之所以重要,主要有兩個原因:
- 允許水平擴容
- 允許分散式儲存和並行操作,從而提高效能/吞吐量
shard如何分佈以及如何將其文件聚合回搜尋請求的機制完全由Elasticsearch管理,對於使用者來說是透明的。
副本指的是分片的副本,是shard的複製
複製之所以重要,主要有兩個原因:
- 在shard/node失敗的時候,它提供高可用性。正因為如此,複製的shard(簡稱shard的副本)絕不會跟原始shard在同一個節點上
- 它允許擴充套件搜尋量/吞吐量,因為搜尋可以並行地在所有副本上執行
(It provides high availability in case a shard/node fails. For this reason, it is important to note that a replica shard is never allocated on the same node as the original/primary shard that it was copied from.)
總而言之,每個索引可以被分成多個碎片
索引還可以被複制0次(即沒有副本)或更多次
複製之後,每個索引都將擁有主分片(原始分片)和 副本分片(主分片的副本)
1.6. 小結 & 回顧:
- node是一臺伺服器,表示叢集中的節點
- document表示索引記錄
- 一個index中不建議定義多個type
- 一個index可以有多個shard,每個shard可以有0個或多個副本
- original shard (原始shard,或者叫 primary shard)的複製成為副本shard,簡稱shard
- 主分片和副本決不會在同一個節點上
- 分片的好處主要有兩個:第一,突破單臺伺服器的硬體限制;第二,可以並行操作,從而提高效能和吞吐量;(PS:其實跟kafka差不多)
- 副本的好處主要在於:第一,提供高可用;第二,並行提升效能和吞吐量
- 一個索引包含一個或多個分片,索引記錄(即文件)資料儲存在這些shard中,且一個文件只會存在於一個分片中
- 每個shard都是一個獨立的功能完善的“index”,意思是它可以獨立處理索引/搜尋請求
(注意:本文中提到的分片指的是主分片(primary shard),而不是副本(replica shard))
2. 讀寫文件
在Elasticsearch中,每個索引都被劃分為分片,每個分片可以有多個副本。這些副本稱為複製組,在新增或刪除文件時必須保持同步。如果我們做不到這一點,從一個副本中讀取的結果將與從另一個副本中讀取的結果非常不同。保持碎片副本同步並提供從中讀取的服務的過程稱為資料複製模型。
Elasticsearch的資料複製模型基於主備份模型,該模型中有一個主分片,以及從主分片那裡複製的複製組,這些稱之為複製分片。主(伺服器)節點作為所有索引操作的主要入口點,它負責驗證並確保操作是正確的。一旦主伺服器接受了索引操作,主伺服器還負責將該操作複製到其他副本。
2.1. Basic write model (基本的寫模型)
在Elasticsearch中,每個索引操作首先會通過路由(通常是基於 document ID)解析到一個複製組(PS:這裡解析到複製組的意思是定位到索引的哪個分片)。一旦確定了複製組,該操作將在內部轉發到該組的當前主分片。主分片負責驗證操作並將其轉發到其他副本。由於副本可以是下線狀態(PS:不線上),因此主分片不需要將該操作複製到所有副本。代替的,Elasticsearch維護應該接收操作的分片副本列表,這個列表稱為同步副本,由主節點(PS:master node)維護。顧名思義,這些是一組“良好的”分片副本,它們保證處理了使用者已確認的所有索引和刪除操作。主伺服器負責維護這個不變數(PS:指的是同步副本列表),因此必須將所有操作複製到這個集合中的每個副本。
主分片的基本流程如下:
- 校驗輸入操作,並且如果結構無效(PS:比如欄位型別錯誤等等)時拒絕該操作
- 在本地執行操作,即索引或刪除相關文件。這也將驗證欄位的內容,並在需要時拒絕
- 將操作轉發到當前同步副本集中的每個副本。如果有多個副本,則並行執行此操作
- 一旦所有副本成功地執行了操作並響應了主副本(PS:只的主分片),主副本就向客戶端確認請求已成功完成
2.1.1. 失敗處理
如果主分片失敗的話,那麼它所在伺服器節點將想主伺服器節點(master node)傳送一條關於該主分片的訊息。索引操作將等待(預設情況下最多1分鐘,具體看 Dynamic index settings)主節點(master)將其中一個副本提升為新的主副本。然後將操作轉發到新的主分片進行處理。注意,主節點(master)還負責監視節點的健康狀況,並可能決定主動降級主副本。當持有主副本的節點因網路問題與叢集隔離時,通常會發生這種情況。
在主分片上成功執行操作之後,主分片在副本分片上執行操作時必須處理潛在的故障。這可能是由於副本上的實際故障,或者由於網路問題導致操作無法到達副本(或阻止副本響應)。所有這些最終結果是:作為同步複製集的一部分的副本會丟失即將被確認的操作。為了避免違反不變數,主分片的宿主伺服器向主伺服器傳送一條訊息,請求從同步副本集中刪除有問題的分片。(PS:跟Kafka的副本同步有點兒像)一旦主伺服器確認分片副本的移除後,主分片才會確認這個寫請求操作。
在將操作轉發到副本時,主分片將使用副本驗證它仍然是活的主分片。如果主分片由於網路分割槽(或長時間GC)而被隔離,它可能會在意識到它已經降級之前繼續處理輸入的索引操作,然後將操作路由到新的主伺服器。
2.2. Basic read model (基本的讀模型)
Elasticsearch中的讀取可以是非常輕量級的ID查詢,也可以是具有複雜聚合(佔用大量CPU資源)的大型搜尋請求。主備份模型的優點之一是它保持所有分片副本相同,因此,一個同步副本就足以滿足讀取請求。
當一個節點接收到讀請求時,該節點負責將其轉發到持有相關分片的節點、整理響應並響應客戶端。我們將該節點稱為該請求的協調節點。
基本流程如下:
- 解析這個讀請求到相關的分片
- 從這個分片的複製組中選擇一個活的分片,這個活的分片可以是主分片,也可以是複製分片(副本)。預設情況下,Elasticsearch只是簡單地在副本之間進行輪詢。
- 傳送分片級別的讀請求給選中的副本
- 聚合結果並對客戶端作出響應
2.2.1. 失敗處理
當分片未能響應讀取請求時,協調節點將從相同的複製組中選擇另一個副本,並將分片級搜尋請求傳送到該副本。重複失敗可能導致沒有分片副本可用。在某些情況下,例如_search, Elasticsearch更喜歡快速響應,儘管會得到部分結果,而不是等待問題得到解決(部分結果在響應的_shards頭中表示)。
2.2.2. 高效地讀
在正常操作下,對每個相關複製組執行一次讀操作。只有在失敗的條件下,相同分片的多個副本才能執行相同的搜尋。
2.2.3. 讀未確認
由於先在主分片上寫,然後複製請求,因此併發讀取可能在確認更改之前就已經看到更改。
2.2.4. 預設兩個副本
當只維護資料的兩個副本時(number_of_replicas預設是1),該模型可以容錯。
2.3. 小結 & 回顧
1、一個索引(index)有多個分片(shard),每個分片(primary shard)有多個副本(replica shard),主分片和它的副本稱組成該分片的複製組
2、資料複製模型基於主備份模型
寫
1、首先,計算資料在哪個分片上,這個過程稱之為路由,通常是根據文件ID來計算的
2、將請求轉到相應的節點上,主分片校驗請求,然後在本地執行,隨後將請求轉發到同步副本集中的每個副本上,多個副本是並行執行的,當所有副本都執行完成以後,主副本向客戶端作出響應
3、每個分片都有一個同步副本集,它由master節點維護
4、如果主分片操作失敗,則該分片的節點立即向master節點傳送一條關於該分片的訊息,然後master節點將從它的副本中選出一個作為主分片,並且將請求轉到新的主分片上執行
5、如果主分片操作成功,副本操作失敗時,則改分片的節點會向master節點請求將改副本從同步副本集中移除,待master確認以後主分片就可以想要客戶端了
6、如果主分片操作成功,但是由於網路分割,導致主分片與叢集的連線斷開了,那麼在選出新的主分片之前元主分片繼續處理請求,一旦選出新的分片後,原主分片不再試主分片,就不能再接收請求了
讀
1、解析讀請求到相應的分片(PS:路由,即計算資料在哪個分片上)
2、從分片的複製組中選擇一個分片(PS:預設選擇的演算法是在副本之間輪詢)
3、給選中的分片發請求
4、獲取分片完成請求後的響應結果,並響應客戶端
5、接收客戶端請求的那個結點負責將請求轉發到相應分片結點上,聚合各節點的響應結果,並響應客戶端,該結點成為本次請求的協調節點
6、如果分片未能正常響應,協調節點將從相同的複製組中選擇另一個副本,並將分片級搜尋請求傳送到該副本
7、一個讀請求只會在一個副本上執行,只有當執行失敗的時候,才會換另外一個副本上執行
8、併發讀寫可能會讀到髒資料
3. Index API
下面這個例子,向"twitter"索引中插入一條id為1的文件,並且是在"_doc"型別下
curl -X PUT "localhost:9200/twitter/_doc/1" -H 'Content-Type: application/json' -d' { "user" : "kimchy", "post_date" : "2009-11-15T14:12:12", "message" : "trying out Elasticsearch" } '
響應結果可能是這樣的:
{ "_shards" : { "total" : 2, "failed" : 0, "successful" : 2 }, "_index" : "twitter", "_type" : "_doc", "_id" : "1", "_version" : 1, "_seq_no" : 0, "_primary_term" : 1, "result" : "created" }
_shards欄位中提供了關於索引操作的複製過程的資訊
- total 表明索引操作應在多少分片(主分片和副本分片)上執行
- successful 表示成功執行的複製數量(PS:successful至少是1)
- failed 在索引操作在副本分片上執行失敗的情況下,包含複製相關錯誤的陣列
(PS:主分片 primary shard ;副本分片 replica shard )
3.1. 自動建立索引
如果在執行index api之前沒有建立索引的話,那麼該操作會自動建立索引,並自動建立一個動態型別對映(mapping)。
對映本身非常靈活,而且沒有模式。新的欄位和物件將自動新增到指定型別的對映定義中。
可以通過將 action.auto_create_index 設定為false來禁用自動建立索引,將 index.mapper.dynamic 設定為false來禁用自動對映。
3.2. 版本控制
每個被索引的文件都有一個版本號。版本號(version)在index API請求的響應中返回。版本號主要用於併發控制。
curl -X PUT "localhost:9200/twitter/_doc/1?version=2" -H 'Content-Type: application/json' -d' { "message" : "elasticsearch now has versioning support, double cool!" } '
上面這個例子,如果ID為1的文件的版本號是2,則更新其message自動為指定的內容,如果版本不是2,則不會執行更新操作,反而會報錯。
現在執行失敗,是因為當前ID為1的文件版本號是1,因此執行這個請求會失敗。如果我們將該請求後面的版本號改成1,則會成功。
(PS:這其實就是CAS,比較並交換)
(PS:樂觀鎖)
預設情況下,內部版本號從1開始,並且在每次更新和刪除的時候版本號都會遞增。當然,也可以手動指定。為了可以手動指定版本號,應該將version_type指定為external。這個值必須是一個長整型的數值或者為0。
3.2.1. 版本型別
下面是一些不同的版本型別:
- internal 只索引給定版本號與文件儲存的版本號相同的文件。(PS:換言之,只有當給定的版本號與文件儲存的版本號相同時,才會索引該文件,這裡索引操作指的是更新、刪除)
- external 或者 external_gt 只索引那些文件儲存的版本號比給定版本號小或者不存在的文件,同時給定的版本號會作為文件的新版本號。(PS:換言之,只有當給定的版本號比文件儲存的版本號大或者文件不存在時,才會執行)
- external_gte 只索引給定版本號大於或等於儲存文件的版本號的那些檔案,如果文件不存在的話這個操作也會成功。(PS:換言之,只有當給定版本號大於或等於儲存文件的版本號時,才會執行)
(PS:其實很好理解,無非就是給定的版本號與文件當前版本號的一個比較,internal是相等的時候才執行,external是大於的時候才執行,external_get是大於或等於的時候才執行)
3.3. 操作型別
索引操作也可以接受一個 op_type 引數用於強制建立操作。例如:
curl -X PUT "localhost:9200/twitter/_doc/1?op_type=create" -H 'Content-Type: application/json' -d' { "user" : "kimchy", "post_date" : "2009-11-15T14:12:12", "message" : "trying out Elasticsearch" } '
或者,另一種寫法也可以
curl -X PUT "localhost:9200/twitter/_doc/1/_create" -H 'Content-Type: application/json' -d' { "user" : "kimchy", "post_date" : "2009-11-15T14:12:12", "message" : "trying out Elasticsearch" } '
上面的例子,如果文件已經存在,則操作失敗
3.4. 自動ID生成
索引操作可以在不指定id的情況下執行。在這種情況下,將自動生成id。此外,op_type將自動設定為create。下面是一個例子(注意這裡用POST代替PUT):
curl -X POST "localhost:9200/twitter/_doc/" -H 'Content-Type: application/json' -d' { "user" : "kimchy", "post_date" : "2009-11-15T14:12:12", "message" : "trying out Elasticsearch" } '
返回結果如下:
{ "_shards":{ "total":2, "failed":0, "successful":2 }, "_index":"twitter", "_type":"_doc", "_id":"W0tpsmIBdwcYyG50zbta", "_version":1, "_seq_no":0, "_primary_term":1, "result":"created" }
3.5. 路由
預設情況下,路由(routing)控制是通過文件ID的雜湊值來做的。對於顯式的控制,可以使用路由引數直接在每個操作的基礎上指定輸入到路由器使用的雜湊函式中的值。例如:
curl -X POST "localhost:9200/twitter/_doc?routing=kimchy" -H 'Content-Type: application/json' -d' { "user" : "kimchy", "post_date" : "2009-11-15T14:12:12", "message" : "trying out Elasticsearch" } '
上面的例子中,_doc型別下的文件究竟被路由到哪個分片(shard)上是基於路由引數提供的值kimchy
3.6. 分佈
索引操作根據它的路由定向到主分片,並在包含該分片的實際節點上執行。在主分片完成操作之後,如果需要,更新將被分發到適用的副本。
3.7. 超時
預設情況下,索引操作在主分片上最多等待1分鐘,然後失敗並以錯誤進行響應。可以使用timeout引數顯式指定它等待的時間。
下面這個例子,設定超時時間是5分鐘:
curl -X PUT "localhost:9200/twitter/_doc/1?timeout=5m" -H 'Content-Type: application/json' -d' { "user" : "kimchy", "post_date" : "2009-11-15T14:12:12", "message" : "trying out Elasticsearch" } '
4. Get API
下面這個例子從一個名字叫“twitter”的索引中,型別為“_doc”之下,獲取id為0的JSON文件:
curl -X GET "localhost:9200/twitter/_doc/0"
其返回的結果可能是這樣的:
{ "_index" : "twitter", "_type" : "_doc", "_id" : "0", "_version" : 1, "found": true, "_source" : { "user" : "kimchy", "date" : "2009-11-15T14:12:12", "likes": 0, "message" : "trying out Elasticsearch" } }
你還可以用HEAD操作單純的只是檢查某個文件是否存在,例如:
curl -X HEAD "localhost:9200/twitter/_doc/0"
預設情況下,Get 操作是實時的。也就是說,如果文件已經被更新,但是索引還沒重新整理,那麼get操作會呼叫重新整理
4.1. Source欄位過濾
預設情況下,get操作的返回中包含 _source 欄位,你可以手動關閉它。例如:
curl -X GET "localhost:9200/twitter/_doc/0?_source=false"
如果只想顯示_source中的某些欄位,可以這樣簡短的表示:
curl -X GET "localhost:9200/twitter/_doc/0?_source=*.id,retweeted"
4.2. 路由
curl -X GET "localhost:9200/twitter/_doc/2?routing=user1"
注意,如果你帶了routing引數,而且還路由值還帶錯了,那麼將找不到文件
5. ?refresh
Index , Update ,Delete等操作支援設定 refresh 引數來控制什麼時候改變對搜尋可見。(PS:意思了,對文件做了更新以後,什麼時候這個更新可以被檢索的時候看到)
refresh引數的值可以是下列之一:
- 空字串 或者 true : 操作發生後立即重新整理相關的主分片和副本分片(不是整個索引),以便更新後的文件立即出現在搜尋結果中。
- wait_for : 在回覆之前,等待請求所做的更改被重新整理。這並不強制立即重新整理,而是等待重新整理發生。Elasticsearch自動重新整理已更改每個索引的分片。refresh_interval,預設為1秒。
- false(預設) : 不要執行重新整理相關操作。此請求所做的更改將在請求返回後的某個時點可見。
下面是一些例子:
curl -X PUT "localhost:9200/test/_doc/1?refresh" -H 'Content-Type: application/json' -d' {"test": "test"} ' curl -X PUT "localhost:9200/test/_doc/2?refresh=true" -H 'Content-Type: application/json' -d' {"test": "test"} '
建立一個文件,並立即重新整理
curl -X PUT "localhost:9200/test/_doc/3" -H 'Content-Type: application/json' -d' {"test": "test"} ' curl -X PUT "localhost:9200/test/_doc/4?refresh=false" -H 'Content-Type: application/json' -d' {"test": "test"} '
只是建立一個文件,其它的什麼也不做
curl -X PUT "localhost:9200/test/_doc/4?refresh=wait_for" -H 'Content-Type: application/json' -d' {"test": "test"} '
建立一個文件,並等待重新整理
6. 參考
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-replication.html