- 1. 文档的全量替换
- 2. 文档的部分更新
- 3. 文档的部分更新原理
- 4. 文档部分更新的并发冲突问题
在ES中文档是不可改变的,不能修改它们。如果想要更新现有的文档,方法是检索并修改它,然后重新索引整个文档(检索-更新-索引),创建文档&替换文档是一样的语法:
PUT /index/type/id
1、假如我们创建了一个文档
PUT /test_index/_doc/4 { "test field1":"test1", "test field2":"test2", "test field3":"test3" }
{ "_index" : "test_index", "_type" : "_doc", "_id" : "4", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 7, "_primary_term" : 1 }
2、想要修更新改文档中test field1字段的值,我们使用这个语法只索引test field1字段:
PUT /test_index/_doc/4 { "test field1":"test4" }
{ "_index" : "test_index", "_type" : "_doc", "_id" : "4", "_version" : 2, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 11, "_primary_term" : 1 }
查询更新后的文档,发现文档此时只有test field1字段,其他字段都没了:
GET /test_index/_doc/4
{ "_index" : "test_index", "_type" : "_doc", "_id" : "4", "_version" : 2, "_seq_no" : 11, "_primary_term" : 1, "found" : true, "_source" : { "test field1" : "test4" } }
4、事实上,文档是不可变的:他们不能被修改,只能被替换。想要只更新改文档中test field1字段的值,需要重新索引整个文档:
PUT /test_index/_doc/4 { "test field1":"test4", "test field2":"test2", "test field3":"test3" }
{ "_index" : "test_index", "_type" : "_doc", "_id" : "4", "_version" : 3, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 12, "_primary_term" : 1 }
查询更新后的文档,实现了更新文档中部分字段的目的:
GET /test_index/_doc/4
{ "_index" : "test_index", "_type" : "_doc", "_id" : "4", "_version" : 3, "_seq_no" : 12, "_primary_term" : 1, "found" : true, "_source" : { "test field1" : "test4", "test field2" : "test2", "test field3" : "test3" } }
因此,更新文档时需要经过检索-更新-重新索引整个文档的流程,在内部,ES已将旧文档标记为已删除,并增加一个全新的文档。 尽管你不能再对旧版本的文档进行访问,但它并不会立即消失。当继续索引更多的数据,ES会在后台清理这些已删除文档。
一般对应到应用程序中,每次的执行流程基本是这样的:
(1)应用程序先发起一个get请求,获取到document,展示到前台界面,供用户查看和修改
(2)用户在前台界面修改数据,发送到后台
(3)后台代码,会将用户修改的数据在内存中进行执行,然后封装好修改后的全量数据
(4)然后发送PUT请求,到es中,进行全量替换
(5)es将老的document标记为deleted,然后重新创建一个新的document
2. 文档的部分更新
使用 update API 我们可以部分更新文档,语法格式为:
post /index/type/id/_update { "doc": { "要修改的少数几个field即可,不需要全量的数据" } }
update 请求最简单的一种形式是接收文档的一部分作为 doc 的参数, 它只是与现有的文档进行合并。对象被合并到一起,覆盖现有的字段,增加新的字段。 看起来,好像就比较方便了,每次就传递少数几个发生修改的field即可,不需要将全量的document数据发送过去。
1、构造数据创建一个文档:
PUT /test_index/_doc/5 { "test field1":"test1", "test field2":"test2", "test field3":"test3" }
2、只更新文档中的部分字段:
POST /test_index/_doc/5/_update { "doc":{ "test field1":"test4" } }
#! Deprecation: [types removal] Specifying types in document update requests is deprecated, use the endpoint /{index}/_update/{id} instead. { "_index" : "test_index", "_type" : "_doc", "_id" : "5", "_version" : 3, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 15, "_primary_term" : 1 }
通过提示我们发现新版本es中,type被去除了,让我们使用/{index}/_update/{id} 替代:
POST /test_index/_update/5 { "doc":{ "test field1":"test4" } }
3、查询更新后的文档:
GET /test_index/_doc/5
{ "_index" : "test_index", "_type" : "_doc", "_id" : "5", "_version" : 3, "_seq_no" : 15, "_primary_term" : 1, "found" : true, "_source" : { "test field1" : "test4", "test field2" : "test2", "test field3" : "test3" } }
3. 文档的部分更新原理
文档的部分更新,看起来很方便的 *** 作,实际内部的原理是什么样子的,然后它的优点是什么?
如图,用户不用检索ES中的文档,而是直接执行更新文档,它似乎对文档直接进行了修改。但是文档是不可变的:他们不能被修改,只能被替换。即使使用update API 也必须遵循同样的规则。 从外部来看,我们在一个文档的某个位置进行部分更新,然而在内部,update API和传统的文档全量替换流程几乎一样,即检索-修改-重建索引整个文档的处理过程。:
(1)内部先获取document
(2)将传过来的field更新到document的json中
(3)将老的document标记为deleted
(4)将修改后的新document创建出来
文档部分更新相较于文档全量替换的好处:
1、所有的查询,修改和写回文档的 *** 作发生在分片内部,这样就避免了多次请求的网络开销(减少了2次网络请求)。 update API 仅仅通过一个客户端请求来实现这些步骤,而不需要单独的 get 和 index 请求。
2、用户查询文档后修改文档可能需要10分钟甚至半小时,修改完后再将数据写回ES,可能ES中的数据早就被人修改了,所以并发冲突的情况发生的较多。通过减少检索和重建索引步骤之间的时间,我们也减少了其他进程的变更带来冲突的可能性。
4. 文档部分更新的并发冲突问题
我们说检索和重建索引步骤的间隔越小,变更冲突的机会越小。 但是它并不能完全消除冲突的可能性。 还是有可能在 update 设法重新索引之前,来自另一进程的请求修改了文档。
为了避免数据丢失, update API 在检索步骤时检索得到文档当前的 _version 号,并传递版本号到重建索引步骤的 index 请求。 如果另一个进程修改了处于检索和重新索引步骤之间的文档,那么 _version 号将不匹配,更新请求将会失败。
对于部分更新的很多使用场景,文档已经被改变也没有关系。 例如,如果两个进程都对页面访问量计数器进行递增 *** 作,它们发生的先后顺序其实不太重要; 如果冲突发生了,我们唯一需要做的就是尝试再次更新。
(1)再次获取document数据和最新版本号
(2)基于最新版本号再次去更新,如果成功就ok了
(3)如果失败,重复前两个步骤,通过设置参数 retry_on_conflict 来指定重试的次数。
这可以通过设置参数 retry_on_conflict 来自动完成, 这个参数规定了失败之前 update 应该重试的次数,它的默认值为 0 。retry_on_conflict=5表示在失败之前重试该更新5次。
POST /test_index/_update/5?retry_on_conflict=5 { "doc":{ "test field1":"test6" } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)