基于_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. zabbix 自定义mysql监控

    一.配置zabbix-agent 编辑 /etc/zabbix/zabbix_agentd.conf文件  增加如下两个配置 1.vim /etc/zabbix/zabbix_agentd.conf ...

  2. Kubernetes Pod 调度约束

    Kubernetes Pod 调度约束 可以将pod调度到指定的节点Node内 默认:根据节点资源利用率等分配Node节点. nodeName用于将Pod调度到指定的Node名称上 nodeSelec ...

  3. Prometheus 监控K8S Node监控

    Prometheus 监控K8S Node监控 Prometheus社区提供的NodeExporter项目可以对主机的关键度量指标进行监控,通过Kubernetes的DeamonSet可以在各个主机节 ...

  4. Activex在没有电子秤api的情况下获取串口数据

    大二做B/S架构的项目使用了安衡电子秤CHS-D+R和一款扫码枪,两个设备的串口使用一样,这款电子秤是相当的坑,没有开发的api,无奈只能自己开发Activex了,在B/S架构中进行引用Activex ...

  5. 如何使用Charles让手机访问PC自定义域名?

    需求:移动端访问PC上的自定义域名,如在Nginx上配置的域名 ​ 如vv.zzcloud.com这个域名在pc上是通过host映射的方式访问,现在需要在手机上访问到这个域名. 工具:Charles代 ...

  6. .NET同一个页面父容器与子容器通信方案

    主界面: 关键主页面代码: <div id="EditDiv"> <iframe src="javascript:void(0)" id=&q ...

  7. tkinter为多个窗口设置相同的icon报错

    import threading import tkinter from PIL import Image, ImageTk def show_window(): window = tkinter.T ...

  8. Java11新特性解读

    在去年的9月26日,Oracle官方宣布Java11正式发布,这是Java大版本周期变化后的第一个长期支持版本,非常值得关注.Java9和Java10都在很短的时间内就过渡了,所以,Java11将是一 ...

  9. Google Analytics 学习笔记二 —— GA部署

    一.直接部署 直接复制GA跟踪代码 放到所有页面 跟踪代码放到 "head"前面 二.GTM部署方法一 三.GTM部署方法二 Tacking ID 四.测试.参数配置与调优

  10. Tomcat 配置介绍

    参数说明: maxThreads: 最大可以创建请求的线程数 minSpareThreads: 服务启动时创建的处理请求的进程数 Connector中的port: 创建服务器端的端口号,此端口监听用户 ...