深耕ElasticSearch - 文档的部分更新

深耕ElasticSearch - 文档的部分更新,第1张

深耕ElasticSearch - 文档的部分更新

文章目录
    • 1. 文档的全量替换
    • 2. 文档的部分更新
    • 3. 文档的部分更新原理
    • 4. 文档部分更新的并发冲突问题

1. 文档的全量替换

在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"
   }
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5669793.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存