让我们以js“app”为例,它基本上是CRUD,因此它创建,更新和删除(不是真的)某些“记录”。
在最基本的情况下,一个人不需要解决这种应用程序中的冲突,因为DBMS的ACID属性用于消除并发更新(我知道这里略读了大量细节)。当无法模拟更新的串行执行时,可以使用时间戳来确定更新“获胜”。即使这样,客户端也不必担心时间戳,因为它们可以在服务器上的请求时生成。
但是,如果我们更进一步并允许更新在客户端排队一段时间(例如,允许应用程序在没有网络连接的情况下工作),然后推送到服务器,该怎么办?然后无法在服务器上生成时间戳,因为更新被推送到服务器的时间和执行更新的实际时间可能差别很大。
在所有时钟同步的理想世界中,这不是问题 - 只需在执行更新时在客户端上生成时间戳。但实际上,时间往往会偏离“服务器”时间(假设它是完美的,毕竟它的我们配置服务器,它可能出现什么问题?)或者只是简单的错误按小时(可能在您未设置时区时,而是更新系统的时间/日期以匹配)。在这种情况下,如何解释现实会怎样做?
也许还有其他解决冲突的方法,可以在这种情况下使用?
答案 0 :(得分:4)
您的问题有两个方面:
如果你正在维护客户端的队列,当它认为合适时推送到服务器,那么它最好有简单的同步。因为它只是破坏了服务器所依赖的时间戳的目的。
此处ACID的范围有限,因为如果客户端更新不是实时的,则无法根据创建的请求的时间戳或请求到达来序列化。它创建了一个场景,其中创建的请求R2晚于请求R1到达R1之前。
时间是一个相对概念,使用客户端或服务器的本地时间将导致另一个drift
。它也不会扩展(如果你有几个对等节点 - 分布式效率低下)。它引入了单点故障。
要解决这个问题vector-clocks已经设计好了。它们是逻辑时钟,当机器上的事件原子发生时,它会递增时钟。 BASE数据库(基本可用,软状态,最终一致性)使用它。
结果是1和2永远不会有效。您永远不应将使用时间戳进行冲突解决的请求排队。
答案 1 :(得分:1)
很好的挑战。虽然我很欣赏user568109的anwser,但这就是我在CQRS / DDD应用程序中处理类似情况的方式。
在DDD应用程序中,我有很多不同的命令和查询,在CRUD应用程序中,对于每种类型的“记录”,我们有CREATE,UPDATE和DELETE命令以及READ查询。
在我的系统中,在客户端上,我跟踪元组中的先前同步,其中包括:服务器上的UTC时间,客户端上的时间(我们称之为 LastSync )。
<强> READ 强>
读取查询不会分离到同步。但是,在某些情况下,您可能必须向服务器发送LogRead命令以跟踪用于做出决策的信息。这种命令确实包含实体的类型,实体的标识符和LastSync.ServerTime)。
创建强>
根据定义,创建命令是幂等元素:它们成功或失败(当具有相同标识的记录已存在时)。在同步时,您必须通知用户冲突(以便他能够处理这种情况,例如通过更改标识符)或来修复时间戳,如后面所述。
<强>更新强>
更新命令有点棘手,因为您可能应该在不同类型的记录上以不同方式处理它们。为了简单起见,您应该能够强制上次更新总是获胜的用户并设计命令以仅携带应更新的属性(就像SQL UPDATE语句一样)。否则你将不得不处理自动/手动合并(但相信我,它是一个睡眠:大多数用户都不会理解它!)最初我的客户需要大多数实体的这个功能,但过了一段时间他们接受了最后一个更新胜利以避免这种复杂性。此外,如果对已删除对象进行更新,则应将该情况通知给用户,并根据更新的实体类型应用更新。
删除强>
检测命令应该很简单,除非你必须通知用户发生的更新可能导致他保留记录而不是删除它。
您应该仔细分析如何为每种类型的实体处理每个命令(在UPDATE的情况下,您可能会被迫以不同方式处理它们,以便更新不同的属性集)。
同步过程
同步会话应该开始向服务器发送带有
这样,服务器可以计算其时间与客户端时间之间的偏移量,并将这种偏移量应用于他收到的每个命令。此外,它可以检查 LastSync 后偏移量是否发生变化,并选择一种策略来处理此更改。请注意,通过这种方式,服务器将无法知道何时调整客户端的时钟。
在成功同步结束时(由您自行决定此处的成功含义),客户端会更新 LastSync 元组。
最终说明
这是一个非常复杂的解决方案。如果这种复杂性在开始实施之前给你足够的价值,你应该仔细考虑客户。