ES并發沖突問題與悲觀鎖與樂觀鎖并發控制

1、ES并發沖突問題

圖解ES并發沖突問題

2、悲觀鎖與樂觀鎖兩種并發控制解決方案

悲觀鎖與樂觀鎖并發控制方案
  • 悲觀鎖的優點是:方便,直接加鎖,對應用程序來說,透明,不需要做額外的操作;缺點,并發能力很低,同一時間只能一條線程操作數據
  • 樂觀鎖的優點是:并發能力很高,不給數據加鎖,大量線程并發操作;缺點,麻煩,每次更新的時候,都要先對比版本號,然后可能需要重新加載數據,再次修改,再寫;這個過程,可能要重復好幾次。

3、Elasticsearch內部如何基于_version進行樂觀鎖并發控制

(1)_version元數據

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

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "6",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

第一次創建一個document的時候,它的_version內部版本號就是1;以后,每次對這個document執行修改或者刪除操作,都會對這個_version版本號自動加1;哪怕是刪除,也會對這條數據的版本號加1

{
  "found": true,
  "_index": "test_index",
  "_type": "test_type",
  "_id": "6",
  "_version": 4,
  "result": "deleted",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  }
}

我們會發現,在刪除一個document之后,可以從一個側面證明,它不是立即物理刪除掉的,因為它的一些版本號等信息還是保留著的。先刪除一條document,再重新創建這條document,其實會在delete version基礎之上,再把version號加1

PUT /test_index/test_type/7
{
  "test_field": "test test"
}
#返回
{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "6",
  "_version": 1,  //這里是1
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

#刪除
DELETE  /test_insex/test_type/7

PUT /test_index/test_type/7
{
  "test_field": "test test"
}
#返回
{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "6",
  "_version": 2,  //變成了2
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": false
}

(2)圖解內部如何基于_version進行樂觀鎖并發控制


_version進行樂觀鎖并發控制

4、實戰演練基于_version進行樂觀鎖并發控制

  1. 先構造一條數據出來
#添加
PUT /test_index/test_type/7
{
  "test_field":"test test"
}
  1. 模擬兩個客戶端,都獲取到了同一條數據
#查詢
GET /test_index/test_type/7
#返回
{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "7",
  "_version": 1,
  "found": true,
  "_source": {
    "test_field": "test test"
  }
}
  1. 其中一個客戶端,先更新了一下這個數據,同時帶上數據的版本號,確保說,es中的數據的版本號,跟客戶端中的數據的版本號是相同的,才能修改
#修改
PUT /test_index/test_type/7?version=1
{
  "test_field":"test client1"
}
#返回
{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "7",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": false
}
  1. 另外一個客戶端,嘗試基于version=1的數據去進行修改,同樣帶上version版本號,進行樂觀鎖的并發控制
#再次修改
PUT /test_index/test_type/7?version=1 
{
  "test_field": "test client 2"
}
#返回報錯
{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[test_type][7]: version conflict, current version [2] is different than the one provided [1]",
        "index_uuid": "EbphihYrQMWacVrHu8eWAw",
        "shard": "3",
        "index": "test_index"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[test_type][7]: version conflict, current version [2] is different than the one provided [1]",
    "index_uuid": "EbphihYrQMWacVrHu8eWAw",
    "shard": "3",
    "index": "test_index"
  },
  "status": 409
}
  1. 在樂觀鎖成功阻止并發問題之后,嘗試正確的完成更新。帶上當前正確的版本號,在進行修改。
#查詢獲取版本號
GET /test_index/test_type/7
#返回
{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "7",
  "_version": 2,
  "found": true,
  "_source": {
    "test_field": "test client 1"
  }
}

#帶上當前版本號進行修改
PUT /test_index/test_type/7?version=2
{
  "test_field":"test client2"
}
#返回
{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "7",
  "_version": 3,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": false
}

基于最新的數據和版本號,去進行修改,修改后,帶上最新的版本號,可能這個步驟會需要反復執行好幾次,才能成功,特別是在多線程并發更新同一條數據很頻繁的情況下

5. 實戰演練基于external version進行樂觀鎖并發控制

es提供了一個feature,就是說,你可以不用它提供的內部_version版本號來進行并發控制,可以基于你自己維護的一個版本號來進行并發控制。舉個列子,加入你的數據在mysql里也有一份,然后你的應用系統本身就維護了一個版本號,無論是什么自己生成的,程序控制的。這個時候,你進行樂觀鎖并發控制的時候,可能并不是想要用es內部的_version來進行控制,而是用你自己維護的那個version來進行控制。

#語法:
?version=1&version_type=external

version_type=external,唯一的區別在于。
_version,只有當你提供的version與es中的_version一模一樣的時候,才可以進行修改,只要不一樣,就報錯;
version_type=external,只有當你提供的version比es中的_version大的時候,才能完成修改

# 1. 先添加一條數據
PUT /test_index/test_type/8
{
  "test_field":"test"
}

# 2. 模擬兩個客戶端同時查詢到這條數據
GET /test_index/test_type/8

# 3. 第一個客戶端先進行修改,此時客戶端程序是在自己的數據庫中獲取到了這條數據的最新版本號,比如說版本號是2
PUT /test_index/test_type/8?version=2&version_type=external
{
  "test_field":"test2"
}
#成功返回
{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "8",
  "_version": 2,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

# 4. 模擬第二個客戶端,同時拿到了自己數據庫中維護的那個版本號,也是2,同時基于version=2發起了修改
PUT /test_index/test_type/8?version=2&version_type=external
{
  "test_field":"test3"
}
#錯誤返回
{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[test_type][8]: version conflict, current version [2] is higher or equal to the one provided [2]",
        "index_uuid": "BTr18MteTx6HxbRogSjrpw",
        "shard": "1",
        "index": "test_index"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[test_type][8]: version conflict, current version [2] is higher or equal to the one provided [2]",
    "index_uuid": "BTr18MteTx6HxbRogSjrpw",
    "shard": "1",
    "index": "test_index"
  },
  "status": 409
}

# 5. 在并發控制成功后,重新基于最新的版本號發起更新
#先獲取最新的版本號
GET /test_index/test_type/8
#version=2
#再使用大于最新的版本號的號碼進行修改
PUT /test_index/test_type/8?version=3&version_type=external
{
  "test_field":"test3"
}
#成功返回
{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "8",
  "_version": 3,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": false
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容