## 處理沖突
當使用`index` API更新文檔的時候,我們讀取原始文檔,做修改,然后將**整個文檔(whole document)**一次性重新索引。最近的索引請求會生效——Elasticsearch中只存儲最后被索引的任何文檔。如果其他人同時也修改了這個文檔,他們的修改將會丟失。
很多時候,這并不是一個問題。或許我們主要的數據存儲在關系型數據庫中,然后拷貝數據到Elasticsearch中只是為了可以用于搜索。或許兩個人同時修改文檔的機會很少。亦或者偶爾的修改丟失對于我們的工作來說并無大礙。
但有時丟失修改是一個**很嚴重**的問題。想象一下我們使用Elasticsearch存儲大量在線商店的庫存信息。每當銷售一個商品,Elasticsearch中的庫存就要減一。
一天,老板決定做一個促銷。瞬間,我們每秒就銷售了幾個商品。想象兩個同時運行的web進程,兩者同時處理一件商品的訂單:

`web_1`讓`stock_count`失效是因為`web_2`沒有察覺到`stock_count`的拷貝已經過期(譯者注:`web_1`取數據,減一后更新了`stock_count`。可惜在`web_1`更新`stock_count`前它就拿到了數據,這個數據已經是過期的了,當`web_2`再回來更新`stock_count`時這個數字就是錯的。這樣就會造成看似賣了一件東西,其實是賣了兩件,這個應該屬于幻讀。)。結果是我們認為自己確實還有更多的商品,最終顧客會因為銷售給他們沒有的東西而失望。
變化越是頻繁,或讀取和更新間的時間越長,越容易丟失我們的更改。
在數據庫中,有兩種通用的方法確保在并發更新時修改不丟失:
### 悲觀并發控制(Pessimistic concurrency control)
這在關系型數據庫中被廣泛的使用,假設沖突的更改經常發生,為了解決沖突我們把訪問區塊化。典型的例子是在讀一行數據前鎖定這行,然后確保只有加鎖的那個線程可以修改這行數據。
### 樂觀并發控制(Optimistic concurrency control):
被Elasticsearch使用,假設沖突不經常發生,也不區塊化訪問,然而,如果在讀寫過程中數據發生了變化,更新操作將失敗。這時候由程序決定在失敗后如何解決沖突。實際情況中,可以重新嘗試更新,刷新數據(重新讀取)或者直接反饋給用戶。
## 樂觀并發控制
Elasticsearch是分布式的。當文檔被創建、更新或刪除,文檔的新版本會被復制到集群的其它節點。Elasticsearch即是同步的又是異步的,意思是這些復制請求都是平行發送的,并**無序(out of sequence)**的到達目的地。這就需要一種方法確保老版本的文檔永遠不會覆蓋新的版本。
上文我們提到`index`、`get`、`delete`請求時,我們指出每個文檔都有一個`_version`號碼,這個號碼在文檔被改變時加一。Elasticsearch使用這個`_version`保證所有修改都被正確排序。當一個舊版本出現在新版本之后,它會被簡單的忽略。
我們利用`_version`的這一優點確保數據不會因為修改沖突而丟失。我們可以指定文檔的`version`來做想要的更改。如果那個版本號不是現在的,我們的請求就失敗了。
Let's create a new blog post:
讓我們創建一個新的博文:
```Javascript
PUT /website/blog/1/_create
{
"title": "My first blog entry",
"text": "Just trying this out..."
}
```
響應體告訴我們這是一個新建的文檔,它的`_version`是`1`。現在假設我們要編輯這個文檔:把數據加載到web表單中,修改,然后保存成新版本。
首先我們檢索文檔:
```Javascript
GET /website/blog/1
```
響應體包含相同的`_version`是`1`
```Javascript
{
"_index" : "website",
"_type" : "blog",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title": "My first blog entry",
"text": "Just trying this out..."
}
}
```
現在,當我們通過重新索引文檔保存修改時,我們這樣指定了`version`參數:
```Javascript
PUT /website/blog/1?version=1 <1>
{
"title": "My first blog entry",
"text": "Starting to get the hang of this..."
}
```
- <1> 我們只希望文檔的`_version`是`1`時更新才生效。
This request succeeds, and the response body tells us that the `_version`
has been incremented to `2`:
請求成功,響應體告訴我們`_version`已經增加到`2`:
```Javascript
{
"_index": "website",
"_type": "blog",
"_id": "1",
"_version": 2
"created": false
}
```
然而,如果我們重新運行相同的索引請求,依舊指定`version=1`,Elasticsearch將返回`409 Conflict`狀態的HTTP響應。響應體類似這樣:
```Javascript
{
"error" : "VersionConflictEngineException[[website][2] [blog][1]:
version conflict, current [2], provided [1]]",
"status" : 409
}
```
這告訴我們當前`_version`是`2`,但是我們指定想要更新的版本是`1`。
我們需要做什么取決于程序的需求。我們可以告知用戶其他人修改了文檔,你應該在保存前再看一下。而對于上文提到的商品`stock_count`,我們需要重新檢索最新文檔然后申請新的更改操作。
所有更新和刪除文檔的請求都接受`version`參數,它可以允許在你的代碼中增加樂觀鎖控制。
## 使用外部版本控制系統
一種常見的結構是使用一些其他的數據庫做為主數據庫,然后使用Elasticsearch搜索數據,這意味著所有主數據庫發生變化,就要將其拷貝到Elasticsearch中。如果有多個進程負責這些數據的同步,就會遇到上面提到的并發問題。
如果主數據庫有版本字段——或一些類似于`timestamp`等可以用于版本控制的字段——是你就可以在Elasticsearch的查詢字符串后面添加`version_type=external`來使用這些版本號。版本號必須是整數,大于零小于`9.2e+18`——Java中的正的`long`。
外部版本號與之前說的內部版本號在處理的時候有些不同。它不再檢查`_version`是否與請求中指定的**一致**,而是檢查是否**小于**指定的版本。如果請求成功,外部版本號就會被存儲到`_version`中。
外部版本號不僅在索引和刪除請求中指定,也可以在**創建(create)**新文檔中指定。
例如,創建一個包含外部版本號`5`的新博客,我們可以這樣做:
```Javascript
PUT /website/blog/2?version=5&version_type=external
{
"title": "My first external blog entry",
"text": "Starting to get the hang of this..."
}
```
在響應中,我們能看到當前的`_version`號碼是`5`:
```Javascript
{
"_index": "website",
"_type": "blog",
"_id": "2",
"_version": 5,
"created": true
}
```
現在我們更新這個文檔,指定一個新`version`號碼為`10`:
```Javascript
PUT /website/blog/2?version=10&version_type=external
{
"title": "My first external blog entry",
"text": "This is a piece of cake..."
}
```
請求成功的設置了當前`_version`為`10`:
```Javascript
{
"_index": "website",
"_type": "blog",
"_id": "2",
"_version": 10,
"created": false
}
```
如果你重新運行這個請求,就會返回一個像之前一樣的沖突錯誤,因為指定的外部版本號不大于當前在Elasticsearch中的版本。
- Introduction
- 入門
- 是什么
- 安裝
- API
- 文檔
- 索引
- 搜索
- 聚合
- 小結
- 分布式
- 結語
- 分布式集群
- 空集群
- 集群健康
- 添加索引
- 故障轉移
- 橫向擴展
- 更多擴展
- 應對故障
- 數據
- 文檔
- 索引
- 獲取
- 存在
- 更新
- 創建
- 刪除
- 版本控制
- 局部更新
- Mget
- 批量
- 結語
- 分布式增刪改查
- 路由
- 分片交互
- 新建、索引和刪除
- 檢索
- 局部更新
- 批量請求
- 批量格式
- 搜索
- 空搜索
- 多索引和多類型
- 分頁
- 查詢字符串
- 映射和分析
- 數據類型差異
- 確切值對決全文
- 倒排索引
- 分析
- 映射
- 復合類型
- 結構化查詢
- 請求體查詢
- 結構化查詢
- 查詢與過濾
- 重要的查詢子句
- 過濾查詢
- 驗證查詢
- 結語
- 排序
- 排序
- 字符串排序
- 相關性
- 字段數據
- 分布式搜索
- 查詢階段
- 取回階段
- 搜索選項
- 掃描和滾屏
- 索引管理
- 創建刪除
- 設置
- 配置分析器
- 自定義分析器
- 映射
- 根對象
- 元數據中的source字段
- 元數據中的all字段
- 元數據中的ID字段
- 動態映射
- 自定義動態映射
- 默認映射
- 重建索引
- 別名
- 深入分片
- 使文本可以被搜索
- 動態索引
- 近實時搜索
- 持久化變更
- 合并段
- 結構化搜索
- 查詢準確值
- 組合過濾
- 查詢多個準確值
- 包含,而不是相等
- 范圍
- 處理 Null 值
- 緩存
- 過濾順序
- 全文搜索
- 匹配查詢
- 多詞查詢
- 組合查詢
- 布爾匹配
- 增加子句
- 控制分析
- 關聯失效
- 多字段搜索
- 多重查詢字符串
- 單一查詢字符串
- 最佳字段
- 最佳字段查詢調優
- 多重匹配查詢
- 最多字段查詢
- 跨字段對象查詢
- 以字段為中心查詢
- 全字段查詢
- 跨字段查詢
- 精確查詢
- 模糊匹配
- Phrase matching
- Slop
- Multi value fields
- Scoring
- Relevance
- Performance
- Shingles
- Partial_Matching
- Postcodes
- Prefix query
- Wildcard Regexp
- Match phrase prefix
- Index time
- Ngram intro
- Search as you type
- Compound words
- Relevance
- Scoring theory
- Practical scoring
- Query time boosting
- Query scoring
- Not quite not
- Ignoring TFIDF
- Function score query
- Popularity
- Boosting filtered subsets
- Random scoring
- Decay functions
- Pluggable similarities
- Conclusion
- Language intro
- Intro
- Using
- Configuring
- Language pitfalls
- One language per doc
- One language per field
- Mixed language fields
- Conclusion
- Identifying words
- Intro
- Standard analyzer
- Standard tokenizer
- ICU plugin
- ICU tokenizer
- Tidying text
- Token normalization
- Intro
- Lowercasing
- Removing diacritics
- Unicode world
- Case folding
- Character folding
- Sorting and collations
- Stemming
- Intro
- Algorithmic stemmers
- Dictionary stemmers
- Hunspell stemmer
- Choosing a stemmer
- Controlling stemming
- Stemming in situ
- Stopwords
- Intro
- Using stopwords
- Stopwords and performance
- Divide and conquer
- Phrase queries
- Common grams
- Relevance
- Synonyms
- Intro
- Using synonyms
- Synonym formats
- Expand contract
- Analysis chain
- Multi word synonyms
- Symbol synonyms
- Fuzzy matching
- Intro
- Fuzziness
- Fuzzy query
- Fuzzy match query
- Scoring fuzziness
- Phonetic matching
- Aggregations
- overview
- circuit breaker fd settings
- filtering
- facets
- docvalues
- eager
- breadth vs depth
- Conclusion
- concepts buckets
- basic example
- add metric
- nested bucket
- extra metrics
- bucket metric list
- histogram
- date histogram
- scope
- filtering
- sorting ordering
- approx intro
- cardinality
- percentiles
- sigterms intro
- sigterms
- fielddata
- analyzed vs not
- 地理坐標點
- 地理坐標點
- 通過地理坐標點過濾
- 地理坐標盒模型過濾器
- 地理距離過濾器
- 緩存地理位置過濾器
- 減少內存占用
- 按距離排序
- Geohashe
- Geohashe
- Geohashe映射
- Geohash單元過濾器
- 地理位置聚合
- 地理位置聚合
- 按距離聚合
- Geohash單元聚合器
- 范圍(邊界)聚合器
- 地理形狀
- 地理形狀
- 映射地理形狀
- 索引地理形狀
- 查詢地理形狀
- 在查詢中使用已索引的形狀
- 地理形狀的過濾與緩存
- 關系
- 關系
- 應用級別的Join操作
- 扁平化你的數據
- Top hits
- Concurrency
- Concurrency solutions
- 嵌套
- 嵌套對象
- 嵌套映射
- 嵌套查詢
- 嵌套排序
- 嵌套集合
- Parent Child
- Parent child
- Indexing parent child
- Has child
- Has parent
- Children agg
- Grandparents
- Practical considerations
- Scaling
- Shard
- Overallocation
- Kagillion shards
- Capacity planning
- Replica shards
- Multiple indices
- Index per timeframe
- Index templates
- Retiring data
- Index per user
- Shared index
- Faking it
- One big user
- Scale is not infinite
- Cluster Admin
- Marvel
- Health
- Node stats
- Other stats
- Deployment
- hardware
- other
- config
- dont touch
- heap
- file descriptors
- conclusion
- cluster settings
- Post Deployment
- dynamic settings
- logging
- indexing perf
- rolling restart
- backup
- restore
- conclusion