假設有這樣的狀況,在併發請求中多個客戶端都要對文檔進行更新,這時最後一次更新就會覆蓋掉前面的全部更新,這種狀況看起來是沒有問題的。web
可是若是咱們存放的是商品促銷時商品庫存信息,這是web1和web2訪問到都是有100的庫存量同時下單,就會出現後來者覆蓋前面更新庫存的狀況,這是就會出現咱們沒有足夠的商品給客戶。數據庫
變動越頻繁,讀數據和更新數據的間隙越長,也就越可能丟失變動。在這種狀況下數據庫一般有兩種解決辦法。適用悲觀鎖或者樂觀鎖。併發
悲觀鎖:這種方法被關係型數據庫普遍使用,它假定有變動衝突可能發生,所以阻塞訪問資源以防止衝突。 一個典型的例子是讀取一行數據以前先將其鎖住,確保只有放置鎖的線程可以對這行數據進行修改。異步
樂觀鎖:Elasticsearch 中使用的這種方法假定衝突是不可能發生的,而且不會阻塞正在嘗試的操做。 然而,若是源數據在讀寫當中被修改,更新將會失敗。應用程序接下來將決定該如何解決衝突。 例如,能夠重試更新、使用新的數據、或者將相關狀況報告給用戶。分佈式
Elasticsearch 是分佈式的,當文檔發生修改時,文檔的修改也要複製到其餘節點上面,同時Elasticsearch 也是異步和併發的,這意味着這些複製請求被並行發送,這些複製被髮送到其餘節點是順序是亂的,則就須要一種方式去控制保證新的修改不會被就的修改所覆蓋。ide
Elasticsearch 當文檔被修改時版本號遞增,Elasticsearch 使用這個 _version
號來確保變動以正確順序獲得執行。若是舊版本的文檔在新版本以後到達,它能夠被簡單的忽略。ui
咱們能夠利用 _version
號來確保 應用中相互衝突的變動不會致使數據丟失。咱們經過指定想要修改文檔的 version
號來達到這個目的。 若是該版本不是當前版本號,咱們的請求將會失敗。this
首先建立索引website:spa
PUT website
新增文檔:線程
PUT website/blog/1/_create { "title": "My first blog entry", "text": "Just trying this out..." }
響應體告訴咱們,這個新建立的文檔 _version
版本號是 1
{ "_index": "website", "_type": "blog", "_id": "1", "_version": 1, "result": "created", "_shards": { "total": 2, "successful": 2, "failed": 0 }, "_seq_no": 0, "_primary_term": 1 }
這時假設有兩個用戶將要對文檔進行修改,兩個用戶分區都讀取數據了版本號1
{ "_index": "website", "_type": "blog", "_id": "1", "_version": 1, "found": true, "_source": { "title": "My first blog entry", "text": "Just trying this out..." } }
第一個用更新文檔
PUT /website/blog/1?version=1 { "title": "My first blog entry", "text": "Starting to get the hang of this..." }
更新成功,並返回版本號爲2
{ "_index": "website", "_type": "blog", "_id": "1", "_version": 2, "result": "updated", "_shards": { "total": 2, "successful": 2, "failed": 0 }, "_seq_no": 1, "_primary_term": 1 }
這是第二個用戶由於也已經讀取了文檔數據且不知道第一個用戶已經對文檔進行過修改,這時他再對該文檔進行修改
PUT /website/blog/1?version=1 { "title": "The secend time update blog entry", "text": "Starting to get the hang of this..." }
返回狀態409更新失敗
{ "error": { "root_cause": [ { "type": "version_conflict_engine_exception", "reason": "[blog][1]: version conflict, current version [2] is different than the one provided [1]", "index_uuid": "XFDz_R-MTHqhQfu4_yXLLw", "shard": "3", "index": "website" } ], "type": "version_conflict_engine_exception", "reason": "[blog][1]: version conflict, current version [2] is different than the one provided [1]", "index_uuid": "XFDz_R-MTHqhQfu4_yXLLw", "shard": "3", "index": "website" }, "status": 409 }
這告訴咱們在 Elasticsearch 中這個文檔的當前 _version
號是 2
,但咱們指定的更新版本號爲 1
,更新不成功。全部文檔的更新或刪除 API,均可以接受 version
參數,這容許你在代碼中使用樂觀的併發控制,這是一種明智的作法。
一個常見的設置是使用其它數據庫做爲主要的數據存儲,使用 Elasticsearch 作數據檢索, 這意味着主數據庫的全部更改發生時都須要被複制到 Elasticsearch ,若是多個進程負責這一數據同步,你可能遇到相似於以前描述的併發問題。
若是你的主數據庫已經有了版本號 , 那麼你就能夠在 Elasticsearch 中經過增長 version_type=external
到查詢字符串的方式重用這些相同的版本號, 版本號必須是大於零的整數, 且小於 9.2E+18
— 一個 Java 中 long
類型的正值。
外部版本號的處理方式和咱們以前討論的內部版本號的處理方式有些不一樣, Elasticsearch 不是檢查當前_version
和請求中指定的版本號是否相同, 而是檢查當前 _version
是否 小於 指定的版本號。若是當前版本小於指定版本則更新成功,外部的版本號做爲文檔的新 _version
進行存儲。
外部版本號不只能夠在更新刪除時使用也能夠在新建文檔時使用:
PUT /website/blog/2?version=5&version_type=external { "title": "My first external blog entry", "text": "Starting to get the hang of this..." }
執行結果:新增文檔版本號5
{ "_index": "website", "_type": "blog", "_id": "2", "_version": 5, "result": "created", "_shards": { "total": 2, "successful": 2, "failed": 0 }, "_seq_no": 0, "_primary_term": 1 }
如今咱們更新這個文檔,指定一個新的 version
號是 10
PUT /website/blog/2?version=10&version_type=external { "title": "My first external blog entry", "text": "Starting to get the hang of this..." }
響應結果爲更新了版本號爲10
{ "_index": "website", "_type": "blog", "_id": "2", "_version": 10, "result": "updated", "_shards": { "total": 2, "successful": 2, "failed": 0 }, "_seq_no": 1, "_primary_term": 1 }
而後咱們查詢該文檔信息
GET website/blog/2
響應結果
{ "_index": "website", "_type": "blog", "_id": "2", "_version": 10, "found": true, "_source": { "title": "My first external blog entry", "text": "Starting to get the hang of this..." } }