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分布式锁
悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给 ...
随机推荐
- rpc和webservice的关系简述
RPC(Remote Procedure Call,远程过程调用)是一个很大的概念.它是一种通过网络从远程计算机程序上跨语言跨平台的请求服务.RPC能省略部分接口代码的开发,可以跨机器之间访问对象(J ...
- f(n-1) + f(n-2)的编译器处理
https://gcc.godbolt.org int addx(int a){ return a + 2; } int gooo(){ return addx(3) + addx(4) + ad ...
- java函数式编程的形式
java中没有真正的函数变量: 一.所有的函数(拉姆达)表达式,都被解释为functional interface @FunctionalInterface interface GreetingSer ...
- C#循环结构
一.背景: 因编程的基础差,因此最近开始巩固学习C#基础,后期把自己学习的东西,总结相应文章中,有不足处请大家多多指教. 二.简介 有的时候,可能需要多次执行同一块代码.一般情况下,语句是顺序执行的: ...
- golang中,slice的几个易混淆点
slice在golang中是最常用的类型,一般可以把它作为数组使用,但是比数组要高效呀.不过,我感觉这个东西用的不好坑太多了.还是需要了解下他底层的实现 slice的结构定义 type slice s ...
- Java问题记录——IllegalMonitorStateException
Java问题记录——IllegalMonitorStateException 摘要:本文主要分析了IllegalMonitorStateException的产生原因. 部分内容来自以下博客: http ...
- 2.原生js实现图片懒加载
网上查了很多图片懒加载的内容, 但基本上都是jQuery实现的, 没有说清楚其原理, 所以研究了一下 多的不说, 上代码, 看不明白的建议看下我的上一篇文章<1. 图解浏览器和用户设备的宽高等属 ...
- SQL行转列,列转行
SQL 行转列,列转行 行列转换在做报表分析时还是经常会遇到的,今天就说一下如何实现行列转换吧. 行列转换就是如下图所示两种展示形式的互相转换 行转列 假如我们有下表: SELECT * FROM s ...
- QQ和微信点击链接或扫描自动跳转外部浏览器
微信上进行的网页宣传.游戏传播.APP下载各类活动很多,但是各位朋友肯定经常会遇到一些特殊需求,网页需要在手机默认浏览器打开而不是微信内置浏览器.这个问题怎么解决呢? 另一种情况是你的网址被恶意举报被 ...
- flask上下文管理之threading.local
Flask之上下文管理 知识储备之问题情境: request中的参数: 单进程单线程 单进程多线程-->reqeust 会因为多个请求,数据发生错乱.--->可以基于threading.l ...