1、ES并發(fā)沖突問題
2、悲觀鎖與樂觀鎖兩種并發(fā)控制解決方案
- 悲觀鎖的優(yōu)點是:方便,直接加鎖,對應(yīng)用程序來說,透明,不需要做額外的操作;缺點,并發(fā)能力很低,同一時間只能一條線程操作數(shù)據(jù)
- 樂觀鎖的優(yōu)點是:并發(fā)能力很高,不給數(shù)據(jù)加鎖,大量線程并發(fā)操作;缺點,麻煩,每次更新的時候,都要先對比版本號,然后可能需要重新加載數(shù)據(jù),再次修改,再寫;這個過程,可能要重復(fù)好幾次。
3、Elasticsearch內(nèi)部如何基于_version進(jìn)行樂觀鎖并發(fā)控制
(1)_version元數(shù)據(jù)
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
}
第一次創(chuàng)建一個document的時候,它的_version內(nèi)部版本號就是1;以后,每次對這個document執(zhí)行修改或者刪除操作,都會對這個_version版本號自動加1;哪怕是刪除,也會對這條數(shù)據(jù)的版本號加1
{
"found": true,
"_index": "test_index",
"_type": "test_type",
"_id": "6",
"_version": 4,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
}
}
我們會發(fā)現(xiàn),在刪除一個document之后,可以從一個側(cè)面證明,它不是立即物理刪除掉的,因為它的一些版本號等信息還是保留著的。先刪除一條document,再重新創(chuàng)建這條document,其實會在delete version基礎(chǔ)之上,再把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)圖解內(nèi)部如何基于_version進(jìn)行樂觀鎖并發(fā)控制
4、實戰(zhàn)演練基于_version進(jìn)行樂觀鎖并發(fā)控制
- 先構(gòu)造一條數(shù)據(jù)出來
#添加
PUT /test_index/test_type/7
{
"test_field":"test test"
}
- 模擬兩個客戶端,都獲取到了同一條數(shù)據(jù)
#查詢
GET /test_index/test_type/7
#返回
{
"_index": "test_index",
"_type": "test_type",
"_id": "7",
"_version": 1,
"found": true,
"_source": {
"test_field": "test test"
}
}
- 其中一個客戶端,先更新了一下這個數(shù)據(jù),同時帶上數(shù)據(jù)的版本號,確保說,es中的數(shù)據(jù)的版本號,跟客戶端中的數(shù)據(jù)的版本號是相同的,才能修改
#修改
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
}
- 另外一個客戶端,嘗試基于version=1的數(shù)據(jù)去進(jìn)行修改,同樣帶上version版本號,進(jìn)行樂觀鎖的并發(fā)控制
#再次修改
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
}
- 在樂觀鎖成功阻止并發(fā)問題之后,嘗試正確的完成更新。帶上當(dāng)前正確的版本號,在進(jìn)行修改。
#查詢獲取版本號
GET /test_index/test_type/7
#返回
{
"_index": "test_index",
"_type": "test_type",
"_id": "7",
"_version": 2,
"found": true,
"_source": {
"test_field": "test client 1"
}
}
#帶上當(dāng)前版本號進(jìn)行修改
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
}
基于最新的數(shù)據(jù)和版本號,去進(jìn)行修改,修改后,帶上最新的版本號,可能這個步驟會需要反復(fù)執(zhí)行好幾次,才能成功,特別是在多線程并發(fā)更新同一條數(shù)據(jù)很頻繁的情況下
5. 實戰(zhàn)演練基于external version進(jìn)行樂觀鎖并發(fā)控制
es提供了一個feature,就是說,你可以不用它提供的內(nèi)部_version版本號來進(jìn)行并發(fā)控制,可以基于你自己維護(hù)的一個版本號來進(jìn)行并發(fā)控制。舉個列子,加入你的數(shù)據(jù)在mysql里也有一份,然后你的應(yīng)用系統(tǒng)本身就維護(hù)了一個版本號,無論是什么自己生成的,程序控制的。這個時候,你進(jìn)行樂觀鎖并發(fā)控制的時候,可能并不是想要用es內(nèi)部的_version來進(jìn)行控制,而是用你自己維護(hù)的那個version來進(jìn)行控制。
#語法:
?version=1&version_type=external
version_type=external,唯一的區(qū)別在于。
_version,只有當(dāng)你提供的version與es中的_version一模一樣的時候,才可以進(jìn)行修改,只要不一樣,就報錯;
version_type=external,只有當(dāng)你提供的version比es中的_version大的時候,才能完成修改
# 1. 先添加一條數(shù)據(jù)
PUT /test_index/test_type/8
{
"test_field":"test"
}
# 2. 模擬兩個客戶端同時查詢到這條數(shù)據(jù)
GET /test_index/test_type/8
# 3. 第一個客戶端先進(jìn)行修改,此時客戶端程序是在自己的數(shù)據(jù)庫中獲取到了這條數(shù)據(jù)的最新版本號,比如說版本號是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. 模擬第二個客戶端,同時拿到了自己數(shù)據(jù)庫中維護(hù)的那個版本號,也是2,同時基于version=2發(fā)起了修改
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. 在并發(fā)控制成功后,重新基于最新的版本號發(fā)起更新
#先獲取最新的版本號
GET /test_index/test_type/8
#version=2
#再使用大于最新的版本號的號碼進(jìn)行修改
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
}