Elasticsearch由浅入深(五)_version乐观锁、external version乐观锁、partial update、groovy脚本实现partial update
基于_version进行乐观锁并发控制
- 先构造一条数据出来
PUT /test_index/test_type/
{
"test_field": "test test"
} - 模拟两个客户端,都获取到了同一条数据
GET test_index/test_type/ {
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"found": true,
"_source": {
"test_field": "test test"
}
} 其中一个客户端,先更新了一下这个数据
同时带上数据的版本号,确保说,es中的数据的版本号,跟客户端中的数据的版本号是相同的,才能修改
PUT /test_index/test_type/?version=
{
"test_field": "test client 1"
}{
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"result": "updated",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"created": false
}- 另外一个客户端,尝试基于version=1的数据去进行修改,同样带上version版本号,进行乐观锁的并发控制
PUT /test_index/test_type/?version=
{
"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": "6m0G7yx7R1KECWWGnfH1sw",
"shard": "",
"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": "6m0G7yx7R1KECWWGnfH1sw",
"shard": "",
"index": "test_index"
},
"status":
} - 在乐观锁成功阻止并发问题之后,尝试正确的完成更新
GET /test_index/test_type/ {
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"found": true,
"_source": {
"test_field": "test client 1"
}
} 基于最新的数据和版本号,去进行修改,修改后,带上最新的版本号,可能这个步骤会需要反复执行好几次,才能成功,特别是在多线程并发更新同一条数据很频繁的情况下 PUT /test_index/test_type/?version=
{
"test_field": "test client 2"
} {
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"result": "updated",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"created": false
}
基于external version进行乐观锁并发控制
什么是external version
es提供了一个feature,就是说,你可以不用它提供的内部_version版本号来进行并发控制,可以基于你自己维护的一个版本号来进行并发控制。举个列子,加入你的数据在mysql里也有一份,然后你的应用系统本身就维护了一个版本号,无论是什么自己生成的,程序控制的。这个时候,你进行乐观锁并发控制的时候,可能并不是想要用es内部的_version来进行控制,而是用你自己维护的那个version来进行控制。
?version=
?version=&version_type=external
version_type=external,唯一的区别在于,_version,只有当你提供的version与es中的_version一模一样的时候,才可以进行修改,只要不一样,就报错;当version_type=external的时候,只有当你提供的version比es中的_version大的时候,才能完成修改
es,_version=,?version=,才能更新成功
es,_version=,?version>&version_type=external,才能成功,比如说?version=&version_type=external
- 先构造一条数据
PUT /test_index/test_type/
{
"test_field": "test"
}{
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"result": "created",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"created": true
} - 模拟两个客户端同时查询到这条数据
GET /test_index/test_type/ {
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"found": true,
"_source": {
"test_field": "test"
}
} - 第一个客户端先进行修改,此时客户端程序是在自己的数据库中获取到了这条数据的最新版本号,比如说是2
PUT /test_index/test_type/?version=&version_type=external
{
"test_field": "test client 1"
} {
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"result": "updated",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"created": false
} - 模拟第二个客户端,同时拿到了自己数据库中维护的那个版本号,也是2,同时基于version=2发起了修改
PUT /test_index/test_type/?version=&version_type=external
{
"test_field": "test client 2"
} {
"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": "6m0G7yx7R1KECWWGnfH1sw",
"shard": "",
"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": "6m0G7yx7R1KECWWGnfH1sw",
"shard": "",
"index": "test_index"
},
"status":
} - 在并发控制成功后,重新基于最新的版本号发起更新
GET /test_index/test_type/ {
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"found": true,
"_source": {
"test_field": "test client 1"
}
} PUT /test_index/test_type/?version=&version_type=external
{
"test_field": "test client 2"
} {
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"result": "updated",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"created": false
}
partial update
简介
一般对应到应用程序中,每次的执行流程基本是这样的:
- 应用程序先发起一个get请求,获取到document,展示到前台界面,供用户查看和修改
- 用户在前台界面修改数据,发送到后台
- 后台代码,会将用户修改的数据在内存中进行执行,然后封装好修改后的全量数据
- 然后发送PUT请求,到es中,进行全量替换
- es将老的document标记为deleted,然后重新创建一个新的document
partial update是仅仅进行部分更新,无需全量替换
语法:
post /index/type/id/_update
{
"doc": {
"要修改的少数几个field即可,不需要全量的数据"
}
}
看起来,好像就比较方便了,每次就传递少数几个发生修改的field即可,不需要将全量的document数据发送过去
partial update原理及优点

全量更新的原理

其实es内部对partial update的实际执行,跟传统的全虽替换方式,是几乎一样的
- 内部先获取document
- 将传过来的field更新到document的json中
- 将老的document标记为deleted
- 将修改后的新的document创建出来
partial update相较于全星替换的优点:
- 所有的查询、修改和写国操作,都发生在es中的一个shard内部,避免了所有的网络数据传输的开销(减少2次网络请求) ,大大提升了性能
- 减少了查询和修改中的时间间隔,可以有效减少并发冲突的情况
示例
PUT /test_index/test_type/
{
"test_field1": "test1",
"test_field2": "test2"
}
partial update
POST /test_index/test_type//_update
{
"doc": {
"test_field2": "updated test2"
}
}
基于groovy脚本实现partial update
ES其实是有个内置的脚本支持的,可以基于groovy脚本实现各种各样的复杂操作,基于groovy脚本,如何执行partial update
初始化脚本:
PUT /test_index/test_type/
{
"num": ,
"tags": []
}
- 内置脚本
POST /test_index/test_type//_update
{
"script": "ctx._source.num+=1"
} - 外部脚本
假设我们想在tags中新增内容
我们可以写成脚本,在 \config\scripts 目录下新建 test-add-yags.groovy 脚本:ctx._source.tags+=new_tag
POST /test_index/test_type//_update
{
"script": {
"lang": "groovy",
"file": "test-add-yags",
"params": {
"new_tag":"tag1"
}
}
}GET /test_index/test_type/
{
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"found": true,
"_source": {
"num": ,
"tags": [
"tag1"
]
}
} - 用脚本删除文档
如果num等于count则删除
我们可以写成脚本,在 \config\scripts 目录下新建 test-delete-document.groovy 脚本:ctx.op = ctx._source.num == count ? 'delete' : 'none'
POST /test_index/test_type//_update
{
"script": {
"lang": "groovy",
"file": "test-delete-document",
"params": {
"count":
}
}
}{
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"result": "deleted",
"_shards": {
"total": ,
"successful": ,
"failed":
}
} - upsert操作
顾名思义:upsert=update+insert
不存在document进行更新POST /test_index/test_type//_update
{
"doc": {
"num":
}
} {
"error": {
"root_cause": [
{
"type": "document_missing_exception",
"reason": "[test_type][11]: document missing",
"index_uuid": "d7GOSxVnTNKYuI8x7cZfkA",
"shard": "",
"index": "test_index"
}
],
"type": "document_missing_exception",
"reason": "[test_type][11]: document missing",
"index_uuid": "d7GOSxVnTNKYuI8x7cZfkA",
"shard": "",
"index": "test_index"
},
"status":
}upsert操作
POST /test_index/test_type//_update
{
"script": "ctx._source.num+=1",
"upsert": {
"num": ,
"tags": []
}
}
图解partial update乐观锁并发控制原理

partial update内部会自动执行我们之前所说的乐观锁的并发控制策
避免自动partial update fail掉的解决方案,使用 retry_on_conflict
retry策略
- 再次获取document数据和最新版本号
- 基于最新版本号再次去更新,如果成功那么就ok了、如果失败,重复1和2两个步骤,最多重复几次呢?可以通过retry那个参数的值指定,比如5次
示例:
post /index/type/id/_update?retry_on_conflict=&version=
Elasticsearch由浅入深(五)_version乐观锁、external version乐观锁、partial update、groovy脚本实现partial update的更多相关文章
- Elasticsearch由浅入深(四)ES并发冲突、悲观锁与乐观锁、_version乐观锁并发
ES并发冲突 举个例子,比如是电商场景下,假设说,我们有个程序,工作的流程是这样子的: 读取商品信息(包含了商品库存) 用户下单购买 更新商品信息(主要是将库存减1) 我们比如咱们的程序就是多线程的, ...
- ElasticSearch内部基于_version乐观锁控制机制
1.悲观锁与乐观锁机制 为控制并发问题,我们通常采用锁机制.分为悲观锁和乐观锁两种机制. 悲观锁:很悲观,所有情况都上锁.此时只有一个线程可以操作数据.具体例子为数据库中的行级锁.表级锁.读锁.写锁等 ...
- 22.external version
主要知识点 基于external version进行乐观锁并发控制 es提供了一个feature,就是说,你可以不用它提供的内部_version版本号来进行并发控制,可以基于你自己维护的一个版本号来进 ...
- [数据库锁机制] 深入理解乐观锁、悲观锁以及CAS乐观锁的实现机制原理分析
前言: 在并发访问情况下,可能会出现脏读.不可重复读和幻读等读现象,为了应对这些问题,主流数据库都提供了锁机制,并引入了事务隔离级别的概念.数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务 ...
- [Todo] 乐观悲观锁,自旋互斥锁等等
乐观锁.悲观锁.要实践 http://chenzhou123520.iteye.com/blog/1860954 <mysql悲观锁总结和实践> http://chenzhou123520 ...
- 【MySQL锁】MySQL悲观锁和乐观锁概念
悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常基础的概念.本文将对这两种常见的锁机制在数据库数据上的实现进行比较系统的介绍. 悲观锁(Pessimistic Lock) 悲观锁的 ...
- mysql中的锁机制之悲观锁和乐观锁
1.悲观锁? 悲观锁顾名思义就是很悲观,悲观锁认为数据随时就有可能会被外界进行修改,所以悲观锁一上来就会把数据给加上锁.悲观锁一般都是依靠关系型数据库提供的锁机制,然而事实上关系型数据库中的行锁,表锁 ...
- Elasticsearch由浅入深(六)批量操作:mget批量查询、bulk批量增删改、路由原理、增删改内部原理、document查询内部原理、bulk api的奇特json格式
mget批量查询 批量查询的好处就是一条一条的查询,比如说要查询100条数据,那么就要发送100次网络请求,这个开销还是很大的如果进行批量查询的话,查询100条数据,就只要发送1次网络请求,网络请求的 ...
- 乐观、悲观锁、redis分布式锁
悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给 ...
随机推荐
- Mysql 二进制日志备份还原
Mysql 二进制日志备份还原 一.开启二进制日志 1.进入配置文件[mysqld]下添加配置 方案一 vim /etc/my.cnf log-bin = /usr/local/mysql/logs/ ...
- 【08】Nginx:安全优化 / 信息隐藏 / 请求限制 / 白名单
写在前面的话 nginx 中主要的内容在前面的章节其实已经差不多了,接下都是一些小功能的实现以及关于 nginx 的优化问题.我们一起来探讨以下,如何把我们的 nginx 打造成为企业级应用. 安全优 ...
- dedecms用runphp功能,写for循环,@me输出不出来
今天在{dede:field name='typeid' runphp='yes'}中写for循环,出现@me输出不了内容,把for循环删掉之后,就可以输出.死了几十万脑细胞,没有解决,后来把循环 f ...
- Log4基本配置
前言:作为一个程序员你要学会调试,对于一种调试都无法找到问题所在的情况,你要学会看日志,要学会看日志你的学会怎么样去写入日志,接下来教你配置C#Log4 第一步,你的在配置文件中配置好对应的参数 &l ...
- 初识Android App自动化测试框架--Unittest
1.为什么需要使用框架实现自动化测试 作为测试工程师,可能在代码能力上相比开发工程师要弱一点,所以我们在写脚本的时候就会相对容易的碰到更多的问题,如果有一个成熟的框架供给我们使用的话,可以帮助我们避免 ...
- vue学习指南:第九篇(详细) - Vue的 Slot-插槽
Slot v-slot 插槽元素 浏览器在解析时候首先把它当作标签来解析,只有遇到不认识的就不管了,直接跳过,当你发现是组件,在以组件形式解析. 使用插槽的好处? 比如一个网站 分布顶部都是一样的, ...
- vue学习指南:第六篇(详细) - Vue的组件 component
1. 什么是组件?有两种解释 1. 第一种解释: 什么是组件? 1. 组件是 vue 中的一个可复用的实例,所以new Vue() 是vue中最大的那个组件(根组件),有名字,使用的时候以单标签或双标 ...
- 相同域名下的cookie污染
问题描述 本地用同一个tomcat调试两个相同框架的不同项目,在同一个浏览器界面里切换时,A项目的登录会把B项目的登录给踢掉,翻反过来亦如此.通过查看浏览器cookie,发现两个项目的cookie完全 ...
- 如何在一个ubuntu系统上搭建SVN版本控制工具
有话说,由于公司项目部署需要,将Windows工程迁移到Linux,通过调查确定使用Ubuntu的Linux操作系统.那么如何快速搭建和Windows一样快捷方便的开发环境就很重要了.本文讲述如何在一 ...
- 多线程学习笔记(一) InvokeRequired 和 delegate
入门示例: 假如有一个label,我们希望像走马灯一样,从1显示到100 private void button1_Click(object sender, EventArgs e) { ; i &l ...