ElasticSearch进阶篇(一)--版本控制

一、前言

ElasticSearch(以下简称ES)的数据写入支持高并发,高并发就会带来很普遍的数据一致性问题。常见的解决方法就是加锁。同样,ES为了保证高并发写的数据一致性问题,加入了类似于锁的实现方法--版本控制。锁从其中的一个角度可分为乐观锁和悲观锁。

对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定会有别的线程过来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。而乐观锁则认为自己在使用数据时不会有别的线程来修改数据,所以不会添加锁,只是在更新或者提交数据的时候去判断之前有没有别的线程更新了这个数据。那么ES属于那种锁呢?下面大狮兄就和大家一起探讨官方的具体做法来回答这个问题。

二、版本控制实现及验证

1. ES6.7 Before

# 新建测试索引
PUT test
{
  "settings" : {
    "number_of_shards" : "3",
    "number_of_replicas" : "0"
  }
}

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

## 响应结果
{
  "_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,版本号为200

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

## 返回结果
{
  "_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
## 返回结果
{
  "_index" : "test",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 200,
  "_seq_no" : 1,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "user" : "zhangsan",
    "age" : 10
  }
}

更新成功,年龄更新为10且版本号更新为200

更新文档(version版本小于或等于已写入文档版本),更新年龄为22,版本号为180

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

## 返回结果
{
  "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

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

更新失败,数据没有变化,提示版本冲突,现有的版本号大于要插入的版本号。

  • vertion_type=external 或者 vertion_type=external_gt :目标版本号大于已有的版本号才会更新成功。
  • vertion_type=external_gte :目标版本号大于或等于已有的版本号才会更新成功。

2. ES6.7 OR Later

# 新建测试索引
PUT testccc
{
  "settings" : {
    "number_of_shards" : "1",
    "number_of_replicas" : "0"
  }
}


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

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

返回结果注意最后的两个字段,_seq_no表示序列号是自增的,_primary_term表是文档位于哪个shard。

更新数据(seq_no大于已写入文档序列号),更新年龄为10,序列号为20

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

## 返回结果
{
  "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 

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

更新失败,数据无变化,提示版本冲突,最近文档的序列号为0,要更新的序列号为20。

更新数据(seq_no等于已写入文档序列号),更新年龄为10

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

## 返回结果
{
  "_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 
## 返回结果
{
  "_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}

## 返回结果
{
  "_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}

## 返回结果
{
  "_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}

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

可以观察到对于不同的文档,seq_no总是自增1的。

三、总结

  1. ES版本控制类似于Java中的乐观锁,尤其对版本号字段的巧妙使用与解决乐观锁ABA问题的CAS算法有异曲同工之妙。
  2. ES6.7之后添加的if_seq_no与if_primary_term版本控制是针对于整个索引的,而_version和version_type版本控制是针对于单条记录(即单个文档)的,不同的应用场景可使用不同的版本控制策略。
  3. if_seq_no配置的值必须等于存在于现有文档中才能更新成功,而_version配置的值根据不同的version_type,必须大于或者大于等于文档最近更改过的_version值才能更新成功。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容