elasticsearch Elasticsearch:处理无序更新,elasticsearch,elasticsearch" /> elasticsearch Elasticsearch:处理无序更新,elasticsearch,elasticsearch" />

elasticsearch Elasticsearch:处理无序更新

elasticsearch Elasticsearch:处理无序更新,elasticsearch,elasticsearch,假设我有以下文档: { "name": "Foo" "age": 0 } { "script": { "source": "if (ctx._source.name.last_updated_time < event.service_timestamp) { ctx._source.name.value = event.updated_name; ctx._source.name.last_upda

假设我有以下文档:

{
  "name": "Foo"
  "age": 0
}
{
  "script": {
    "source": "if (ctx._source.name.last_updated_time < event.service_timestamp) { 
                 ctx._source.name.value = event.updated_name;
                 ctx._source.name.last_updated_time = event.service_timestamp;
               }"
  }
}
我们收到触发这些字段更新的事件:

Event 1
{
  "service_timestamp": "2019-09-15T09:00:01",
  "updated_name": "Bar"
}

Event 2
{
  "service_timestamp": "2019-09-15T09:00:02",
  "updated_name": "Foo"
}
事件2
由我们的服务发布的时间比
事件1
晚1秒,因此我们希望我们的文档首先将“name”属性更新为“Bar”,然后返回到“Foo”。然而,想象一下,无论出于何种原因,这些事件都会发生无序(
event2
然后
event1
)。文档的最终状态将为“Bar”,这不是所需的行为

我们需要保证按照事件上“服务时间戳”字段的顺序更新文档。

我们提出的一个解决方案是在每个字段上增加一个
last\u updated\u属性
,如下所示:

{
  "name": {
    "value": "Foo",
    "last_updated_time": 1970-01-01T00:00:00
  }

  "age": {
    "value": 0,
    "last_updated_time": 1970-01-01T00:00:00
  }
}
然后,如果事件的
服务时间戳
发生在文档中属性的
上次更新时间
之后,我们才会更新属性:

{
  "name": "Foo"
  "age": 0
}
{
  "script": {
    "source": "if (ctx._source.name.last_updated_time < event.service_timestamp) { 
                 ctx._source.name.value = event.updated_name;
                 ctx._source.name.last_updated_time = event.service_timestamp;
               }"
  }
}
{
“脚本”:{
“源”:“如果(ctx.\u source.name.last\u updated\u time
虽然这会起作用,但每次更新时先读后写似乎代价高昂。是否有其他方法保证事件以正确的顺序更新

编辑1:其他一些需要考虑的事情
我们不能假设无序事件会在很短的时间内发生。设想如下:我们尝试更新客户的姓名,但此更新失败,因此我们将更新事件存储在某个死信队列中,以便稍后重新填充它。我们修复了导致更新失败的错误,并重新填充死信队列中的所有事件。如果在修复此错误期间没有更新名称字段的更新,则死信队列中的事件应成功更新属性。但是,如果某些事件确实更新了名称,死信队列中的事件不应更新属性。

Mousa所说的一切都是正确的wrt“内部”版本控制,这就是让Elasticsearch处理版本递增的地方

但是,Elasticsearch还支持“外部”版本控制,您可以为每个更新提供一个版本,该版本将根据当前文档的版本进行检查。我相信这将解决将事件索引为ES“无序”的情况,并在事件的任何时间范围内防止这些问题(无论间隔1秒还是1周,如死信队列示例所示)

为此,您需要跟踪主数据存储中文档的版本(Elasticsearch永远不应该是主数据存储!),并将其附加到索引请求

首先,您可以使用所需的任何版本号创建文档,让我们从1开始:

POST localhost:9200/my-index/my-type/<doc id>?version=1&version_type=external -d
{
  "name": "Foo"
  "age": 0
}

POST localhost:9200/my index/my type/?version=1&version\u type=external-d
{
“名称”:“Foo”
“年龄”:0
}
然后更新还将从您的服务和/或主数据存储中获得指定的版本

Event 1
POST localhost:9200/my-index/my-type/<doc id>?version=2&version_type=external -d
{
  "service_timestamp": "2019-09-15T09:00:01",
  "updated_name": "Bar"

}

Event 2
POST localhost:9200/my-index/my-type/<doc id>?version=3&version_type=external -d
{
  "service_timestamp": "2019-09-15T09:00:02",
  "updated_name": "Foo"
}
事件1
POST localhost:9200/my index/my type/?version=2&version\u type=external-d
{
“服务时间戳”:“2019-09-15T09:00:01”,
“更新的_名称”:“酒吧”
}
事件2
POST localhost:9200/my index/my type/?version=3&version\u type=external-d
{
“服务时间戳”:“2019-09-15T09:00:02”,
“更新的_名称”:“Foo”
}
这样可以确保即使更新应用顺序不正确,最近的更新也会获胜。如果在事件2之后应用事件1,您将得到一个表示
versionconflictengineeexception
409
错误代码,最重要的是,事件1将而不是覆盖事件2

您可以选择将时间戳转换为epoch millis,并将其作为版本提供,而不是每次将版本int增加1,这类似于您创建
last\u updated\u属性
字段的想法,但可以利用Elasticsearch的内置版本控制。这样,最近的时间戳更新将始终“获胜”,并最后应用

我高度推荐你阅读这篇关于Elasticsearch版本控制的短篇博文-它比我在这里做的更详细:


搜索快乐

存储在Elasticsearch中的每个文档都有一个关联的版本号。该版本号是介于1和2^63-1(含)之间的正数。当您第一次为文档编制索引时,它会获取版本1,并且每次对此文档执行写入操作时,无论是索引、更新还是删除,Elasticsearch都会将版本增加1,因此您应该查找该版本,而不是时间戳。看看“Elasticsearch版本控制系统”。@TunaMcFish我如何使用版本号?在每次更新时创建一个新版本,但将某种“正在使用”版本设置为最新的更新时间戳?当您
获取
文档时,您会在响应中返回一个
\u version
字段。现在,您只需将该版本号添加到您的
帖子中,例如,Elasticsearch需要做的是比较两个版本号,如果是相同的版本,则更新将成功完成,否则您将获得
200 OK
(版本不相同)elastic将不执行该操作,它将以
309冲突向您发出信号。现在,如果我正确理解了您的问题,我不明白您为什么还要使用自定义的“更新时间戳”字段。例如,如果您的服务针对给定文档的同一版本触发了两个更新事件,那么第一个会成功,但第二个肯定会失败。@TunaMcFish啊,这是一个有趣的想法。我看到的唯一问题是,如果我想修改名称,然后快速连续修改年龄字段,该怎么办?理论上,名称更新将增加版本,导致年龄更新失败