? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Elasticsearch集群知識筆記
Elasticsearch內部提供了一個rest接口用于查看集群內部的健康狀況:
curl -XGET http://localhost:9200/_cluster/health |
response結果:
{ "cluster_name": "format-es", "status": "green", ... } |
這里的status有3種狀態,分別是green(所有主分片和復制分片都可用),yellow(所有主分片可用,但不是所有復制分片都可用)和red(不是所有主分片可用)。
分片(Shard)
Elasticsearch中的索引(index)是由分片(shard)構成的。
比如我們集群中有個索引users,該索引由3個分片組成,那么這個users索引中的文檔數據將分布在這3個分片中。
users索引中的文檔是根據下面這個規則確定該文檔屬于哪個分片:
shard = hash(routing) % number_of_primary_shards // routing值默認是文檔的_id,number_of_primary_shards是索引的主分片個數 |
這個routing默認是文檔的_id,可以自定義(文章后面部分會舉例說明)。
這3個分片可以進行復制,復制是為了實現容錯性,比如復制1份,那么一共就需要6個分片(3個主分片+3個主分片復制出來的復制分片)。
users索引的創建命令(主分片3個,復制1份):
curl -XPUT http://localhost:9200/users -d ' { "settings": { "number_of_shards": 3, "number_of_replicas": 1 } } ' |
創建完users索引之后,es集群(單節點)分片情況如下:
由于users索引有3個分片,es內部會創建出3個分片,分別是P0、P1和P2(大寫P指的是primary),且這3個分片都是主分片。users索引需要對分片進行復制1份,所以這3個主分片都需要復制1份,分別對應R0、R1和R2這3個復制分片(大寫R指的是replica)。這個時候我們的集群只有1個節點node-1,所以復制分片并沒有起作用(如果復制分片和主分片在同一個節點了,那么這個復制分片的意義就不存在了。復制分片的意義在于容錯性,當一個節點掛了,另一個節點上的分片可以代替掛掉節點上的分片)。
查看健康狀態:
curl -XGET http://localhost:9200/_cluster/health |
response結果:
{ "cluster_name": "format-es", "status": "yellow", "timed_out": false, "number_of_nodes": 1, "number_of_data_nodes": 1, "active_primary_shards": 3, "active_shards": 3, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 3, "delayed_unassigned_shards": 0, "number_of_pending_tasks": 0, "number_of_in_flight_fetch": 0, "task_max_waiting_in_queue_millis": 0, "active_shards_percent_as_number": 50 } |
這里可以看到,集群的狀態變成了yellow。這是因為users索引中的分片需要復制1份,但是沒有足夠的機器用來存儲復制出來的復制分片,還有其它的一些字段比如unassigned_shards字段為3,對應R0、R1和R2這3個未分配的復制分片。
在集群中加入節點node-2,查看健康狀況(這里使用偽集群。node-1節點對應9200端口的進程,node-2節點對應9201端口的進程):
curl -XGET http://localhost:9200/_cluster/health |
response結果:
{ "cluster_name": "format-es", "status": "green", "timed_out": false, "number_of_nodes": 2, "number_of_data_nodes": 2, "active_primary_shards": 3, "active_shards": 6, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 0, "delayed_unassigned_shards": 0, "number_of_pending_tasks": 0, "number_of_in_flight_fetch": 0, "task_max_waiting_in_queue_millis": 0, "active_shards_percent_as_number": 100 } |
主分片和復制分片均可用,status為green。
此時,es集群分片情況如下:
這個時候es集群由2個節點node-1和node-2組成,并且這2個節點上具有主分片和復制分片,具有容錯性。
我們往users索引中插入一條文檔:
curl -XPOST http://localhost:9200/users/normal -d ' { "name" : "Format", "age" : 111 } ' |
返回:
{ "_index": "users", "_type": "normal", "_id": "AV0hs4LnkXxVJ5DURwXr", "_version": 1, "_shards": { "total": 2, "successful": 2, "failed": 0 }, "created": true } |
從返回的信息中可以看到,這個文檔已經被創建成功,并且2個分片都成功。id由es內部自動創建,值為AV0hs4LnkXxVJ5DURwXr。
讀取id為AV0hs4LnkXxVJ5DURwXr的文檔:
curl -XGET http://localhost:9200/users/normal/AV0hs4LnkXxVJ5DURwXr # 結果 { "_index": "users", "_type": "normal", "_id": "AV0hs4LnkXxVJ5DURwXr", "_version": 1, "found": true, "_source": { "name": "Format", "age": 111 } } |
這個時候如果節點node-1掛了,讀取數據:
curl -XGET http://localhost:9201/users/normal/AV0hs4LnkXxVJ5DURwXr # 結果 { "_index": "users", "_type": "normal", "_id": "AV0hs4LnkXxVJ5DURwXr", "_version": 1, "found": true, "_source": { "name": "Format", "age": 111 } } |
在節點node-1已經掛了的情況下還是讀取到了之前插入的文檔。這是因為我們users索引會復制2份,node-1節點雖然已經掛了,但是node-2節點上這個文檔的數據還在,所以文檔會被讀取到。
在node-1節點掛掉的情況下,再次插入一條文檔:
curl -XPOST http://localhost:9201/users/normal -d ' { "name" : "Jim", "age" : 66 } ' |
返回:
{ "_index": "users", "_type": "normal", "_id": "AV0qMto5dJHprgu99sSN", "_version": 1, "_shards": { "total": 2, "successful": 1, "failed": 0 }, "created": true } |
這里看到返回的數據中,這個文檔對應的分片只有1個成功插入,因為另1個分片對應的節點已經掛了。
然后讀取這個新插入的文檔:
curl -XGET http://localhost:9201/users/normal/AV0qMto5dJHprgu99sSN # 結果 { "_index": "users", "_type": "normal", "_id": "AV0qMto5dJHprgu99sSN", "_version": 1, "found": true, "_source": { "name": "Jim", "age": 66 } } |
然后node-1節點恢復(節點恢復之后,es內部會自動從數據全的分片中復制數據到數據少的分片上,保證高可用),然后讀取數據:
curl -XGET http://localhost:9200/users/normal/AV0qMto5dJHprgu99sSN # 結果 { "_index": "users", "_type": "normal", "_id": "AV0qMto5dJHprgu99sSN", "_version": 1, "found": true, "_source": { "name": "Jim", "age": 66 } } |
ES中文檔的新建、刪除和修改都是先在主分片上完成的,在主分片上完成這些操作以后,才會進行復制操作。比如有3個節點node-1、node-2和node-3,索引blogs有2個主分片,并且復制2份,集群結構如下:
當進行新建文檔的時候過程如下:
- 客戶端給master節點node-1發送新建文檔的請求
- node-1節點根據文檔的_id,確定該文檔屬于屬于分片1。分片1的主分片在節點node-2上,故將請求轉發到node-2
- node-2上的主分片P1處理文檔成功,然后轉發請求到node-1和node-3節點上的復制節點上。當所有的復制節點報告成功后,node-2節點報告成功到請求的節點,請求節點再返回給客戶端
當進行檢索文檔的時候過程如下:
- 客戶端給master節點node-1發送檢索文檔的請求
- node-1節點根據文檔的_id,確定該文檔屬于分片0。分片0在3個節點中都存在,本次請求中使用節點node-2,于是將請求轉發給node-2節點
- node-2節點得到文檔數據,并返回給node-1節點,node-1節點返回給客戶端
這里es集群會使用輪詢的策略對讀取不同節點上的分片中的文檔數據,比如針對上圖中的查詢,下次查詢就會讀取node-3節點上的R0分片中的文檔。
當對文檔進行局部更新的時候過程如下:
- 客戶端給master節點node-1發送局部更新文檔的請求
- node-1節點根據文檔的_id,確定該文檔屬于分片1,并發現找到分片1的主分片在node-2節點上,轉發請求到node-2節點上
- node-2節點在主分片P1中找出對應id的文檔,修改文檔內部的_source屬性,之后對文檔重建索引。如果這個文檔已經被其它進程修改,會重試步驟3 retry_on_conflict 次數(retry_on_conflict可通過參數設置)
- 如果步驟3執行成功,node-2節點轉發新版本的文檔給node-1和node-3節點上的復制分片,這2個節點對文檔進行重建索引。一旦node-1和node-3節點上的復制分片處理成功,node-2節點返回成功給node-1節點,node-1節點返回給客戶端
節點(Node)
在分布式集群情況下,ES中的節點可分為4類:
- master節點:配置文件中node.master屬性為true(默認為true),就有資格被選為master節點,master節點用于控制整個集群的操作。比如創建或刪除索引,管理其它非master節點等
- data節點:配置文件中node.data屬性為true(默認為true),就有資格被設置成data節點,data節點主要用于執行數據相關的操作。比如文檔的CRUD
- 客戶端節點:配置文件中node.master屬性和node.data屬性均為false。該節點不能作為master節點,也不能作為data節點。可以作為客戶端節點,用于響應用戶的請求,把請求轉發到其他節點
- 部落節點:當一個節點配置tribe.*的時候,它是一個特殊的客戶端,它可以連接多個集群,在所有連接的集群上執行搜索和其他操作
查詢集群狀態的Rest接口
可以通過es內部提供的rest接口查看master節點:
curl -XGET http://localhost:9200/_cat/master?v id host ip node 9FINsHCpTKqcpFlnnA4Yww 10.1.251.164 10.1.251.164 node-1 |
查看節點信息:
curl -XGET http://localhost:9200/_cat/nodes?v host ip heap.percent ram.percent load node.role master name 10.1.251.164 10.1.251.164 6 100 5.48 d * node-1 10.1.251.164 10.1.251.164 6 100 5.48 d m node-3 10.1.251.164 10.1.251.164 7 100 5.48 d m node-2 |
或者使用head插件查看節點情況。圖中帶有五角星的節點是master,這里users索引有3個主分片和3個復制分片(綠色框外部加粗的邊框就是主分片,否則就是復制分片):
如果我們的集群上node-1節點由于硬盤容量不足導致不可用時,head插件情況如下(3個復制節點未被分配,健康狀況為黃色):
也可使用es內部的rest接口查看分片信息:
curl -XGET http://localhost:9200/_cat/shards?v index shard prirep state docs store ip node users 1 p STARTED 1 3.3kb 10.1.251.164 node-2 users 1 r UNASSIGNED users 2 p STARTED 0 159b 10.1.251.164 node-2 users 2 r UNASSIGNED users 0 p STARTED 2 6.6kb 10.1.251.164 node-3 users 0 r UNASSIGNED |
routing參數決定如何分片(可以在index、get、delete、update、bulk等方法中使用),我們覆蓋默認的routing為_id的默認策略:
# 執行10次 curl -XPOST http://localhost:9200/users/normal?routing=1 -d ' { "name" : "Format345", "age" : 456 } ' # 執行1次 curl -XPOST http://localhost:9200/users/normal -d ' { "name" : "Format345", "age" : 456 } ' # 使用routing參數得到文檔的結果(多了個_rouring屬性) { "_index": "users", "_type": "normal", "_id": "AV07AubA6HDSJNRJle0i", "_version": 1, "_routing": "1", "found": true, "_source": { "name": "Format345", "age": 456 } } # 查詢文檔分布情況(前面10次分布到了P2分片,后面1次分布到了P1分片) curl -XGET http://localhost:9200/_cat/shards?v index shard prirep state docs store ip node users 1 p STARTED 2 3.3kb 10.1.251.164 node-2 users 1 r UNASSIGNED users 2 p STARTED 10 159b 10.1.251.164 node-2 users 2 r UNASSIGNED users 0 p STARTED 2 6.6kb 10.1.251.164 node-3 users 0 r UNASSIGNED |
官網上有更多關于_cat api和_cluster api相關的文檔。
文檔操作
es中文檔的操作可以使用其內部提供的rest接口,使用過程中可以指定一些參數修改默認行為。
1.replication:用于設置復制分片的處理過程是同步還是異步。默認值是sync(主分片需要等待復制分片全部處理完畢),也可以設置成async(主分片不需要等待復制分片的處理過程,但是還是會轉發請求給復制分片,這個轉發過程是異步的)。該參數在2.0.0版本后已經被廢棄,因為異步轉發給復制分片的話,不知道復制分片是否成功與否,而且復制分片在還沒有處理完成的情況下由于一直過來的異步請求而導致es過載,不建議使用async
2.consistency:寫文檔的一致性參數,可以設置成one,quorum和all;分別表示主分片可用即可、過半分片可用[公式:int( (primary + number_of_replicas) / 2 ) + 1]以及全部分片可用。比如有個blogs索引,有3個主分片,并且復制2份,當集群中的1個節點掛了,并使用all的話,將會拋出異常:
curl -XPOST http://localhost:9200/blogs/normal?consistency=all -d ' { "name" : "POST-1" } ' # 一分鐘后拋出異常 { "error": { "root_cause": [ { "type": "unavailable_shards_exception", "reason": "[blogs][0] Not enough active copies to meet write consistency of [ALL] (have 2, needed 3). Timeout: [1m], request: [index {[blogs][normal][AV1AF1FEl7qPpRBCQMV7], source[{\n \"name\" : \"POST-1\"\n}]}]" } ], "type": "unavailable_shards_exception", "reason": "[blogs][0] Not enough active copies to meet write consistency of [ALL] (have 2, needed 3). Timeout: [1m], request: [index {[blogs][normal][AV1AF1FEl7qPpRBCQMV7], source[{\n \"name\" : \"POST-1\"\n}]}]" }, "status": 503 } |
使用默認的quorum策略:
curl -XPOST http://localhost:9200/blogs/normal -d ' { "name" : "POST-1" } # 由于集群中的節點掛了1個,所分片只有2個success { "_index": "blogs", "_type": "normal", "_id": "AV1AckLfl7qPpRBCQMV_", "_version": 1, "_shards": { "total": 3, "successful": 2, "failed": 0 }, "created": true } ' |
consistency參數在5.0.0版本已經被棄用
3.timeout:當分片不足的時候,es等待的時間(等待節點重新啟動,分片恢復),默認為1分鐘,可以進行修改,改成10秒:
curl -XPOST http://localhost:9200/blogs/normal?consistency=all&timeout=10s -d ' { "name" : "POST-1" } ' # 10秒后拋出異常 { "error": { "root_cause": [ { "type": "unavailable_shards_exception", "reason": "[blogs][1] Not enough active copies to meet write consistency of [ALL] (have 2, needed 3). Timeout: [10s], request: [index {[blogs][normal][AV1AdXxsl7qPpRBCQMWB], source[{\n \"name\" : \"POST-1\"\n}]}]" } ], "type": "unavailable_shards_exception", "reason": "[blogs][1] Not enough active copies to meet write consistency of [ALL] (have 2, needed 3). Timeout: [10s], request: [index {[blogs][normal][AV1AdXxsl7qPpRBCQMWB], source[{\n \"name\" : \"POST-1\"\n}]}]" }, "status": 503 } |
4.version
es中每個文檔都有對應的版本信息,可以使用version版本參數用來實現并發情況下的樂觀鎖機制:
# 新建一個文檔 curl -XPUT http://localhost:9200/blogs/normal/format-001 -d ' { "name" : "format-post-001" } ' # 結果 { "_index": "blogs", "_type": "normal", "_id": "format-001", "_version": 1, "_shards": { "total": 3, "successful": 3, "failed": 0 }, "created": true } # id為format-001的文檔目前的version為1,進行更新 # 用version為2去更新 curl -XPUT http://localhost:9200/blogs/normal/format-001?version=2 -d ' { "name" : "format-post-001-001" } ' # 報錯,版本沖突 { "error": { "root_cause": [ { "type": "version_conflict_engine_exception", "reason": "[normal][format-001]: version conflict, current [1], provided [2]", "shard": "0", "index": "blogs" } ], "type": "version_conflict_engine_exception", "reason": "[normal][format-001]: version conflict, current [1], provided [2]", "shard": "0", "index": "blogs" }, "status": 409 } # 用version為1去更新 curl -XPUT http://localhost:9200/blogs/normal/format-001?version=1 -d ' { "name" : "format-post-001-001" } ' # 更新成功,文檔版本變成2 { "_index": "blogs", "_type": "normal", "_id": "format-001", "_version": 2, "_shards": { "total": 3, "successful": 3, "failed": 0 }, "created": false } |
5.op_type:可以指定本次操作的類型,比如create操作。
# 創建一個id為1,type為normal,在blogs索引中的文檔 curl -XPUT http://localhost:9200/blogs/normal/1?op_type=create -d ' { "name" : "POST-2" } ' { "_index": "blogs", "_type": "normal", "_id": "1", "_version": 1, "_shards": { "total": 3, "successful": 3, "failed": 0 }, "created": true } # 繼續調用同一個操作 curl -XPUT http://localhost:9200/blogs/normal/1?op_type=create -d ' { "name" : "POST-2" } ' # 報錯,文檔已經存在 { "error": { "root_cause": [ { "type": "document_already_exists_exception", "reason": "[normal][1]: document already exists", "shard": "0", "index": "blogs" } ], "type": "document_already_exists_exception", "reason": "[normal][1]: document already exists", "shard": "0", "index": "blogs" }, "status": 409 } |
可以不使用op_type操作,在url中指定。這兩種方法效果是一樣的
http://localhost:9200/blogs/normal/1/_create 效果跟 http://localhost:9200/blogs/normal/1?op_type=create 是一樣的。
目前支持的op_type有create(只支持創建文檔)和index(支持創建和更新文檔)。
6.wait_for_active_shards
在5.0.0版本新引入的一個參數,表示等待活躍的分片數。作用跟consistency類似,可以設置成all或者任意正整數。
比如在這種場景下:集群中有3個節點node-1、node-2和node-3,并且索引中的分片需要復制3份。那么該索引一共擁有4個分片,包括1個主分片和3個復制分片。
默認情況下,索引操作只需要等待主分片可用(wait_for_active_shards為1)即可。
如果node-2和node-3節點掛了,索引操作是不會受影響的(wait_for_active_shards默認為1);如果設置了wait_for_active_shards為3,那么需要3個節點全部存活;如果設置了wait_for_active_shards為4或者all(一共4個分片,4和all是一樣的效果),那么該集群中的索引操作永遠都會失敗,因為集群一共就3個節點,不能處理所有的4個分片。
比如設置成all,則會拋出如下錯誤:
{ "error": { "root_cause": [ { "type": "unavailable_shards_exception", "reason": "[blogs][2] Not enough active copies to meet shard count of [ALL] (have 3, needed 4). Timeout: [1m], request: [index {[blogs][normal][AV1QVDz3RpA5iuXn159C], source[{\n \"name\" : \"POST-1\"\n}]}]" } ], "type": "unavailable_shards_exception", "reason": "[blogs][2] Not enough active copies to meet shard count of [ALL] (have 3, needed 4). Timeout: [1m], request: [index {[blogs][normal][AV1QVDz3RpA5iuXn159C], source[{\n \"name\" : \"POST-1\"\n}]}]" }, "status": 503 } |
wait_for_active_shards的默認值可以在定義索引的時候進行設置,也可以動態地進行修改:
curl -XPUT http://localhost:9200/blogs/_settings -d ' { "index.write.wait_for_active_shards": 3 } ' |
7.自動生成id
創建文檔的時候,可以不指定id,es會自動為你生成1個id,需要注意的話需要使用POST方式,而不是PUT方式。
curl -XPOST http://localhost:9200/blogs/normal -d ' { "name" : "my-post" } ' { "_index": "blogs", "_type": "normal", "_id": "AV1Pj6MdAuPf3r3i0ysL", # 自動生成的id "_version": 1, "_shards": { "total": 3, "successful": 3, "failed": 0 }, "created": true } |
8.文檔的局部更新
# 新建文檔 curl -XPUT http://localhost:9200/blogs/normal/format-doc-001 -d ' { "title" : "springboot in action", "author" : "Format" } ' # 執行全更新操作 curl -XPUT http://localhost:9200/blogs/normal/format-doc-001 -d ' { "create_at": "2017-07-18" } ' # 獲取文檔 curl -XGET http://localhost:9200/blogs/normal/format-doc-001 { "_index": "blogs", "_type": "normal", "_id": "format-doc-001", "_version": 2, "found": true, "_source": { "create_at": "2017-07-18" } } # 使用文檔局部更新 curl -XPOST http://localhost:9200/blogs/normal/format-doc-001/_update -d ' { "doc": { "title" : "springboot in action", "author" : "Format" } } ' # 獲取文檔 curl -XGET http://localhost:9200/blogs/normal/format-doc-001 { "_index": "blogs", "_type": "normal", "_id": "format-doc-001", "_version": 3, "found": true, "_source": { "create_at": "2017-07-18", "author": "Format", "title": "springboot in action" } } # 使用腳本局部更新 curl -XPOST http://localhost:9200/blogs/normal/format-doc-001/_update -d ' { "script" : "ctx._source.views = 0; ctx._source.tags = [new_tag]", "params": { "new_tag": "java" } } ' # 獲取文檔 curl -XGET http://localhost:9200/blogs/normal/format-doc-001 { "_index": "blogs", "_type": "normal", "_id": "format-doc-001", "_version": 3, "found": true, "_source": { "create_at": "2017-07-18", "author": "Format", "title": "springboot in action", "tags": [ "java" ], "views": 0 } } # 使用腳本局部更新新創建的文檔 curl -XPOST http://localhost:9200/blogs/normal/format-doc-002/_update -d ' { "script" : "ctx._source.views+=1" } ' # 報錯,因為id為format-doc-002的文檔不存在 { "error": { "root_cause": [ { "type": "document_missing_exception", "reason": "[normal][format-doc-002]: document missing", "shard": "0", "index": "blogs" } ], "type": "document_missing_exception", "reason": "[normal][format-doc-002]: document missing", "shard": "0", "index": "blogs" }, "status": 404 } # 加上upsert參數(設置字段的初始值) curl -XPOST http://localhost:9200/blogs/normal/format-doc-002/_update -d ' { "script" : "ctx._source.views+=1", "upsert": { "views": 1 } } ' # 獲取文檔 curl -XGET http://localhost:9200/blogs/normal/format-doc-002 { "_index": "blogs", "_type": "normal", "_id": "format-doc-002", "_version": 1, "found": true, "_source": { "views": 1 } } |
9.檢索多個文檔(Multi Get API)
可以在一個請求中獲得多個文檔數據。
# 在所有索引中執行mget,在參數中指定索引 curl -XGET http://localhost:9200/_mget -d ' { "docs" : [ { "_index" : "blogs", "_type" : "normal", "_id" : "format-doc-001" }, { "_index" : "blogs", "_type" : "normal", "_id" : "format-doc-002" } ] } ' # 結果 { "docs": [ { "_index": "blogs", "_type": "normal", "_id": "format-doc-001", "_version": 3, "found": true, "_source": { "create_at": "2017-07-18", "author": "Format", "title": "springboot in action", "tags": [ "java" ], "views": 0 } }, { "_index": "blogs", "_type": "normal", "_id": "format-doc-002", "_version": 1, "found": true, "_source": { "views": 1 } } ] } # 基于特定的索引做mget curl -XGET http://localhost:9200/blogs/_mget -d ' { "docs" : [ { "_type" : "normal", "_id" : "format-doc-001" }, { "_type" : "normal", "_id" : "format-doc-002" } ] } ' # 基于特定的索引和類型做mget curl -XGET http://localhost:9200/blogs/normal/_mget -d ' { "docs" : [ { "_id" : "format-doc-001" }, { "_id" : "format-doc-002" } ] } ' # 簡化版的基于特定的索引和類型做mget curl -XGET http://localhost:9200/blogs/normal/_mget -d ' { "ids": ["format-doc-001", "format-doc-002"] } ' # 過濾source中的屬性 curl -XGET http://localhost:9200/_mget -d ' { "docs" : [ { "_index": "blogs", "_type": "normal", "_id" : "format-doc-001", "_source": ["title", "author"] }, { "_index": "blogs", "_type": "normal", "_id" : "format-doc-002", "_source": false }, { "_index": "blogs", "_type": "normal", "_id" : "format-doc-003", "_source": { "include": ["title"], "exclude": ["author"] } } ] } ' |
10.批量操作(bulk)
批量操作可以實現同一個請求操作多個文檔的過程。需要注意的是bulk操作Http Body中的格式,對文檔進行處理的話需要使用換行。比如創建新文檔,更新文檔都需要使用換行把創建目錄和文檔數據進行分割。不同的操作也需要用換行進行分割,比如創建文檔和刪除文檔。