1. 详解并发冲突

在电商场景下,工作流程为:

  1. 读取商品信息,包括库存数量
  2. 用户下单购买
  3. 更新商品信息,将库存数减一

如果是多线程操作,就可能有多个线程并发的去执行上述的3步骤流程,假如此时有两个人都来读取商品数据,两个线程并发的服务于两个人,同时在进行商品库存数据的修改。正确的情况:线程A将库存-1,设置为99件,线程B接着读取99件,再-1,变为98件。如果A,B线程都读取的为100件,A处理完之后修改为99件,B处理完之后再次修改为99件,此时结果就出错了。

2. 解决方案

2.1 悲观锁

在读取商品数据时,同时对这一行数据加锁,当此线程处理完数据之后,再解锁,另一个线程开始处理。

悲观锁并发控制方案,就是在各种情况下,都上锁。上锁之后,就只有一个线程可以操作这一条数据,不同的场景之下,上的锁不同,行级锁,表级锁,读锁,写锁。

2.2 乐观锁

乐观锁不加锁,每个线程都可以任意操作。es的每条文档中有一个version字段,新建文档后为1,修改一次累加,线程A,B同时读取到数据,version=1,A处理完之后库存为99,在写入es的时候会跟es中的版本号比较,都是1,则写入成功,version=2,B处理完之后也为99,存入es时与es中的数据的版本号version=2相比,明显不同,此时不会用99去更新,而是重新读取最新的数据,再减一,变为98,执行上述操作,写入。

2.3 Elasticsearch的乐观锁

Elasticsearch的后台都是多线程异步的,多个请求之间是乱序的,可能后修改的先到,先修改的后到。

Elasticsearch的多线程异步并发修改是基于自己的_version版本号进行乐观锁并发控制的。

在后修改的先到时,修改完毕后,当先修改的后到时,会比较一下_version版本号,如果不相等就直接扔掉,不需要了。这样结果会就会保存为一个正确状态。

代码示例:

PUT /test_index/test_type/3
{"test_field": "test test"
}
结果:
{"_index": "test_index","_type": "test_type","_id": "3","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"created": true
}
修改
PUT /test_index/test_type/3
{"test_field": "test1 test1"
}
结果
{"_index": "test_index","_type": "test_type","_id": "3","_version": 2,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"created": true
}
删除
DELETE /test_index/test_type/3
结果:
{"_index": "test_index","_type": "test_type","_id": "3","_version": 3,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"created": true
}
重新创建
PUT /test_index/test_type/3
{"test_field": "test1 test1"
}
结果
{"_index": "test_index","_type": "test_type","_id": "4","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"created": true
}
复制代码

删除操作也会对这条数据的版本号加1

在删除一个document之后,可以从一个侧面证明,它不是立即物理删除掉的,因为它的一些版本号等信息还是保留着的。先删除一条document,再重新创建这条document,其实会在delete version基础之上,再把version号加1

2.4 es的乐观锁并发控制示例

  • 先新建一条数据

    PUT /test_index/test_type/4
    {"test_field": "test"
    }
    复制代码
  • 模拟两个客户端,都获取到了同一条数据

    GET /test_index/test_type/4
    返回
    {"_index": "test_index","_type": "test_type","_id": "4","_version": 1,"found": true,"_source": {"test_field": "test"}}
    复制代码
  • 其中一个客户端,先更新了一下这个数据, 同时带上数据的版本号,确保说,es中的数据的版本号,跟客户端中的数据的版本号是相同的,才能修改

    PUT test_index/test_type/4?version=1
    {"test_field": "client1 changed"
    }
    返回结果
    {"_index": "test_index","_type": "test_type","_id": "4","_version": 2,"result": "updated","_shards": {"total": 2,"successful": 1,"failed": 0},"created": false
    }
    复制代码
  • 另外一个客户端,尝试基于version=1的数据去进行修改,同样带上version版本号,进行乐观锁的并发控制

    PUT test_index/test_type/4?version=1
    {"test_field": "client2 changed"
    }
    会出错,返回
    {"error": {"root_cause": [{"type": "version_conflict_engine_exception","reason": "[test_type][4]: version conflict, current version [2] is different than the one provided [1]","index_uuid": "rsiZYqiwSCC2XdR8N2bJow","shard": "2","index": "test_index"}],"type": "version_conflict_engine_exception","reason": "[test_type][4]: version conflict, current version [2] is different than the one provided [1]","index_uuid": "rsiZYqiwSCC2XdR8N2bJow","shard": "2","index": "test_index"},"status": 409
    }
    复制代码

    乐观锁就成功阻止并发问题

  • 在乐观锁成功阻止并发问题之后,尝试正确的完成更新

    重新进行GET请求,得到 version

    GET /test_index/test_type/4
    {"_index": "test_index","_type": "test_type","_id": "4","_version": 2,"found": true,"_source": {"test_field": "client1 changed"}
    }
    复制代码

    基于最新的数据和版本号,去进行修改,修改后,带上最新的版本号,可能这个步骤会需要反复执行好几次,才能成功,特别是在多线程并发更新同一条数据很频繁的情况下

    PUT /test_index/test_type/4?version=2
    {"test_field": "client2 changed"
    }
    返回
    {"_index": "test_index","_type": "test_type","_id": "4","_version": 3,"result": "updated","_shards": {"total": 2,"successful": 1,"failed": 0},"created": false
    }
    复制代码

2.5 基于external version进行乐观锁并发控制

es提供了一个feature,就是说,你可以不用它提供的内部_version版本号来进行并发控制,可以基于你自己维护的一个版本号来进行并发控制。

?version=1&version_type=external
复制代码

version_type=external,唯一的区别在于,_version,只有当你提供的version与es中的_version一模一样的时候,才可以进行修改,只要不一样,就报错;当version_type=external的时候,只有当你提供的version比es中的_version大的时候,才能完成修改

es,_version=1,?version=1,才能更新成功
es,_version=1,?version>1&version_type=external,才能成功,比如说?version=2&version_type=external

代码示例:

  • 先创建一条数据

    PUT test_index/test_type/5
    {"test_field": "external test"
    }
    返回
    {"_index": "test_index","_type": "test_type","_id": "5","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"created": true
    }
    复制代码
  • 模拟两个客户端同时查询到这条数据

    GET /test_index/test_type/5
    返回
    {"_index": "test_index","_type": "test_type","_id": "5","_version": 1,"found": true,"_source": {"test_field": "external test"}
    }
    复制代码
  • 第一个客户端先进行修改,此时客户端程序是在自己的数据库中获取到了这条数据的最新版本号,比如说是2

    PUT /test_index/test_type/5?version=2&version_type=external
    {"test_field": "external client1 changed"
    }
    返回
    {"_index": "test_index","_type": "test_type","_id": "5","_version": 2,"result": "updated","_shards": {"total": 2,"successful": 1,"failed": 0},"created": false
    }
    复制代码
  • 模拟第二个客户端,同时拿到了自己数据库中维护的那个版本号,也是2,同时基于version=2发起了修改

    PUT /test_index/test_type/5?version=2&version_type=external
    {"test_field": "external client2 changed"
    }
    会出错,返回
    {"error": {"root_cause": [{"type": "version_conflict_engine_exception","reason": "[test_type][5]: version conflict, current version [2] is higher or equal to the one provided [2]","index_uuid": "rsiZYqiwSCC2XdR8N2bJow","shard": "1","index": "test_index"}],"type": "version_conflict_engine_exception","reason": "[test_type][5]: version conflict, current version [2] is higher or equal to the one provided [2]","index_uuid": "rsiZYqiwSCC2XdR8N2bJow","shard": "1","index": "test_index"},"status": 409
    }
    复制代码
  • 在并发控制成功后,重新基于最新的版本号发起更新

    GET /test_index/test_type/5
    返回
    {"_index": "test_index","_type": "test_type","_id": "5","_version": 2,"found": true,"_source": {"test_field": "external client1 changed"}
    }
    PUT /test_index/test_type/5?version=3&version_type=external
    {"test_field": "external client2 changed"
    }
    返回
    {"_index": "test_index","_type": "test_type","_id": "5","_version": 3,"result": "updated","_shards": {"total": 2,"successful": 1,"failed": 0},"created": false
    }复制代码

转载于:https://juejin.im/post/5c66afb0e51d452a1c6159cc

Elasticsearch——并发冲突以及解决方案相关推荐

  1. 【Elasticsearch】 Elasticsearch并发冲突问题

    1.概述 作者:缓慢移动的蜗牛 链接:https://www.jianshu.com/p/7a3652bae8a4 来源:简书 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 转 ...

  2. “并发冲突” 分析与解决

    并发冲突分析与解决      我们都知道,我们计算机中的运算都是交给CPU处理的,如果对于一个单核CPU来说,在绝对的某个时刻内,CPU只能同时完成一个任务,不可能同时的处理多个任务.但是我们都知道, ...

  3. CC00045.elasticsearch——|HadoopElasticSearch.V45|——|ELK.v45|原理剖析|并发冲突处理机制剖析|

    一.并发冲突处理机制剖析 ### --- 详解并发冲突~~~ # 在电商场景下,工作流程为: ~~~ 读取商品信息,包括库存数量 ~~~ 用户下单购买 ~~~ 更新商品信息,将库存数减一 ~~~ 如果 ...

  4. php中并发读写文件冲突的解决方案(文件锁应用示例)

    php中并发读写文件冲突的解决方案(文件锁应用示例) 参考文章: (1)php中并发读写文件冲突的解决方案(文件锁应用示例) (2)https://www.cnblogs.com/wellsoho/p ...

  5. [架构]--高并发问题及解决方案

    2019独角兽企业重金招聘Python工程师标准>>> 本文一共分析了三个案例,分别介绍并发系统中的共享资源并发访问.计算型密集型任务缓存访问 .单一热点资源峰值流量问题和解决方案. ...

  6. scanf_s 发送访问冲突_程序员如何解决并发冲突的难题?

    作者 | 羽生结弦 责编 | 胡雪蕊 出品 | CSDN(ID: CSDNnews) 在大多数的应用中都会出现客户端同时发送多个请求对同一条数据就行修改,这个时候就会出现并发冲突.我们一般的做法会有如 ...

  7. Elasticsearch技术解析与实战(六)Elasticsearch并发

    乐观锁与悲观锁 图示的冲突过程,其实就是es的并发冲突问题,会导致数据不准确 当并发操作es的线程越多,或者读取一份数据,供用户查询和操作的时间越长,在这段时间里,如果数据被其他用户修改,那么我们拿到 ...

  8. 第二十节: 深入理解并发机制以及解决方案(锁机制、EF自有机制、队列模式等)

    一. 理解并发机制 1. 什么是并发,并发与多线程有什么关系? ①. 先从广义上来说,或者从实际场景上来说. 高并发通常是海量用户同时访问(比如:12306买票.淘宝的双十一抢购),如果把一个用户看做 ...

  9. 程序员如何解决并发冲突的难题?

    作者 | 羽生结弦 责编 | 胡雪蕊 出品 | CSDN(ID: CSDNnews) 在大多数的应用中都会出现客户端同时发送多个请求对同一条数据就行修改,这个时候就会出现并发冲突.我们一般的做法会有如 ...

最新文章

  1. ssh: connect to host github.com port 22: Connection timed out fatal: Could not read from remote...
  2. Java继承的概念与实现
  3. 深入浅出学习Struts框架(八):分析Struts框架实例3
  4. dos通过for命令截取字符串
  5. java _web之Servlet简单应用
  6. Netty工作笔记0036---单Reactor单线程模式
  7. chrome插件系列一:Secure Shell(替代ssh客户端)
  8. Sql server 2008 R2 导出/导入数据报错之无法打开全局共享内存以与性能 DLL 通信
  9. html和css设计网页实例,经典网页设计:30个创意的 CSS 应用案例
  10. UA OPTI512R 傅立叶光学导论14 卷积定理
  11. flash html 通信,Javascript与flash交互通信基础教程
  12. 【Linux】进程通信、同步、IO复用代码
  13. JS判断当前手机类型
  14. linux c蜂鸣器驱动程序,嵌入式Linux设备驱动程序设计——蜂鸣器驱动程序
  15. localStorage的黑科技-js和css缓存机制
  16. 莫比乌斯反演小结 + 黑暗爆炸 2301
  17. R代码学习(1)——算术运算、关系运算、逻辑运算
  18. SQL server 变量、运算符
  19. Nelder Mead算法推荐阅读博文
  20. oracle06004,Oracle不完全恢复-主动恢复和incarnation/RMAN-20208/RMAN-06004

热门文章

  1. Android使用磁盘缓存DiskLruCache
  2. 判断接收的数据中是否有中文
  3. Spring 命名空间
  4. Spark入门实战系列--6.SparkSQL(中)--深入了解SparkSQL运行计划及调优
  5. MPEG简介 + 如何计算CBR 和VBR的MP3的播放时间
  6. 好系统U盘启动来说一说win10系统有必要更新到最新版本吗?
  7. PHP过滤器 filter_has_var() 函数
  8. 【278】◀▶ Python 数学函数说明
  9. 洛谷P2327 [SCOI2005] 扫雷
  10. Android View体系(六)从源码解析Activity的构成