一、前言

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值才能更新成功。

ElasticSearch进阶篇(一)--版本控制的更多相关文章

  1. Elasticsearch进阶篇(一)~head插件的安装与配置

    1.安装node.js 1.1.通过官网下载二进制安装包 https://nodejs.org/en/download/ 选择对应的版本,右键复制下载链接,进入linux目录,切换到要安装目录的磁盘. ...

  2. SQL Server调优系列进阶篇(如何维护数据库索引)

    前言 上一篇我们研究了如何利用索引在数据库里面调优,简要的介绍了索引的原理,更重要的分析了如何选择索引以及索引的利弊项,有兴趣的可以点击查看. 本篇延续上一篇的内容,继续分析索引这块,侧重索引项的日常 ...

  3. Membership三步曲之进阶篇 - 深入剖析Provider Model

    Membership 三步曲之进阶篇 - 深入剖析Provider Model 本文的目标是让每一个人都知道Provider Model 是什么,并且能灵活的在自己的项目中使用它. Membershi ...

  4. idea 插件的使用 进阶篇

    CSDN 2016博客之星评选结果公布    [系列直播]零基础学习微信小程序!      "我的2016"主题征文活动   博客的神秘功能 idea 插件的使用 进阶篇(个人收集 ...

  5. 2. web前端开发分享-css,js进阶篇

    一,css进阶篇: 等css哪些事儿看了两三遍之后,需要对看过的知识综合应用,这时候需要大量的实践经验, 简单的想法:把qq首页全屏另存为jpg然后通过ps工具切图结合css转换成html,有无从下手 ...

  6. windows系统快捷操作の进阶篇

    上次介绍了windows系统上一些自带的常用快捷键,有些确实很方便,也满足了我们的一部分需求.但是我们追求效率的步伐怎会止步于此?这一次我将会进一步介绍windows上提升效率的方法. 一:运行 打开 ...

  7. python 面向对象(进阶篇)

    上一篇<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个“函数”供使 ...

  8. 最快让你上手ReactiveCocoa之进阶篇

    前言 由于时间的问题,暂且只更新这么多了,后续还会持续更新本文<最快让你上手ReactiveCocoa之进阶篇>,目前只是简短的介绍了些RAC核心的一些方法,后续还需要加上MVVM+Rea ...

  9. SQL Server调优系列进阶篇(查询优化器的运行方式)

    前言 前面我们的几篇文章介绍了一系列关于运算符的基础介绍,以及各个运算符的优化方式和技巧.其中涵盖:查看执行计划的方式.几种数据集常用的连接方式.联合运算符方式.并行运算符等一系列的我们常见的运算符. ...

随机推荐

  1. 一小时搞懂Mysql锁机制

    内容概述: 我们知道,数据也是一种供许多用户共享访问的资源.如何保证数据并发访问的一致性.有效性,是所有数据库必须解决的一个问题,锁的冲突也是影响数据库并发访问性能的一个重要因素.从这一角度来说,锁对 ...

  2. MySQL中的字段拼接 concat() concat_ws() group_concat()函数

    1.concat()函数 2.concat_ws()函数 3.group_concat()函数 操作的table select * from test_concat order by id limit ...

  3. centos7 之 设置环境变量(转载)

    设置centos环境变量,可以用export命令,也可以通过修改文件形式实现,本文以lavavel需要设置环境变量为例,将 /root/.config/composer/vendor/bin 路径加到 ...

  4. 《TCP/IP详解 卷1:协议》第4章 ARP:地址解析协议

    4.1 引言 本章我们要讨论的问题是只对TCP/IP协议簇有意义的IP地址.数据链路如以太网或令牌环网都有自己的寻址机制(常常为48 bit地址),这是使用数据链路的任何网络层都必须遵从的.一个网络如 ...

  5. java封装继承以及多态(含代码)

    封装 该露的露,该藏的藏 我们常需设计要追求,"高内聚,低耦合".高内聚就是类的内部数据操作细节自己完成.不允许外部干涉:低耦合:仅暴漏少量的方法给外部使用. 封装(数据的隐藏) ...

  6. Leetcode No.66 Plus One(c++实现)

    1. 题目 1.1 英文题目 Given a non-empty array of decimal digits representing a non-negative integer, increm ...

  7. [网络流24题]最长k可重线段集[题解]

    最长 \(k\) 可重线段集 题目大意 给定平面 \(x-O-y\) 上 \(n\) 个开线段组成的集合 \(I\) ,和一个正整数 \(k\) .试设计一个算法,从开线段集合 \(I\) 中选取开线 ...

  8. 「HEOI2016/TJOI2016」排序

    「HEOI2016/TJOI2016」排序 题目大意 给定一个 \(1\) 到 \(n\) 的排列,每次可以对这个序列的一个区间进行升序/降序排序,求所有操作后第 \(q\) 个位置上的数字. 题解 ...

  9. python根据正则表达式生成指定规律的网址

    import os def file_name(file_dir): for root, dirs, files in os.walk(file_dir): print(root) #当前目录路径 p ...

  10. python根据窗口标题找句柄,将窗口前置活动

    import time, threading, copy import win32api, win32con import win32gui import win32gui def zhaojb(aa ...