【发布时间】:2019-08-15 18:27:31
【问题描述】:
假设我有以下文件:
{
"name": "Foo"
"age": 0
}
我们收到触发这些字段更新的事件:
Event 1
{
"service_timestamp": "2019-09-15T09:00:01",
"updated_name": "Bar"
}
Event 2
{
"service_timestamp": "2019-09-15T09:00:02",
"updated_name": "Foo"
}
Event 2 比Event 1 晚 1 秒由我们的服务发布,因此我们希望我们的文档首先将“name”属性更新为“Bar”,然后再更新为“Foo”。但是,想象一下,无论出于何种原因,这些事件都会出现故障(Event 2 THEN Event 1)。文档的最终状态将是“Bar”,这不是所需的行为。
我们需要保证我们按照事件上“service_timestamp”字段的顺序更新我们的文档。
我们提出的一个解决方案是在每个字段上添加一个 last_updated_property,如下所示:
{
"name": {
"value": "Foo",
"last_updated_time": 1970-01-01T00:00:00
}
"age": {
"value": 0,
"last_updated_time": 1970-01-01T00:00:00
}
}
只有当事件的service_timestamp 发生在文档中属性的last_updated_time 之后时,我们才会更新属性:
{
"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;
}"
}
}
虽然这可行,但在每次更新时执行读取然后写入似乎代价高昂。还有其他方法可以保证事件以正确的顺序更新吗?
编辑 1:需要考虑的其他一些事项
我们不能假设乱序事件会在很小的时间窗口内发生。想象一下:我们试图更新一个客户的名字,但是这个更新失败了,所以我们将更新事件存储在一些死信队列中,以便稍后重新触发它。我们修复了导致更新失败的错误,并重新触发死信队列中的所有事件。如果在我们修复此错误期间没有发生更新名称字段的更新,则死信队列中的事件应该成功更新属性。但是,如果某些事件确实更新了名称,死信队列中的事件不应更新属性。
【问题讨论】:
-
您存储在 Elasticsearch 中的每个文档都有一个关联的版本号。该版本号是介于 1 和 2 ^63-1(含)之间的正数。当您第一次为文档建立索引时,它会获取版本 1,并且对该文档的每次写入操作,无论是索引、更新还是删除,Elasticsearch 都会将版本增加 1,因此您应该查看而不是时间戳。看看“Elasticsearch 版本控制系统”。
-
@TunaMcFish 我怎样才能使用版本号?每次更新都创建一个新版本,但将某种“正在使用”的版本设置为最新的更新时间戳?
-
当您
GET一个文档时,您会在响应中返回一个_version字段。现在您只需将该版本号添加到您的POST中,Elasticsearch 要做的就是比较两个版本号,如果版本相同,那么更新将成功完成,您将得到200 OK否则(版本不同)elastic 将不会执行该操作,它会用309 CONFLICT向您发送信号。现在,如果我正确理解了您的问题,我不明白为什么您仍然必须使用自定义的“更新时间戳”字段。 -
如果您的服务针对给定文档的同一版本触发了两个更新事件,那么第一个会成功,但第二个肯定会失败。
-
@TunaMcFish 啊,这是一个有趣的想法。我看到的唯一问题是如果我想修改名称,然后快速连续修改年龄字段怎么办?理论上,名称更新会增加版本,导致年龄更新失败。但是,我希望两者都能成功,因为这些事件不会引起任何形式的冲突
标签: elasticsearch