es里的文档是不可改变的,我们不能改变它们。



如果你想更新一个已经存在的文档,我们重新索引或者替换它,可以使用同样的API。



 



PUT /website/blog/123



{



"title": "My first blog entry",



"text":  "I am starting to get the hang of this...",



"date":  "2014/01/02"



}



 



响应如下:可以看到已经修改了_version字段。



{



"_index" :   "website",



"_type" :    "blog",



"_id" :      "123",



"_version" : 2,



"created":   false



}



 



created标识为false,表明已经有这么一个index,type,ID的组合存在过。



 



es已经标记了老的文档为删除,并且增加了一个全新的文档。



老版本的文档不会立刻消失,尽管你已经不能再访问它。



es会后台删除标记为删除的文档。



 



后面将讨论更新API,可以部分更新文档。看起来像是更新部分文档,实际上仍然是执行了一样的操作。



1 从老的文档中检索JSON



2 改变它。



3 删除老文档



4 索引新的文档。



 



也就是说依靠老的文档来获得新的文档,然后删除老文档,索引新文档。



唯一的不同在于:更新API在一次请求内完成。而不需要用户get一次加上index一次。




 



 



 



 



 



 



 



 



 



 




我们怎么能够保证,当索引一个文档,我们创建了一个全新的新文档而不是覆盖一个已经存在的文档。



 



记得组合(_index,_type,_id)唯一标示了一个文档,最容易的方式来检验我们的文档是新的,



就是让es自己增加一个id.使用post.



 



POST /website/blog/



{ ... }



 



尽管如此,如果我们已经有了一个_id想使用,我们需要告诉es:它应该接受我们的请求当这个    ID没有被使用时,



有两种方法,自己视方便而定。



1)



PUT /website/blog/123?op_type=create



{ ... }



2)



PUT /website/blog/123/_create



{ ... }



 



如果请求成功的创建了新的文档,es将会返回之前一样的信息,http返回码为201 Created.



 



另外一方面,如果一个文档已经用了这个ID,es返回409码表明冲突。



可能的错误信息如下:



{



"error" : "DocumentAlreadyExistsException[[website][4] [blog][123]:



document already exists ]",



"status" : 409



}








 



 



 



 



 



删除文档:



DELETE /website/blog/123



 



如果文档找到了,es返回一个200 OK码,如下:注意,_version数字已经改变。



{



"found" :    true ,



"_index" :   "website" ,



"_type" :    "blog" ,



"_id" :      "123" ,



"_version" : 3



}



如果文档没找到,返回404.



 



{



"found" :    false ,



"_index" :   "website" ,



"_type" :    "blog" ,



"_id" :      "123" ,



"_version" : 4



}



尽管文档不存在,没有找到它,_version仍然增加。



这是内部机制,保证多节点的变动按照顺序执行。



 



如更新文档时所说,删除一个文档不会立刻从磁盘删除文档,仅仅标记为删除。



es将在你索引别的文档时后台删除标记为删除的文档。




 



 



 



 



 



 



 



当使用index API来更新文档时,我们读取原始文档,做出修改,重新索引整个文档,一口气完成。



 



最后一次的索引请求胜出,如果某人之前做出了文档改变,它的改变将会丢失。



 



通常,这不是一个问题,也许我们的主数据存储时一个关系型数据库,我们只是把数据复制到了ES来让它可以搜索,



也许基本不可能有两个人同一时间改变了同一个文档,或者文档改变的丢失对业务来说无关紧要。



 



但是,有的时候,丢失改变非常关键,想象一下,我们正在使用es来存储在线商店里的库存的物品数量,



每次我们卖出了一个,我们减少数量。



 



有一天,管理者决定卖,突然,我们每秒钟卖出一些物品,想象两个web进程,同时运行,都在处理同一个物品。



图7:没有并发控制的结果。



 



web_1做出的数量改变已经丢失,因为web_2不知道它的数据失效了,



结果就是:我认为我们有更多的物品,实际上不然,这会让客户失望。



操作越频繁,或者读和更新操作之间的时长越大,丢失越会发生。



 



有两种方法来保证并发的正确性。



 



悲观并发控制策略:



作为已经被关系型数据库广泛使用的策略,假设冲突很容易发生,



锁住相关资源来预防竞争,典型的例子就是:在读数据之前,锁住一行,保证当前线程可以做出改变。



 



乐观并发控制策略



这是es使用的策略,假设冲突不太可能发生,不阻止操作。



尽管如此,如果在读和写之间,数据已经改变,更新将会失败。



这得由应用自己决定如何解决这个问题,比如:



可以使用新的数据重新更新,或者报告失败给用户。




 



 



 



 



 



 



es是一个分布式的系统,当文档创建,更新或者删除,新版本的文档必须复制到其它节点,



es同时也是异步和并发的,这意味着这些备份请求同时发送,并且可能乱序到达目的地,



这就需要一种策略来保证旧版本的文档不会覆盖新版本的文档。



 



当我们讨论索引,get/ delete 请求,我们指出:每个文档有一个_version字段,当文档改变时



这个字段自增,es使用这个数字来保证改变的应用按照正确的顺序来做,



如果一个旧的文档在新文档之后来到,简单的被丢弃。



 



我们可以通过_version字段来受益,保证冲突不会导致数据丢失。



我们通过指定文档的版本号。如果版本号不是最新的,请求将会失败。



 



先创建一个文档。



PUT /website/blog/1/_create



{



"title" : "My first blog entry" ,



"text" "Just trying this out..."



}



 



响应体告诉我们这是一个新创建的文档,版本号为1



现在想象一下,我们需要编辑这个文档,我们下载这个文档,改变,保存。



 



先检索这个文档。



GET /website/blog/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



"created" false



}



 



尽管如此,如果我们再去发同样的请求,也就是version=1,



es将返回一个409冲突。



响应体如下:



{



"error" : "VersionConflictEngineException[[website][2] [blog][1]:



version conflict, current [2], provided [1]]",



"status" : 409



}



告诉我们,目前的版本号是2,但是我们想要更新版本号1的文档。



 



我们可以告诉用户某人已经改变了文档,然后应该在重新尝试前看一下改变。



我们也可以重新检索最新文档再改变。



 



所有的API(update/ delete )接收一个版本号参数,容许我们应用乐观并发控制。



 



使用外部系统的版本号



 



一个常用的设置是:使用其它数据库作为主数据存储,es让数据可搜索,



也就是说,所有对主数据库的改变需要复制到es.如果有多个进程涉及到数据同步,你将会碰到并发问题。



 



如果你的主数据库已经有一个版本号,或者时间戳之类,你可以使用这些值,通过添加



version_type=external.



版本号必须是整数,大于0,小于9.2e+18 。



 



这种方式跟内部的版本号有点区别:



之前是判断es里的版本号和用户提交的参数是否一致,现在是比较目前的版本号是否比参数小,



如果请求成功,外部的版本号就成功存储。



 



外部的版本号不仅可以在索引和删除一个文档时指定,创建新文档时也可以指定。



 



比如说,创建一个新的blog文档,带有一个版本号5



 



 



PUT /website/blog/2?version=5&version_type=external



{



"title" : "My first external blog entry" ,



"text" "Starting to get the hang of this..."



}



 



响应体:



{



"_index" :   "website" ,



"_type" :    "blog" ,



"_id" :      "2" ,



"_version" : 5,



"created" true



}



 



更新



 



PUT /website/blog/2?version=10&version_type=external



{



"title" : "My first external blog entry" ,



"text" "This is a piece of cake..."



}



 



 



响应体:



{



"_index" :   "website" ,



"_type" :    "blog" ,



"_id" :      "2" ,



"_version" : 10,



"created" false



}



如果你重新执行,会失败,因为版本号不够大。




 



 



 



 



 



 



 



 



之前的更新整个文档时,我们说了:



更新文档的方法是检索,修改,然后重新索引。这是对的。



 



 



尽管如下,使用更新API,我们可以做部分更新操作,比如增加一个计数器的值。



 



我们同时也说,文档不可改变,它们不能被改变,仅仅是替换,更新API必须遵循这个规则。



对外显示的是:我们是部分更新文档,内部来说,更新API执行同样的 检索-更新-重新索引 的过程,



 



不同之处在于,这个过程在一个分片内部发生,避免多个请求造成的负载。



通过减少检索和重新索引的间隔,也可以减少冲突的可能性。



 



 



举个例子,



 



POST /website/blog/1/_update



{



"doc" : {



"tags" : [ "testing" ],



"views" : 0



}



}



 



响应如下:



{



"_index" :   "website" ,



"_id" :      "1" ,



"_type" :    "blog" ,



"_version" : 3



}



 



检索结果:



 



{



"_index" :    "website" ,



"_type" :     "blog" ,



"_id" :       "1" ,



"_version" :  3,



"found" :     true,



"_source" : {



"title" "My first blog entry" ,



"text" :   "Starting to get the hang of this..." ,



"tags" : [ "testing" ],



"views" :  0



}



}



新的字段已经被添加。