我不确定方法的正确设计。
我们使用乐观锁定,使用long
增量版本放置在每个实体上。这种实体的每次更新都是通过比较和交换算法执行的,该算法只是成功或失败,这取决于其他客户端是否同时更新实体。经典乐观锁定例如hibernate做。
我们还需要采用重新尝试的方法。我们使用基于http
的存储(etcd),并且可能会发生一些更新请求只是暂停了。
这就是问题所在。如何结合乐观锁定和重试。这是我面临的具体问题。
假设我有一个version=1
的实体,我正在尝试更新它。下一个版本显然是2
。我的客户端比执行条件更新。仅当持久性版本为1
并且原子级更新为version=2
时才会成功执行。到目前为止,非常好。
现在,假设更新请求的响应未到达。现在不可能说它是否成功。我现在唯一能做的就是再次重新尝试更新。在内存中,实体仍然包含version=1
打算将值更新为2
。
现在出现了真正的问题。 如果第二次更新失败,因为持久性版本为2
而不是1
会怎样?
有两个可能的原因:
现在我不能说什么是真的。我的客户端是否更新了实体或其他客户端?操作是通过还是失败?
目前我们只使用比较持久化实体和主内存中的实体。无论是java等于还是json内容相等。如果它们相等,则更新方法被声明为成功。我对算法不满意,因为它对我来说既不便宜又合理。
另一种可能的方法是不使用long
版本,而是使用timestamp
。每个客户端在更新操作中生成自己的时间戳,这意味着潜在的并发客户端将以高概率生成其他客户端。对我来说问题是概率,特别是当两个并发更新来自同一台机器时。
还有其他解决方案吗?
答案 0 :(得分:1)
恕我直言,由于etcd
建立在HTTP本身就是一种不安全的协议上,因此很难有一个防弹解决方案。
经典SQL数据库使用连接的协议,事务和日记化,以允许用户确保整个事务处理完全提交或完全回滚,即使在最坏的情况下,在操作过程中断电。
因此,如果两个操作相互依赖(从一个银行账户转账到另一个银行账户),您可以确保两者都可以或没有,您可以在数据库中实现“操作”的日志即使您在提交过程中断开连接,也可以通过查阅期刊来查看是否通过了特定的状态。
但我无法想象etcd
这样的解决方案。因此,除非其他人找到更好的方法,否则您将有两个选择
etcd
(或等效的)作为简单缓存但是如果你现在可以读取版本,那么会出现更糟糕的问题,它不在n + 1但已经在n + 2。如果UUID是您的,您确定您的交易已通过,但如果不是,则无人能说。
答案 1 :(得分:1)
您可以使用两步协议伪造etcd中的交易。
更新算法:
第一阶段:将更新记录到etcd
第二阶段:执行实际更新
如果您想阅读一致的数据:
否则,请等待它被删除。
事务恢复,如果您发现没有锁定的更新计划节点: