Elasticsearch Index API 參數詳解
1. index
1.1 index作爲名詞
index就像MySQL中表名,可以給index設置很多字段,並指定字段的類型(字符,數字,日期等)。之前老版本ES還可設置不同type,現在已去掉。
1.2 index作爲動詞
- 作爲動詞,index就是往ES中寫入的數據,類似MySQL的insert和update。
- index操作把JSON文檔寫入ES,然後該JSON文檔可被檢索了。
- 如果該文檔已經存在(id相同),會修改該文檔,然後version版本號+1。
2. index的格式
- PUT /<index>/_doc/<_id>
- POST /<index>/_doc/
- PUT /<index>/_create/<_id>
- POST /<index>/_create/<_id>
上面4個是index操作的格式,<index>和<_id>是參數位置。
- <index> 填index的名稱
- <_id> 填文檔的ID
- _docs是默認type名稱。
- _create是指定是新建文檔操作。
PUT和POST的實例代碼看我博客Elasticsearch筆記(一) Query DSL入門
2.1 PUT和POST區別
前面2個操作如下,可以看出PUT帶了<_id>,而POST沒有。
- PUT /<index>/_doc/<_id>
- POST /<index>/_doc/
2.1.1 區別一
- 當想使用我們自己指定的ID時,需要用PUT。
- 當想讓ES給我們自動生成文檔ID時,得用POST。
2.1.2 區別二
如果指定ID的文檔已經存在時,PUT和POST有如下區別:
- PUT是跟新整個文檔,它先根據ID刪除文檔,然後再寫入新文檔。
- POST是局部跟新文檔,原來沒有的字段會新增,已有的字段會修改。
2.2 _create作用
- PUT /<index>/_create/<_id>
- POST /<index>/_create/<_id>
上面的_create指定該操作是新建文檔,如果參數<_id>的文檔已經存在,則會返回操作失敗。
3. index的參數
3.1 routing
在生產環境,一個index是被分爲多個各分片的,路由機制和分片機制密切相關。
舉例有一個index,它被分3個分片,每個分片各有一個副本。如下圖
- 主分片P0,P1,P2
- 副分片R0,R1,R2
在index數據不指定routing時,ES默認用文檔的ID來作爲路由計算值,計算公式如下:
shard = hash(routing) % number_of_primary_shards
number_of_primary_shards是主分片數,這裏就是3。對文檔ID進行hash,再除以3,得到的餘數肯定是0,1,2之間的數,對應主分片P0,P1,P2。這樣起到了負載均衡的效果,使得每個分片裏文檔的數量理想情況下是幾乎一樣的。
舉例ID=”abc“,shard = hash(“abc”) % 3 = 2,則把文檔存到分片P2上。
3.1.1 根據文檔ID查找
舉例ID=”abc“,shard = hash(“abc”) % 3 = 2,則把這次查詢發往分片P2上,因爲插入時,ID=“abc”的數據就存在分片2上。這樣就解釋了index一旦創建好,主分片參數number_of_primary_shards是不能修改的,如果後期修改了它,查詢時計算出的分片號和存入時分片號就不一致了。
3.1.2 自定義路由
如果不是根據文檔ID查找,而是根據別的查詢條件找,那默認是把請求發給所有的分片上,然後把每個分片的查詢結果在協調節點上聚合,然後再給客戶端最終結果。想想如果分片數很多,這樣是很消耗系統系能的。
如果我們存數據時指定了路由routing參數,查詢數據時也指定相同的routing參數,這樣就會發給那一個分片,這樣提高了查詢效率,不用給每個分片發請求了。
PUT /pigg/_doc/100?routing=dept1
{
"name": "winter",
"dept": "dept1"
}
PUT /pigg/_doc/101?routing=dept1
{
"name": "dong",
"dept": "dept1"
}
查詢時指定routing
GET /pigg/_search?routing=dept1
{
"query": {
"term": {
"dept.keyword": {
"value": "dept1"
}
}
}
}
注意使用routing時,這個和業務數據是相關聯的,一定要考慮到業務數據的特性。
例如routing=dept1的有10萬條,routing=dept2的數據只有1千條,那麼10萬條數據在一個分片上,1千條數據在另一個分片上,這樣導致了數據傾斜。
3.2 timeout
當index操作時,可能主分片不可用,例如此時主分片正在恢復中,或在重定向。這個時候index操作就得等待主分片可用,默認這個等待時間是1分鐘,當然也可以自定義。
等待主分片10秒
PUT /pigg/_doc/102?timeout=10s
{
"name": "dong2",
"dept": "dept1"
}
3.3 refresh
剛學ES時,都知道ES是一個近實時系統,寫入的數據要約1秒後才能查詢到。
refresh可以設置3種值,指定刷新行爲。
ES的JavaAPI裏定義了refresh的枚舉如下:
public static enum RefreshPolicy implements Writeable {
NONE("false"),
IMMEDIATE("true"),
WAIT_UNTIL("wait_for");
//省略別的代碼
}
它有false,true,wait_for三個值,ES默認是false。
(1)refresh = false
index後,不進行相關刷新操作,等待一定時間(約1秒),修改的數據可被查詢到,ES默認這個。
(2)refresh = true
- index後,立即刷新相關的主碎片和副本碎片(不是整個索引),以便可以立即搜索到修改後的文檔。這樣看着挺好,但是有不足的,否則ES也不會默認1S後可查詢了。
- 如果頻率的執行refresh=true的操作,會生成很多小的段文件,這也會給後期段文件的合併增加處理時間,因而會影響搜索效率,這樣就得不償失。
- 這個要謹慎考慮設置爲true。
(3)refresh = wait_for
wait_for顧名思義,就是等待請求所做的更改被刷新可見。
對於在生產環境中,一般還是使用默認的配置refresh = false,很少有理由去設置refresh爲別的,在技術選型時用ES了,也就得考慮到ES只是近實時,不是完全實時的。
3.3 version
每個文檔都有一個版本號version,新增後version=1,以後每次修改,version自動加1,也可以指定version,比如讓它從1一下子變成10這樣。
#先新增一個ID=100的文檔
PUT /pigg/_doc/100
{
"name": "三爺"
}
#返回結果如下,注意這個版本號_version=1
{
"_index" : "pigg",
"_type" : "_doc",
"_id" : "100",
"_version" : 1,//注意這個版本號
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
但我們要修改文檔前,我們已經知道當前version=1,如果文檔被別人修改過,那version肯定大於1。
當我們要修改時,帶上version參數(值是我們認定的更新前,當前文檔的version值),如果我們指定的與ES裏文檔相等,則能成功,否則報異常。
#指定跟新前文檔現有version=1
PUT /pigg/_doc/100?version=1
{
"name": "三爺2"
}
#因爲中間沒有別人操作過id=100的文檔,所以修改成功,version變成2
{
"_index" : "pigg",
"_type" : "_doc",
"_id" : "100",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1
}
3.4 version_type
在指定version參數時,我們還可以指定version_type參數。它有3種類型:
version_type | 說明 |
---|---|
internal | 指定的version == 當前文檔的version |
external or external_gt | 指定的version > 當前文檔的version |
external_gte | 指定的version >= 當前文檔的version |
#指定了version_type=external,必須version>當前的version
PUT /pigg/_doc/100?version=10&version_type=external
{
"name": "三爺3"
}
#返回結果如下,version變成指定的10
{
"_index" : "pigg",
"_type" : "_doc",
"_id" : "100",
"_version" : 10,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 4,
"_primary_term" : 1
}
3.5 if_seq_no & if_primary_term
ES是樂觀併發控制的,在ES6.7這個版本之前是用version+version_type,現在新版本的ES用if_seq_no & if_primary_term在做併發控制。
(1)創建文檔獲取_seq_no和_primary_term
PUT pigg/_doc/300
{
"name": "winter"
}
返回結果如下,注意看最後2個_seq_no和_primary_term
{
"_index" : "pigg",
"_type" : "_doc",
"_id" : "300",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 13,
"_primary_term" : 1
}
(2)修改時帶上if_seq_no&if_primary_term
PUT pigg/_doc/300?if_seq_no=13&if_primary_term=1
{
"name": "winter1"
}
返回結果可見_seq_no加1了
{
"_index" : "pigg",
"_type" : "_doc",
"_id" : "300",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 14,//從13變成14了
"_primary_term" : 1
}
(3)模擬併發操作
再次執行相同的操作
PUT pigg/_doc/300?if_seq_no=13&if_primary_term=1
{
"name": "winter1"
}
返回報錯,說seqNo已經是14了
{
"error" : {
"root_cause" : [
{
"type" : "version_conflict_engine_exception",
"reason" : "[300]: version conflict, required seqNo [13], primary term [1]. current document has seqNo [14] and primary term [1]",
"index_uuid" : "1EcXaVewTu2IFK_4OAGxIw",
"shard" : "0",
"index" : "pigg"
}
],
"type" : "version_conflict_engine_exception",
"reason" : "[300]: version conflict, required seqNo [13], primary term [1]. current document has seqNo [14] and primary term [1]",
"index_uuid" : "1EcXaVewTu2IFK_4OAGxIw",
"shard" : "0",
"index" : "pigg"
},
"status" : 409
}