基于_version进行乐观锁并发控制

  1. 先构造一条数据出来

    PUT /test_index/test_type/
    {
    "test_field": "test test"
    }
  2. 模拟两个客户端,都获取到了同一条数据
    GET test_index/test_type/
    
    {
    "_index": "test_index",
    "_type": "test_type",
    "_id": "",
    "_version": ,
    "found": true,
    "_source": {
    "test_field": "test test"
    }
    }
  3. 其中一个客户端,先更新了一下这个数据

    同时带上数据的版本号,确保说,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
    }
  4. 另外一个客户端,尝试基于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":
    }
  5. 在乐观锁成功阻止并发问题之后,尝试正确的完成更新
    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
  1. 先构造一条数据

    PUT /test_index/test_type/
    {
    "test_field": "test"
    }
    {
    "_index": "test_index",
    "_type": "test_type",
    "_id": "",
    "_version": ,
    "result": "created",
    "_shards": {
    "total": ,
    "successful": ,
    "failed":
    },
    "created": true
    }
  2. 模拟两个客户端同时查询到这条数据
    GET /test_index/test_type/
    
    {
    "_index": "test_index",
    "_type": "test_type",
    "_id": "",
    "_version": ,
    "found": true,
    "_source": {
    "test_field": "test"
    }
    }
  3. 第一个客户端先进行修改,此时客户端程序是在自己的数据库中获取到了这条数据的最新版本号,比如说是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
    }
  4. 模拟第二个客户端,同时拿到了自己数据库中维护的那个版本号,也是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":
    }
  5. 在并发控制成功后,重新基于最新的版本号发起更新
    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

简介

一般对应到应用程序中,每次的执行流程基本是这样的:

  1. 应用程序先发起一个get请求,获取到document,展示到前台界面,供用户查看和修改
  2. 用户在前台界面修改数据,发送到后台
  3. 后台代码,会将用户修改的数据在内存中进行执行,然后封装好修改后的全量数据
  4. 然后发送PUT请求,到es中,进行全量替换
  5. es将老的document标记为deleted,然后重新创建一个新的document

partial update是仅仅进行部分更新,无需全量替换

语法:

post /index/type/id/_update
{
"doc": {
"要修改的少数几个field即可,不需要全量的数据"
}
}

看起来,好像就比较方便了,每次就传递少数几个发生修改的field即可,不需要将全量的document数据发送过去

partial update原理及优点

全量更新的原理

其实es内部对partial update的实际执行,跟传统的全虽替换方式,是几乎一样的

  1. 内部先获取document
  2. 将传过来的field更新到document的json中
  3. 将老的document标记为deleted
  4. 将修改后的新的document创建出来

partial update相较于全星替换的优点:

  1. 所有的查询、修改和写国操作,都发生在es中的一个shard内部,避免了所有的网络数据传输的开销(减少2次网络请求) ,大大提升了性能
  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策略

  1. 再次获取document数据和最新版本号
  2. 基于最新版本号再次去更新,如果成功那么就ok了、如果失败,重复1和2两个步骤,最多重复几次呢?可以通过retry那个参数的值指定,比如5次

示例:

post /index/type/id/_update?retry_on_conflict=&version=

Elasticsearch由浅入深(五)_version乐观锁、external version乐观锁、partial update、groovy脚本实现partial update的更多相关文章

  1. Elasticsearch由浅入深(四)ES并发冲突、悲观锁与乐观锁、_version乐观锁并发

    ES并发冲突 举个例子,比如是电商场景下,假设说,我们有个程序,工作的流程是这样子的: 读取商品信息(包含了商品库存) 用户下单购买 更新商品信息(主要是将库存减1) 我们比如咱们的程序就是多线程的, ...

  2. ElasticSearch内部基于_version乐观锁控制机制

    1.悲观锁与乐观锁机制 为控制并发问题,我们通常采用锁机制.分为悲观锁和乐观锁两种机制. 悲观锁:很悲观,所有情况都上锁.此时只有一个线程可以操作数据.具体例子为数据库中的行级锁.表级锁.读锁.写锁等 ...

  3. 22.external version

    主要知识点 基于external version进行乐观锁并发控制 es提供了一个feature,就是说,你可以不用它提供的内部_version版本号来进行并发控制,可以基于你自己维护的一个版本号来进 ...

  4. [数据库锁机制] 深入理解乐观锁、悲观锁以及CAS乐观锁的实现机制原理分析

    前言: 在并发访问情况下,可能会出现脏读.不可重复读和幻读等读现象,为了应对这些问题,主流数据库都提供了锁机制,并引入了事务隔离级别的概念.数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务 ...

  5. [Todo] 乐观悲观锁,自旋互斥锁等等

    乐观锁.悲观锁.要实践 http://chenzhou123520.iteye.com/blog/1860954 <mysql悲观锁总结和实践> http://chenzhou123520 ...

  6. 【MySQL锁】MySQL悲观锁和乐观锁概念

    悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常基础的概念.本文将对这两种常见的锁机制在数据库数据上的实现进行比较系统的介绍. 悲观锁(Pessimistic Lock) 悲观锁的 ...

  7. mysql中的锁机制之悲观锁和乐观锁

    1.悲观锁? 悲观锁顾名思义就是很悲观,悲观锁认为数据随时就有可能会被外界进行修改,所以悲观锁一上来就会把数据给加上锁.悲观锁一般都是依靠关系型数据库提供的锁机制,然而事实上关系型数据库中的行锁,表锁 ...

  8. Elasticsearch由浅入深(六)批量操作:mget批量查询、bulk批量增删改、路由原理、增删改内部原理、document查询内部原理、bulk api的奇特json格式

    mget批量查询 批量查询的好处就是一条一条的查询,比如说要查询100条数据,那么就要发送100次网络请求,这个开销还是很大的如果进行批量查询的话,查询100条数据,就只要发送1次网络请求,网络请求的 ...

  9. 乐观、悲观锁、redis分布式锁

    悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给 ...

随机推荐

  1. Visual Studio 2019 (VS2019)正式版安装 VisualSVN Server 插件

    VS2019 正式版最近刚刚推出来,目前 Ankhsvn 还不支持,它最高只支持 VS2017,全网搜索了一下,也没有找到.在 Stackoverflow 上看了一下,找到这篇问答: 自己按照这种方法 ...

  2. 使用JWT作为Spring Security OAuth2的token存储

    序 Spring Security OAuth2的demo在前几篇文章中已经讲过了,在那些模式中使用的都是RemoteTokenService调用授权服务器来校验token,返回校验通过的用户信息供上 ...

  3. Flask restful源码分析

    Flask restful的代码量不大,功能比较简单 参见 http://note.youdao.com/noteshare?id=4ef343068763a56a10a2ada59a019484

  4. 【MySQL】多表查询&事务&权限管理

    多表查询: 查询语法: select 列名列表 from 表名列表 where.... 例子: 创建部门表 CREATE TABLE dept( id INT PRIMARY KEY AUTO_INC ...

  5. Java8新特性——Optional类的使用(有效的避免空指针异常)

    OPtional类的使用 概述 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因.以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guav ...

  6. ubuntu 换源 aliyun

    sudo sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list sudo sed -i 's/securit ...

  7. c# 移除类中所有事件的绑定

    单例中为防止多处注册事件引起异步触发时发生报错,网上找了一圈没找到想要的方法. [异常类型]:ArgumentException[异常信息]:该委托必须有一个目标(且仅有一个目标). 结合网上资料整合 ...

  8. 海关单一窗口程序出现网络/MQ问题后自动修复处理

    单一窗口切换了2年多了,由于RabbitMQ或者网络的不稳定,或者升级或者网络调整,等等诸多问题导致了海关单一窗口程序会不定期的出现文件及回执自动处理的作业停止的问题. 最近终于想明白了,把日志监控起 ...

  9. 面试官:来谈谈限流-RateLimiter源码分析

    RateLimiter有两个实现类:SmoothBursty和SmoothWarmingUp,其都是令牌桶算法的变种实现,区别在于SmoothBursty加令牌的速度是恒定的,而SmoothWarmi ...

  10. C#中将long浮点数格式化为{H:min:s.ms}格式的字符串的方法

    场景 表示时间的数据格式为浮点数,如下: 需要将其格式化为{H:min:s.ms}格式的字符串,效果如下: 注: 博客主页:https://blog.csdn.net/badao_liumang_qi ...