Elasticsearch數據處理(三)
1.處理沖突
當使用 index API更新文檔的時候,我們讀取原始文檔,做修改,然後將整個文檔(whole document)一次性重新索引。
最近的索引請求會生效——Elasticsearch中只存儲最後被索引的任何文檔。如果其他人同時也修改了這個文檔,他們的修改將會丟失。
很多時候,這不是問題。也許我們的主數據存儲是關系數據庫,我們只是將數據復制到Elasticsearch中以使其可搜索。
也許兩個人幾乎不可能同時更改同一個文檔。或者,如果偶爾失去變化,對我們的業務來說也許並不重要。
但有時失去變化非常重要。想象一下,我們正在使用Elasticsearch來存儲我們在線商店中庫存的小部件數量。
有一天,管理層決定進行銷售。突然間,我們每秒都在銷售幾個小部件。想象一下,兩個並行運行的Web進程,每個進程都處理一個小部件的銷售,如下圖顯示沒有並發控制的結果。
在數據庫領域,通常使用兩種方法來確保在並發更新時不會丟失更改:
- 悲觀的並發控制
- 這種方法廣泛用於關系數據庫,它假定可能發生沖突的更改,因此阻止對資源的並發訪問以防止沖突。
- 一個典型的例子是在讀取數據之前鎖定一行,確保只有放置鎖的線程才能對該行中的數據進行更改。
- 樂觀的並發控制
- 由Elasticsearch使用, 這種方法假定沖突不太可能發生,並且不會阻止嘗試操作。
- 但是,如果在讀取和寫入之間修改了基礎數據,則更新將失敗。然後由應用程序決定如何解決沖突。例如,它可以使用新數據重新嘗試更新,或者可以向用戶報告情況。
2.樂觀並發控制
Elasticsearch是分布式的。
如果要創建,更新或刪除新版本的文檔,則必須將其復制到群集中的其他節點。
Elasticsearch也是異步和並發的,這意味著這些復制請求是並行發送的,並且可能不按順序到達目的地。
Elasticsearch需要一種方法來確保舊版本的文檔永遠不會覆蓋較新的版本。
當我們之前討論過index
,get
和delete
請求時,我們指出每個文檔都有一個_version
數字,只要文檔被更改,該數字就會遞增。
Elasticsearch使用此_version
數字來確保以正確的順序應用更改。
如果在新版本之後舊版本文檔到達,則可以簡單地忽略它。
我們可以利用這個_version
數字來確保 我們的應用程序所做的沖突的更改不會導致數據丟失。
我們希望通過指定更改的文檔的version編號來完成此操作。如果該版本不再是最新版本,我們的請求將失敗。
讓我們創建一個新的博客文章:
curl -X PUT "localhost:9200/website/blog/1/_create" -H ‘Content-Type: application/json‘ -d‘
{
"title": "My first blog entry",
"text": "Just trying this out..."
}
‘
響應正文告訴我們這個新創建的文檔_version
為1
。現在假設我們要編輯文檔:我們將其數據加載到Web表單中,進行更改,然後保存新版本。
首先我們檢索文檔:
curl -X GET "localhost:9200/website/blog/1"
響應正文包含相同的版本_version
1
:
{
"_index" : "website",
"_type" : "blog",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title": "My first blog entry",
"text": "Just trying this out..."
}
}
現在,當我們嘗試通過重新索引文檔來保存更改時,我們指定此次修改對應的版本號version
:
curl -X PUT "localhost:9200/website/blog/1?version=1" -H ‘Content-Type: application/json‘ -d‘
{
"title": "My first blog entry",
"text": "Starting to get the hang of this..."
}
‘
只有當Elasticsearch中此文檔的版本為1時,我們的更新才會生效。
此請求成功(當前此文檔的版本為1),響應正文告訴我們_version
已增加到2(因為數據更新了)
:
{
"_index": "website",
"_type": "blog",
"_id": "1",
"_version": 2
"created": false
}
但是,如果我們要重新運行相同的索引請求,仍然指定 version=1
,Elasticsearch將使用409 Conflict
HTTP響應代碼進行響應,響應內容大致如下:
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[blog][1]: version conflict, current [2], provided [1]",
"index": "website",
"shard": "3"
}
],
"type": "version_conflict_engine_exception",
"reason": "[blog][1]: version conflict, current [2], provided [1]",
"index": "website",
"shard": "3"
},
"status": 409
}
這告訴我們Elasticsearch中當前的文檔的_version
編號是2
,但我們指定更新版本為1
。
我們現在所做的工作取決於我們的應用要求。我們可以告訴用戶其他人已經對文檔進行了更改,並在嘗試再次保存之前查看更改。或者,與stock_count
先前窗口小部件的情況一樣,我們可以檢索最新文檔並嘗試重新應用更改。
更新或刪除文檔的所有修改API都接受一個version
參數,該參數允許您將樂觀並發控制應用於代碼中有需要的部分。
3.使用外部系統中的版本
常見的設置是使用其他數據庫作為主數據存儲,使用Elasticsearch使數據可搜索, 這意味著對主數據庫的所有更改都需要在發生時復制到Elasticsearch。
如果多個進程負責此數據同步,則可能會遇到類似於前面描述的並發問題。
如果您的主數據庫已經具有版本號 - 或者諸如 timestamp
可以用作版本號的值 - 那麽您可以通過添加version_type=external
到查詢字符串來在Elasticsearch中重用這些相同的版本號。
版本號必須是大於零且小於9.2e+18的整數 - 在Java中為正值
long
。
處理外部版本號的方式與我們之前討論的內部版本號略有不同。
Elasticsearch檢查當前是否小於指定版本,而不是檢查當前_version
是否與請求中指定的當前相同。如果請求成功,則外部版本號將存儲為文檔的新版本。_version
外部版本號不僅可以在索引和刪除請求中指定,還可以在創建新文檔時指定。
例如,要創建一個外部版本號為5的新博客帖子,我們可以執行以下操作:
curl -X PUT "localhost:9200/website/blog/2?version=5&version_type=external" -H ‘Content-Type: application/json‘ -d‘
{
"title": "My first external blog entry",
"text": "Starting to get the hang of this..."
}
‘
在回復中,我們可以看到當前的_version
數字是5
:
{
"_index": "website",
"_type": "blog",
"_id": "2",
"_version": 5,
"created": true
}
現在我們更新這個文檔,指定一個新的version
數字10
:
curl -X PUT "localhost:9200/website/blog/2?version=10&version_type=external" -H ‘Content-Type: application/json‘ -d‘
{
"title": "My first external blog entry",
"text": "This is a piece of cake..."
}
‘
請求成功並將當前設置_version
為10
:
{
"_index": "website",
"_type": "blog",
"_id": "2",
"_version": 10,
"created": false
}
如果您要重新運行此請求,它將產生版本沖突錯誤,因為指定的外部版本號不高於Elasticsearch中的當前版本。
Elasticsearch數據處理(三)