Elasticsearch筆記(十六) routing refresh version seq_no primary_term 參數詳解

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分鐘,當然也可以自定義。

等待主分片10PUT /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
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章