ElasticSearch進(jìn)階篇(一)--版本控制

一、前言

ElasticSearch(以下簡(jiǎn)稱ES)的數(shù)據(jù)寫入支持高并發(fā),高并發(fā)就會(huì)帶來(lái)很普遍的數(shù)據(jù)一致性問(wèn)題。常見(jiàn)的解決方法就是加鎖。同樣,ES為了保證高并發(fā)寫的數(shù)據(jù)一致性問(wèn)題,加入了類似于鎖的實(shí)現(xiàn)方法--版本控制。鎖從其中的一個(gè)角度可分為樂(lè)觀鎖和悲觀鎖。

對(duì)于同一個(gè)數(shù)據(jù)的并發(fā)操作,悲觀鎖認(rèn)為自己在使用數(shù)據(jù)的時(shí)候一定會(huì)有別的線程過(guò)來(lái)修改數(shù)據(jù),因此在獲取數(shù)據(jù)的時(shí)候會(huì)先加鎖,確保數(shù)據(jù)不會(huì)被別的線程修改。而樂(lè)觀鎖則認(rèn)為自己在使用數(shù)據(jù)時(shí)不會(huì)有別的線程來(lái)修改數(shù)據(jù),所以不會(huì)添加鎖,只是在更新或者提交數(shù)據(jù)的時(shí)候去判斷之前有沒(méi)有別的線程更新了這個(gè)數(shù)據(jù)。那么ES屬于那種鎖呢?下面大獅兄就和大家一起探討官方的具體做法來(lái)回答這個(gè)問(wèn)題。

二、版本控制實(shí)現(xiàn)及驗(yàn)證

1. ES6.7 Before

# 新建測(cè)試索引
PUT test
{
  "settings" : {
    "number_of_shards" : "3",
    "number_of_replicas" : "0"
  }
}

## 插入文檔
PUT test/_doc/1 
{"user": "zhangsan", "age": 12}

## 響應(yīng)結(jié)果
{
  "_index" : "test",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

更新文檔(version版本大于已寫入文檔版本),更新年齡為10,版本號(hào)為200

## 更新文檔
PUT test/_doc/1?version=200&version_type=external
{"user": "zhangsan", "age": 10}

## 返回結(jié)果
{
  "_index" : "test",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 200,
  "result" : "updated",
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}

## 查詢文檔
GET test/_doc/1
## 返回結(jié)果
{
  "_index" : "test",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 200,
  "_seq_no" : 1,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "user" : "zhangsan",
    "age" : 10
  }
}

更新成功,年齡更新為10且版本號(hào)更新為200

更新文檔(version版本小于或等于已寫入文檔版本),更新年齡為22,版本號(hào)為180

## 更新文檔
PUT test/_doc/1?version=180&version_type=external
{"user": "zhangsan", "age": 22}

## 返回結(jié)果
{
  "error" : {
    "root_cause" : [
      {
        "type" : "version_conflict_engine_exception",
        "reason" : "[1]: version conflict, current version [200] is higher or equal to the one provided [180]",
        "index_uuid" : "fCv7Q1dkTl6e9E1Z0dNE1g",
        "shard" : "2",
        "index" : "test"
      }
    ],
    "type" : "version_conflict_engine_exception",
    "reason" : "[1]: version conflict, current version [200] is higher or equal to the one provided [180]",
    "index_uuid" : "fCv7Q1dkTl6e9E1Z0dNE1g",
    "shard" : "2",
    "index" : "test"
  },
  "status" : 409
}

## 查詢文檔
GET test/_doc/1

## 返回結(jié)果
{
  "_index" : "test",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 200,
  "_seq_no" : 1,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "user" : "zhangsan",
    "age" : 10
  }
}

更新失敗,數(shù)據(jù)沒(méi)有變化,提示版本沖突,現(xiàn)有的版本號(hào)大于要插入的版本號(hào)。

  • vertion_type=external 或者 vertion_type=external_gt :目標(biāo)版本號(hào)大于已有的版本號(hào)才會(huì)更新成功。
  • vertion_type=external_gte :目標(biāo)版本號(hào)大于或等于已有的版本號(hào)才會(huì)更新成功。

2. ES6.7 OR Later

# 新建測(cè)試索引
PUT testccc
{
  "settings" : {
    "number_of_shards" : "1",
    "number_of_replicas" : "0"
  }
}


## 插入文檔
PUT testccc/_doc/1 
{"user": "lisi", "age": 12}

## 返回結(jié)果
{
  "_index" : "testccc",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

返回結(jié)果注意最后的兩個(gè)字段,_seq_no表示序列號(hào)是自增的,_primary_term表是文檔位于哪個(gè)shard。

更新數(shù)據(jù)(seq_no大于已寫入文檔序列號(hào)),更新年齡為10,序列號(hào)為20

## 更新文檔
PUT testccc/_doc/1?if_seq_no=20&if_primary_term=1
{"user": "lisi", "age": 10}

## 返回結(jié)果
{
  "error" : {
    "root_cause" : [
      {
        "type" : "version_conflict_engine_exception",
        "reason" : "[1]: version conflict, required seqNo [20], primary term [1]. current document has seqNo [0] and primary term [1]",
        "index_uuid" : "N6LzBNj9S5yqVWFubt3x4Q",
        "shard" : "0",
        "index" : "testccc"
      }
    ],
    "type" : "version_conflict_engine_exception",
    "reason" : "[1]: version conflict, required seqNo [20], primary term [1]. current document has seqNo [0] and primary term [1]",
    "index_uuid" : "N6LzBNj9S5yqVWFubt3x4Q",
    "shard" : "0",
    "index" : "testccc"
  },
  "status" : 409
}


## 查詢文檔
GET testccc/_doc/1 

## 返回結(jié)果
{
  "_index" : "testccc",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "user" : "lisi",
    "age" : 12
  }
}

更新失敗,數(shù)據(jù)無(wú)變化,提示版本沖突,最近文檔的序列號(hào)為0,要更新的序列號(hào)為20。

更新數(shù)據(jù)(seq_no等于已寫入文檔序列號(hào)),更新年齡為10

## 更新文檔
PUT testccc/_doc/1?if_seq_no=0&if_primary_term=1
{"user": "lisi", "age": 10}

## 返回結(jié)果
{
  "_index" : "testccc",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 2,
  "result" : "updated",
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}

## 查詢文檔
GET testccc/_doc/1 
## 返回結(jié)果
{
  "_index" : "testccc",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 2,
  "_seq_no" : 1,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "user" : "lisi",
    "age" : 10
  }
}

更新成功,且seq_no自增為1。

## 插入新文檔
PUT testccc/_doc/2
{"user": "wangwu", "age": 40}

## 返回結(jié)果
{
  "_index" : "testccc",
  "_type" : "_doc",
  "_id" : "2",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 2,
  "_primary_term" : 1
}

## 更新原文檔
PUT testccc/_doc/1?if_seq_no=1&if_primary_term=1
{"user": "lisi", "age": 50}

## 返回結(jié)果
{
  "_index" : "testccc",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 3,
  "result" : "updated",
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 3,
  "_primary_term" : 1
}

## 更新新寫入文檔
PUT testccc/_doc/2?if_seq_no=2&if_primary_term=1
{"user": "wangwu", "age": 80}

## 返回結(jié)果
{
  "_index" : "testccc",
  "_type" : "_doc",
  "_id" : "2",
  "_version" : 2,
  "result" : "updated",
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 4,
  "_primary_term" : 1
}

可以觀察到對(duì)于不同的文檔,seq_no總是自增1的。

三、總結(jié)

  1. ES版本控制類似于Java中的樂(lè)觀鎖,尤其對(duì)版本號(hào)字段的巧妙使用與解決樂(lè)觀鎖ABA問(wèn)題的CAS算法有異曲同工之妙。
  2. ES6.7之后添加的if_seq_no與if_primary_term版本控制是針對(duì)于整個(gè)索引的,而_version和version_type版本控制是針對(duì)于單條記錄(即單個(gè)文檔)的,不同的應(yīng)用場(chǎng)景可使用不同的版本控制策略。
  3. if_seq_no配置的值必須等于存在于現(xiàn)有文檔中才能更新成功,而_version配置的值根據(jù)不同的version_type,必須大于或者大于等于文檔最近更改過(guò)的_version值才能更新成功。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容