Elasticsearch由淺入深(五)_version樂觀鎖、external version樂觀鎖、partial update、groovy腳本實現partial update

基於_version進行樂觀鎖併發控制

  1. 先構造一條數據出來
    PUT /test_index/test_type/7
    {
      "test_field": "test test"
    }
  2. 模擬兩個客戶端,都獲取到了同一條數據
    GET test_index/test_type/7
    
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "7",
      "_version": 1,
      "found": true,
      "_source": {
        "test_field": "test test"
      }
    }
  3. 其中一個客戶端,先更新了一下這個數據mysql

    同時帶上數據的版本號,確保說,es中的數據的版本號,跟客戶端中的數據的版本號是相同的,才能修改
    PUT /test_index/test_type/7?version=1 
    {
      "test_field": "test client 1"
    }
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "7",
      "_version": 2,
      "result": "updated",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": false
    }
    View Code
  4. 另一個客戶端,嘗試基於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": "6m0G7yx7R1KECWWGnfH1sw",
            "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": "6m0G7yx7R1KECWWGnfH1sw",
        "shard": "3",
        "index": "test_index"
      },
      "status": 409
    }
  5. 在樂觀鎖成功阻止併發問題以後,嘗試正確的完成更新
    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 client 2"
    }
    
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "7",
      "_version": 3,
      "result": "updated",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": false
    }
    View Code

基於external version進行樂觀鎖併發控制

什麼是external version

es提供了一個feature,就是說,你能夠不用它提供的內部_version版本號來進行併發控制,能夠基於你本身維護的一個版本號來進行併發控制。舉個列子,加入你的數據在mysql裏也有一份,而後你的應用系統自己就維護了一個版本號,不管是什麼本身生成的,程序控制的。這個時候,你進行樂觀鎖併發控制的時候,可能並非想要用es內部的_version來進行控制,而是用你本身維護的那個version來進行控制。sql

?version=1
?version=1&version_type=external

version_type=external,惟一的區別在於,_version,只有當你提供的version與es中的_version如出一轍的時候,才能夠進行修改,只要不同,就報錯;當version_type=external的時候,只有當你提供的version比es中的_version大的時候,才能完成修改數據庫

es,_version=1,?version=1,才能更新成功
es,_version=1,?version>1&version_type=external,才能成功,好比說?version=2&version_type=external
  1. 先構造一條數據
    PUT /test_index/test_type/8
    {
      "test_field": "test"
    }
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "8",
      "_version": 1,
      "result": "created",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": true
    }
  2. 模擬兩個客戶端同時查詢到這條數據
    GET /test_index/test_type/8
    
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "8",
      "_version": 1,
      "found": true,
      "_source": {
        "test_field": "test"
      }
    }
  3. 第一個客戶端先進行修改,此時客戶端程序是在本身的數據庫中獲取到了這條數據的最新版本號,好比說是2
    PUT /test_index/test_type/8?version=2&version_type=external
    {
      "test_field": "test client 1"
    }
    
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "8",
      "_version": 2,
      "result": "updated",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": false
    }
  4. 模擬第二個客戶端,同時拿到了本身數據庫中維護的那個版本號,也是2,同時基於version=2發起了修改
    PUT /test_index/test_type/8?version=2&version_type=external
    {
      "test_field": "test client 2"
    }
    
    {
      "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": "6m0G7yx7R1KECWWGnfH1sw",
            "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": "6m0G7yx7R1KECWWGnfH1sw",
        "shard": "1",
        "index": "test_index"
      },
      "status": 409
    }
  5. 在併發控制成功後,從新基於最新的版本號發起更新
    GET /test_index/test_type/8
    
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "8",
      "_version": 2,
      "found": true,
      "_source": {
        "test_field": "test client 1"
      }
    }
    
    PUT /test_index/test_type/8?version=3&version_type=external
    {
      "test_field": "test client 2"
    }
    
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "8",
      "_version": 3,
      "result": "updated",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": false
    }

partial update

簡介

通常對應到應用程序中,每次的執行流程基本是這樣的:json

  1. 應用程序先發起一個get請求,獲取到document,展現到前臺界面,供用戶查看和修改
  2. 用戶在前臺界面修改數據,發送到後臺
  3. 後臺代碼,會將用戶修改的數據在內存中進行執行,而後封裝好修改後的全量數據
  4. 而後發送PUT請求,到es中,進行全量替換
  5. es將老的document標記爲deleted,而後從新建立一個新的document

partial update是僅僅進行部分更新,無需全量替換網絡

語法:多線程

post /index/type/id/_update 
{
   "doc": {
      "要修改的少數幾個field便可,不須要全量的數據"
   }
}

看起來,好像就比較方便了,每次就傳遞少數幾個發生修改的field便可,不須要將全量的document數據發送過去併發

partial update原理及優勢

全量更新的原理ide

其實es內部對partial update的實際執行,跟傳統的全雖替換方式,是幾乎同樣的post

  1. 內部先獲取document
  2. 將傳過來的field更新到document的json中
  3. 將老的document標記爲deleted
  4. 將修改後的新的document建立出來

partial update相較於全星替換的優勢:性能

  1. 全部的查詢、修改和寫國操做,都發生在es中的一個shard內部,避免了全部的網絡數據傳輸的開銷(減小2次網絡請求) ,大大提高了性能
  2. 減小了查詢和修改中的時間間隔,能夠有效減小併發衝突的狀況

示例

PUT /test_index/test_type/10
{
  "test_field1": "test1",
  "test_field2": "test2"
}

partial update

POST /test_index/test_type/10/_update
{
  "doc": {
    "test_field2": "updated test2"
  }
}

基於groovy腳本實現partial update

ES實際上是有個內置的腳本支持的,能夠基於groovy腳本實現各類各樣的複雜操做,基於groovy腳本,如何執行partial update

初始化腳本:

PUT /test_index/test_type/11
{
  "num": 0,
  "tags": []
}
  • 內置腳本
    POST /test_index/test_type/11/_update
    {
      "script": "ctx._source.num+=1"
    }
  • 外部腳本
    假設咱們想在tags中新增內容
    咱們能夠寫成腳本,在 \config\scripts 目錄下新建 test-add-yags.groovy 腳本:
    ctx._source.tags+=new_tag
    POST /test_index/test_type/11/_update
    {
      "script": {
        "lang": "groovy",
        "file": "test-add-yags",
        "params": {
          "new_tag":"tag1"
        }
      }
    }
    GET /test_index/test_type/11
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "11",
      "_version": 3,
      "found": true,
      "_source": {
        "num": 1,
        "tags": [
          "tag1"
        ]
      }
    }
    View Code

     

  • 用腳本刪除文檔
    若是num等於count則刪除
    咱們能夠寫成腳本,在 \config\scripts 目錄下新建 test-delete-document.groovy 腳本:
    ctx.op = ctx._source.num == count ? 'delete' : 'none'
    POST /test_index/test_type/11/_update
    {
      "script": {
        "lang": "groovy",
        "file": "test-delete-document",
        "params": {
          "count":1
        }
      }
    }
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "11",
      "_version": 4,
      "result": "deleted",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      }
    }
  • upsert操做
    顧名思義:upsert=update+insert
    不存在document進行更新
    POST /test_index/test_type/11/_update
    {
      "doc": {
        "num": 1
      }
    }
    
    {
      "error": {
        "root_cause": [
          {
            "type": "document_missing_exception",
            "reason": "[test_type][11]: document missing",
            "index_uuid": "d7GOSxVnTNKYuI8x7cZfkA",
            "shard": "4",
            "index": "test_index"
          }
        ],
        "type": "document_missing_exception",
        "reason": "[test_type][11]: document missing",
        "index_uuid": "d7GOSxVnTNKYuI8x7cZfkA",
        "shard": "4",
        "index": "test_index"
      },
      "status": 404
    }
    View Code

    upsert操做

    POST /test_index/test_type/11/_update
    {
      "script": "ctx._source.num+=1",
      "upsert": {
        "num": 0,
        "tags": []
      }
    }

     

圖解partial update樂觀鎖併發控制原理

partial update內部會自動執行咱們以前所說的樂觀鎖的併發控制策

避免自動partial update fail掉的解決方案,使用 retry_on_conflict 

retry策略 

  1.  再次獲取document數據和最新版本號
  2. 基於最新版本號再次去更新,若是成功那麼就ok了、若是失敗,重複1和2兩個步驟,最多重複幾回呢?能夠經過retry那個參數的值指定,好比5次

示例:

post /index/type/id/_update?retry_on_conflict=5&version=6
相關文章
相關標籤/搜索