前言
當索引一個文檔的時候,文檔會被存儲到一個主分片中。那么,elasticsearch如何知道一個文檔應該存放到哪個分片中呢?
首先這肯定不是隨機的,否則在檢索文檔時就不知道該從哪去尋找它了。實際上這個過程是根據下面公式決定的:
shard = hash(routing) % number_of_primary_shards
routing是一個可變值,默認是文檔的_id,也可以是自定義的值。hash函數將routing值哈希后生成一個數字,然后這個數字再除以number_of_primary_shards(主分片的數量)得到余數,這個分布在0到number_of_primary_shards減一(計數從0開始,比如5個主分片,那么范圍就是0~4)之間的余數,就是文檔存放的分片位置。
比如一篇文檔的id為123,那么它就應該存在:
>>> hash(123) % 5
3
這篇文檔就存在P3主分片上。
這也就解釋了為什么在創建索引時,主分片的數量一經定義就不能改變,因為如果數量變化了,那么之前所有的路由(routing)值都會無效,文檔就再也找不到了。
一般的,elasticsearch的默認路由算法都會根據文檔的id值作為依據將其哈希到相應的主分片上,該算法基本上會將所有的文檔平均分布在所有的主分片上,而不會產生某個分片數據過大而導致集群不平衡的情況。
那么我們在向一個有100個主分片的索引發送查詢某篇文檔的請求時,該請求發送到集群,集群干了什么呢?
這個請求會被集群交給主節點。
主節點接收這個請求后,將這個查詢請求廣播到這個索引的每個分片上(包含主、復制分片)。
每個分片執行這個搜索請求,並將結果返回。
結果在主節點上合並、排序后返回給用戶。
這里面就有些問題了。因為在存儲文檔時,通過hash算法將文檔平均分布在各分片上,這就導致了elasticsearch也不確定文檔的位置,所以它必須將這個請求廣播到所有的分片上去執行。
為了避免不必要的查詢,我們使用自定義的路由模式,這樣可以使我們的查詢更具目的性。比如之前的查詢是這樣的:
請求來了,你們(索引下的所有分片)都要檢查一下自己是否有符合條件的文檔
當能自定義路由后的查詢變成了:
請求來了,分片3、5你倆把文檔給我返回
數據寫入相關參數選擇配置:
(1) replication
復制默認的值是sync。這將導致主分片得到復制分片的成功響應后才返回。
如果你設置replication為async,請求在主分片上被執行后就會返回給客戶端。它依舊會轉發請求給復制節點,但你將不知道復制節點成功與否。
上面的這個選項不建議使用。默認的sync復制允許Elasticsearch強制反饋傳輸。async復制可能會因為在不等待其它分片就緒的情況下發送過多的請求而使Elasticsearch過載。
(2) consistency
默認主分片在嘗試寫入時需要規定數量(quorum)或過半的分片(可以是主節點或復制節點)可用。這是防止數據被寫入到錯的網絡分區。規定的數量計算公式如下: int( (primary + number_of_replicas) / 2 ) + 1 consistency允許的值為one(只有一個主分片),all(所有主分片和復制分片)或者默認的quorum或過半分片。 注意number_of_replicas是在索引中的的設置,用來定義復制分片的數量,而不是現在活動的復制節點的數量。如果你定義了索引有3個復制節點,那規定數量是: int( (primary + 3 replicas) / 2 ) + 1 = 3 但如果你只有2個節點,那你的活動分片不夠規定數量,也就不能索引或刪除任何文檔。
(3) timeout
當分片副本不足時會怎樣?Elasticsearch會等待更多的分片出現。默認等待一分鍾。如果需要,你可以設置timeout參數讓它終止的更早:100表示100毫秒,30s表示30秒。
自定義路由
所有的文檔 API( get 、 index 、 delete 、 bulk 、 update 以及 mget )都接受一個叫做 routing 的路由參數 ,通過這個參數我們可以自定義文檔到分片的映射。一個自定義的路由參數可以用來確保所有相關的文檔——例如所有屬於同一個用戶的文檔——都被存儲到同一個分片中。
PUT r1/doc/1?routing=user1 { "title":"論母豬的產前保養" } PUT r1/doc/2?routing=user1 { "title":"論母豬的產后護理" }
上例中,該文檔使用user1作為路由值而不是使用_id。這樣,具有相同user1的文檔將會被分配同一個分片上。
reindex增加routing
想用原搜索某個字段作為目標索引的routing,可以通過script來完成。
例如,使用原索引里"city"字段的值做routing,可以這么寫:
POST _reindex { "source": { "index": "old_index" }, "dest": { "index": "new_index" }, "script": { "inline": "ctx._routing = ctx._source.city", "lang": "painless" } }
通過路由查詢文檔
自定義路由可以減少搜索,不需要將搜索請求分發到所有的分片,只需要將請求發送到匹配特定的路由值分片既可。
我們來查詢:
GET r1/doc/1?routing=user1 # 結果如下 { "_index" : "r1", "_type" : "doc", "_id" : "1", "_version" : 3, "_routing" : "user1", "found" : true, "_source" : { "title" : "論母豬的產前保養" } }
也可通過這個路由值查詢文檔:
GET r1/doc/_search { "query": { "terms": { "_routing":["user1"] } } } # 結果如下 { "took" : 0, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : 2, "max_score" : 1.0, "hits" : [ { "_index" : "r1", "_type" : "doc", "_id" : "2", "_score" : 1.0, "_routing" : "user1", "_source" : { "title" : "論母豬的產后護理" } }, { "_index" : "r1", "_type" : "doc", "_id" : "1", "_score" : 1.0, "_routing" : "user1", "_source" : { "title" : "論母豬的產前保養" } } ] } }
刪除文檔
我們來刪除文檔。
DELETE r1/doc/1 # 結果如下 { "_index" : "r1", "_type" : "doc", "_id" : "1", "_version" : 1, "result" : "not_found", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 2, "_primary_term" : 1 } 由上例可見,不提供路由,無法刪除文檔。 DELETE r1/doc/1?routing=user1 # 結果如下 { "_index" : "r1", "_type" : "doc", "_id" : "1", "_version" : 2, "result" : "deleted", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 4, "_primary_term" : 1 }
給上路由就OK了。
由此可見,在查詢、刪除、更新文檔時都要提供相同的路由值。
查詢多個路由
除了指定查詢單個路由值之外,還可以指定多個路由值查詢:
PUT r2/doc/1?routing=user1 { "title":"母豬產前保養重點在多喂飼料,輔以人工按摩" } PUT r2/doc/2?routing=user2 { "title":"母豬產后護理重點在母子隔離喂養" }
此搜索請求將僅在與user1和user2路由值關聯的分片上執行。
GET r2/doc/_search?routing=user1,user2 { "query": { "match": { "title": "母豬" } } } # 結果如下 { "took" : 0, "timed_out" : false, "_shards" : { "total" : 2, "successful" : 2, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : 2, "max_score" : 0.68324494, "hits" : [ { "_index" : "r2", "_type" : "doc", "_id" : "2", "_score" : 0.68324494, "_routing" : "user2", "_source" : { "title" : "母豬產后護理重點在母子隔離喂養" } }, { "_index" : "r2", "_type" : "doc", "_id" : "1", "_score" : 0.5753642, "_routing" : "user1", "_source" : { "title" : "母豬產前保養重點在多喂飼料,輔以人工按摩" } } ] } }
忘了路由值怎么辦?
由之前的示例可以看到,再自定義的路由中,索引、查詢、刪除、更新文檔時,都要提供路由值。但是我們有可能會忘記路由值,導致文檔在多個分片建立索引:
PUT r3/doc/1?routing=u1 { "title":"小豬仔非常可愛" } PUT r3/doc/2 { "title":"再可愛也是一盤菜" }
正如上例所示,我們在創建文檔2的時候,忘記路由了,導致這篇文檔被默認分配到別的分片上了。那我們想通過u1路由查詢就會發現:
GET r3/doc/_search { "query": { "terms": { "_routing":["u1"] } } } # 結果如下 { "took" : 1, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 1.0, "hits" : [ { "_index" : "r3", "_type" : "doc", "_id" : "1", "_score" : 1.0, "_routing" : "u1", "_source" : { "title" : "小豬仔非常可愛" } } ] } }
可以發現,那個文檔2通過這個路由值查詢不到,但是可以通過普通的查詢:
GET r3/doc/_search
這樣,兩篇文檔都會有被返回。
為了避免類似上述的情況出現,我們必須采取安全措施,加個套!在自定義映射關系時,使用_routing參數生成那個安全套!
# 以下是6.5.4版本的寫法 PUT r4 { "mappings": { "doc":{ "_routing":{ "required": true } } } } # 以下是7.0官方文檔的的寫法 PUT my_index2 { “mappings”:{ “_ usting”:{ “required”:true } } }
在_routing參數內,將required:true就表明在對文檔做CURD時需要指定路由。不然就會拋出一個routing_missing_exception錯誤。就像下面的示例一樣。
PUT r4/doc/1 { "title":"母豬不懷孕怎么辦?" } # 結果是報錯 { "error": { "root_cause": [ { "type": "routing_missing_exception", "reason": "routing is required for [r4]/[doc]/[1]", "index_uuid": "_na_", "index": "r4" } ], "type": "routing_missing_exception", "reason": "routing is required for [r4]/[doc]/[1]", "index_uuid": "_na_", "index": "r4" }, "status": 400 }
有了這種規范,我們在自定義路由時,就可以避免一些不必要的情況發生了。
自定義路由唯一ID
索引指定自定義_routing的文檔時,不能保證索引中所有分片的_id唯一性。 事實上,如果使用不同的_routing值索引,具有相同_id的文檔可能最終會出現在不同的分片上。
我們應確保ID在索引中是唯一的。
1.手動自定義id
PUT /index_test/type_test/1 { "name":"one" }
2.自動生成id
(1)自動生成的id,長度為20個字符,URL安全,base64編碼,GUID,分布式系統並行生成時不可能會發生沖突
POST /index_test/type_test { "name":"twe" }
路由到索引分區
問題來了,在實際開發中,可能由於業務場景問題碰到某個路由的文檔量非常大,造成該分片非常大,而某些路由的文檔卻非常小,這就會造成數據偏移而導致集群不平衡。我們該如何辦呢?
我們可以配置索引,使得自定義路由值將轉到分片的子集而不是單個分片。這有助於降低上述問題導致集群不平衡的風險,同時仍然可以減少搜索的影響。
這是通過在索引創建時提供索引級別設置index.routing_partition_size來完成的。隨着分區大小的增加,數據分布越均勻,代價是每個請求必須搜索更多分片。
PUT r6 { "mappings": { "doc":{ "_routing":{ "required": true } } }, "settings": { "index.routing_partition_size": 3 } }
通俗的說,這是限制文檔分布到指定個數分片上,而不是默認的所有分片上,既提高了請求效率,也減小單一分片數據量過大的問題。
當此設置存在時,計算分片的公式變為:
shard_num = (hash(_routing) + hash(_id) % routing_partition_size) % num_primary_shards
也就是說,_routing字段用於計算索引中的一組分片,然后_id用於選擇該集合中的分片。
要啟用此功能,index.routing_partition_size應具有大於1且小於index.number_of_shards的值。
啟用后,分區索引將具有以下限制:
無法在其中創建具有join field關系的映射。
索引中的所有映射都必須將_routing字段標記為必需。
原文鏈接:https://zyc88.blog.csdn.net/article/details/100727714