假设有这样的状况,在并发请求中多个客户端都要对文档进行更新,这时最后一次更新就会覆盖掉前面的全部更新,这种状况看起来是没有问题的。web
可是若是咱们存放的是商品促销时商品库存信息,这是web1和web2访问到都是有100的库存量同时下单,就会出现后来者覆盖前面更新库存的状况,这是就会出现咱们没有足够的商品给客户。数据库
变动越频繁,读数据和更新数据的间隙越长,也就越可能丢失变动。在这种状况下数据库一般有两种解决办法。适用悲观锁或者乐观锁。并发
悲观锁:这种方法被关系型数据库普遍使用,它假定有变动冲突可能发生,所以阻塞访问资源以防止冲突。 一个典型的例子是读取一行数据以前先将其锁住,确保只有放置锁的线程可以对这行数据进行修改。异步
乐观锁:Elasticsearch 中使用的这种方法假定冲突是不可能发生的,而且不会阻塞正在尝试的操做。 然而,若是源数据在读写当中被修改,更新将会失败。应用程序接下来将决定该如何解决冲突。 例如,能够重试更新、使用新的数据、或者将相关状况报告给用户。分布式
Elasticsearch 是分布式的,当文档发生修改时,文档的修改也要复制到其余节点上面,同时Elasticsearch 也是异步和并发的,这意味着这些复制请求被并行发送,这些复制被发送到其余节点是顺序是乱的,则就须要一种方式去控制保证新的修改不会被就的修改所覆盖。ide
Elasticsearch 当文档被修改时版本号递增,Elasticsearch 使用这个 _version
号来确保变动以正确顺序获得执行。若是旧版本的文档在新版本以后到达,它能够被简单的忽略。ui
咱们能够利用 _version
号来确保 应用中相互冲突的变动不会致使数据丢失。咱们经过指定想要修改文档的 version
号来达到这个目的。 若是该版本不是当前版本号,咱们的请求将会失败。this
首先建立索引website:spa
PUT website
新增文档:线程
PUT website/blog/1/_create { "title": "My first blog entry", "text": "Just trying this out..." }
响应体告诉咱们,这个新建立的文档 _version
版本号是 1
{ "_index": "website", "_type": "blog", "_id": "1", "_version": 1, "result": "created", "_shards": { "total": 2, "successful": 2, "failed": 0 }, "_seq_no": 0, "_primary_term": 1 }
这时假设有两个用户将要对文档进行修改,两个用户分区都读取数据了版本号1
{ "_index": "website", "_type": "blog", "_id": "1", "_version": 1, "found": true, "_source": { "title": "My first blog entry", "text": "Just trying this out..." } }
第一个用更新文档
PUT /website/blog/1?version=1 { "title": "My first blog entry", "text": "Starting to get the hang of this..." }
更新成功,并返回版本号为2
{ "_index": "website", "_type": "blog", "_id": "1", "_version": 2, "result": "updated", "_shards": { "total": 2, "successful": 2, "failed": 0 }, "_seq_no": 1, "_primary_term": 1 }
这是第二个用户由于也已经读取了文档数据且不知道第一个用户已经对文档进行过修改,这时他再对该文档进行修改
PUT /website/blog/1?version=1 { "title": "The secend time update blog entry", "text": "Starting to get the hang of this..." }
返回状态409更新失败
{ "error": { "root_cause": [ { "type": "version_conflict_engine_exception", "reason": "[blog][1]: version conflict, current version [2] is different than the one provided [1]", "index_uuid": "XFDz_R-MTHqhQfu4_yXLLw", "shard": "3", "index": "website" } ], "type": "version_conflict_engine_exception", "reason": "[blog][1]: version conflict, current version [2] is different than the one provided [1]", "index_uuid": "XFDz_R-MTHqhQfu4_yXLLw", "shard": "3", "index": "website" }, "status": 409 }
这告诉咱们在 Elasticsearch 中这个文档的当前 _version
号是 2
,但咱们指定的更新版本号为 1
,更新不成功。全部文档的更新或删除 API,均可以接受 version
参数,这容许你在代码中使用乐观的并发控制,这是一种明智的作法。
一个常见的设置是使用其它数据库做为主要的数据存储,使用 Elasticsearch 作数据检索, 这意味着主数据库的全部更改发生时都须要被复制到 Elasticsearch ,若是多个进程负责这一数据同步,你可能遇到相似于以前描述的并发问题。
若是你的主数据库已经有了版本号 , 那么你就能够在 Elasticsearch 中经过增长 version_type=external
到查询字符串的方式重用这些相同的版本号, 版本号必须是大于零的整数, 且小于 9.2E+18
— 一个 Java 中 long
类型的正值。
外部版本号的处理方式和咱们以前讨论的内部版本号的处理方式有些不一样, Elasticsearch 不是检查当前_version
和请求中指定的版本号是否相同, 而是检查当前 _version
是否 小于 指定的版本号。若是当前版本小于指定版本则更新成功,外部的版本号做为文档的新 _version
进行存储。
外部版本号不只能够在更新删除时使用也能够在新建文档时使用:
PUT /website/blog/2?version=5&version_type=external { "title": "My first external blog entry", "text": "Starting to get the hang of this..." }
执行结果:新增文档版本号5
{ "_index": "website", "_type": "blog", "_id": "2", "_version": 5, "result": "created", "_shards": { "total": 2, "successful": 2, "failed": 0 }, "_seq_no": 0, "_primary_term": 1 }
如今咱们更新这个文档,指定一个新的 version
号是 10
PUT /website/blog/2?version=10&version_type=external { "title": "My first external blog entry", "text": "Starting to get the hang of this..." }
响应结果为更新了版本号为10
{ "_index": "website", "_type": "blog", "_id": "2", "_version": 10, "result": "updated", "_shards": { "total": 2, "successful": 2, "failed": 0 }, "_seq_no": 1, "_primary_term": 1 }
而后咱们查询该文档信息
GET website/blog/2
响应结果
{ "_index": "website", "_type": "blog", "_id": "2", "_version": 10, "found": true, "_source": { "title": "My first external blog entry", "text": "Starting to get the hang of this..." } }