Elasticsearch是如何保證併發安全的?

一、什麼是併發問題

在這裏插入圖片描述

二、悲觀鎖&&樂觀鎖

ES採取的是樂觀鎖機制
在這裏插入圖片描述

三、ES是如何解決併發問題的?

ES是靠內部維護的一個_version版本號字段進行樂觀鎖的。
比如我們PUT一條document到ES

PUT /test_index/_doc/1
{
  "test_field" : "test test"  
}

返回結果

{
  "_index" : "test_index",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

這裏可以看到_version是1,因爲是剛created,我們修改一下再來看看

進行修改這條數據,再次看_version字段:

PUT /test_index/_doc/1
{
  "test_field" : "abc 123"  
}

返回結果

{
  "_index" : "test_index",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 2,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}

可以看到_version變成2了。

補充個知識點:
我們刪除id=1的document,再看_version

DELETE /test_index/_doc/1

返回結果:

{
  "_index" : "test_index",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 3,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 2,
  "_primary_term" : 1
}

_version再次+1了,奇怪,刪除怎麼還+1? 我們再PUT試試

再次PUT id=1的document

PUT /test_index/_doc/1
{
  "test_field" : "abc 123"  
}

返回結果

{
  "_index" : "test_index",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 4,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 3,
  "_primary_term" : 1
}

神奇的事情發現了,我們delete掉了,再次PUT的時候_version居然是4而不是1,所以這裏引入一個知識點:
他不是立即物理刪除的,因爲他的一些版本號等信息還是保留着的。先刪除一條document,再重新創建這條document,其實會在delete version基礎之上,再把version號加1

四、實戰演示樂觀鎖

(1)先構造一條數據

PUT /test_index/test_type/3
{
  "test_field" : "test test"
}

(2)新開個瀏覽器網頁,輸入Kibana網址,打開兩個Kibana客戶端,模擬兩個客戶端,都獲取到了同一條數據
GET /test_index/test_type/3

兩個客戶端都返回

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "3",
  "_version": 1,
  "found": true,
  "_source": {
    "test_field": "test test"
  }
}

(3)其中一個客戶端,先更新了一下這條數據同時帶上版本號,es中的數據的版本號跟客戶端中的數據的版本號是相同的才能修改,否則視爲併發衝突。

PUT /test_index/test_type/3?version=1
{
  "test_field" : "test client one"
}

結果

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "3",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 5,
    "successful": 1,
    "failed": 0
  },
  "created": false
}

結果可看到數據變了,此時版本號是2
(4)另一個客戶端,嘗試基於version=1的數據進行修改,同樣帶上version=1(模擬併發請求),進行樂觀鎖的併發控制

PUT /test_index/test_type/3?version=1
{
  "test_field" : "test client two"
}

結果:

{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[test_type][3]: version conflict, current version [2] is different than the one provided [1]",
        "index_uuid": "ey5OuEyFTHe7QhJUvcLDXQ",
        "shard": "2",
        "index": "test_index"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[test_type][3]: version conflict, current version [2] is different than the one provided [1]",
    "index_uuid": "ey5OuEyFTHe7QhJUvcLDXQ",
    "shard": "2",
    "index": "test_index"
  },
  "status": 409
}

版本衝突,當前版本是2,要改的1,找不到version=1的數據
(5)在樂觀鎖成功阻止併發問題之後,嘗試正確的完成更新
GET /test_index/test_type/3
返回結果:

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "3",
  "_version": 2,
  "found": true,
  "_source": {
    "test_field": "test client one"
  }
}

基於最新的數據和版本號,去進行修改,修改後,帶上最新的版本號(這個步驟可能會需要反覆執行好幾次才能成功,主要看併發量多少)

PUT /test_index/test_type/3?version=2
{
  "test_field" : "test client two"
}

結果:

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "3",
  "_version": 3,
  "result": "updated",
  "_shards": {
    "total": 5,
    "successful": 1,
    "failed": 0
  },
  "created": false
}

此時version變成了3。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章