我有一个RabbitMQ代理,在其上发布不同的消息,最终将作为Elasticsearch中的文档。代理中有多个使用者,它们实际上是分配给amqp入站网关的任务执行者中的不同线程(使用spring integration和spring amqp)。
请考虑以下场景:我在ES中创建了一个带有结构
的文档{
"field1" : "value1",
"field2" : "value2"
}
之后我发送了两个更新请求,两个更新同一个字段,让我们说field1
。如果我一个接一个地发送这些消息(生产中的常见用例),我的消费者线程将以正确的顺序获取消息(amqp允许这样做),但处理可能以错误的顺序发生,后来更新的值可能是被第一个覆盖。我最终会得到数据。
如何确保我的数据不会被破坏? =>拥有1个单一的消费者线程是不够的,因为如果我想通过使用我的消费应用程序添加更多机器来扩展,我仍然会有多个消费者。我可能需要订购消息,但是我可能需要创建某种群集感知组件的多台机器,我使用SI,所以在我看来这似乎很难。
在ES的1.2版本中,我们使用了外部版本,比如时间戳,ES会在我的场景中抛出VersionConflictException
:第一次更新会让版本10000让我们说,第二次10001,如果是首先将首先处理,ES将拒绝版本10000的请求,因为它低于现有的请求。但是从最新版本开始,ES人have removed this functionality进行更新操作。
答案 0 :(得分:1)
一种解决方案可能是使用多个队列并在每个队列上拥有一个消费者;使用哈希函数始终将对同一文档的更新路由到同一队列,请参阅RabbitMQ Tutorials了解各种选项。
您可以通过添加更多队列(以及更改哈希函数)来扩展。
要获得弹性,请考虑在Spring XD中运行您的消费者。您可以拥有每个兔子源的单个实例(对于每个队列),如果XD发生故障,XD将负责将其故障转移到另一个容器节点。
否则,您可以通过使用auto-startup="false"
配置热备用 - 入站适配器并使用<control-bus/>
监视并启动新实例(如果活动的实例关闭)来自行推送。
修改强>
回应下面的第四条评论。
正如我上面所说,要扩展,你必须改变哈希函数。因此,在运行时自动添加消费者将是棘手的。
您不必对jar中的队列名称进行硬编码,您可以使用属性占位符并从属性,系统属性或环境变量中填充它。
此解决方案最简单,但确实存在这些限制。
然而,您可以构建一个可以扩展它的管理应用程序 - 停止生产者,等待所有队列停顿,重新配置使用者并重新启动生产者 - Spring Integration提供<control-bus/>
来启动/停止适配器;你也可以通过JMX来做。
替代解决方案是可能的,但通常需要在集群中维护一些共享状态(可能使用zookeeper等),因此要复杂得多;并且仍然必须处理竞争条件(第二次更新可能会在第一次更新之前到达某个消费者)。
答案 1 :(得分:0)
您可以使用默认机制进行一致性检查。基本上你想验证你有更新的最新版本。
因此,您需要使用该对象获取_version。在查询中,您可以通过在顶层设置version = true来完成此操作。这将导致_version与您的查询结果一起返回。然后在进行更新时,只需将url中的version参数设置为您拥有的值,如果不匹配则会产生版本冲突。
Nicer是使用闭包来处理更新。基本上这可以如下工作:有一个通过id获取对象的更新方法,应用一个封装(更新函数的参数)来封装你想要进行的修改,然后存储修改后的对象。如果捕获仍然可能的版本冲突,则可以再次获取对象并将闭包重新应用于对象。我们这样做并在重试之前添加了随机睡眠,这极大地降低了多次更新失败的可能性,并且是一种不错的设计模式。保持读和写在一起可以最大限度地减少冲突的可能性,然后在睡眠之前重试,从而进一步减少冲突。您可以添加多次重试以进一步降低风险。