为去中心化日历项目选择数据库类型

Choosing databasetype for a decentralized calendar project

我正在开发一个去中心化的日历系统。它应该将数据保存在每台设备上并在它们都具有互联网连接时进行同步。我的第一个想法是,只使用关系数据库并尝试在连接后同步数据。但是这个理论说的是另外一回事。 The Brewers CAP-Theorem 描述了它背后的理论,但我不确定这个定理是否过时了。如果我使用这个定理,我有 "AP [Availability/Partition Tolerance] Systems"。 "A" 因为我在任何给定时间都需要我的日历数据并且 "P" 因为它可能发生,设备之间没有连接并且数据无法同步。示例数据库是 CouchDB、RIAK 或 Cassandra。我只使用关系数据库,现在不知道如何继续。为我的项目使用关系数据库有那么糟糕吗?

这是我的学士论文。我只是想开始使用 Postgres,但后来我发现了这个定理...... 整个项目基于Java.

我认为 CAP 定理对您的场景并没有多大帮助。处理分区的分布式系统需要决定当一个部分想要对数据进行修改但无法到达另一部分时要做什么。一种解决方案是让写入等待 - 由于 "partition",这是 CAP 定理提供的选项之一,因此放弃了 "availability"。但是还有更多有用的选项。最有用的 (highly-available) 选项是允许两个部分独立编写,并在它们可以再次连接时调和 冲突。问题是如何去做,不同的分布式系统选择不同的方法。

一些系统,如 Cassandra 或 Amazon 的 DynamoDB,使用 "last writer wins" - 当我们看到两个冲突写入之间发生冲突时,最后一个(根据一些同步时钟)获胜。为了使这种方法有意义,您需要非常小心地对数据建模(例如,注意冲突解决导致两种状态无效混合的情况)。

在其他系统中(以及在 Cassandra 和 DynamoDB 中 - 在它们的 "collection" 类型中)写入仍然可以在不同的节点上独立发生,但是有更复杂的冲突解决方案。一个很好的例子是 Cassandra 的 "list":一个人可以发送一个更新说 "add item X to the list",另一个更新说 "add item Y to the list"。如果这些更新发生在不同的分区上,稍后通过将 both X 和 Y 添加到列表中来解决冲突。像这个列表这样的数据结构——允许在两个节点上以特定方式独立修改内容,然后以合理的方式自动协调,被称为 Conflict-free Replicated Data Type (CRDT)。

最后,Amazon 的 Dynamo 论文中使用了另一种方法(不要与他们当前的 DynamoDB 服务混淆!),称为 "vector clocks":当您想写入 object - 例如, 一个购物车 - 你首先读取 object 的当前状态并得到一个 "vector clock",你可以把它看作是你得到的数据的 "version"。然后您进行修改(例如,将商品添加到购物车),然后写回新版本,同时说明您开始使用的旧版本是什么。如果其中两个修改在不同分区上并行发生,我们稍后需要协调这两个更新。矢量时钟允许系统确定一个修改是否比另一个修改 "newer"(在这种情况下没有冲突),或者它们确实存在冲突。当他们这样做时,application-specific 逻辑用于调和冲突。在购物车示例中,如果我们看到冲突是在一个分区中将商品 A 添加到购物车中,而在另一个分区中将商品 B 添加到购物车中,那么直接的解决方法就是同时添加 A 和B 到购物车。

您或许应该选择其中一种方法。只是说 "the CAP theorem doesn't let me do this" 通常不是一个选项 ;-) 事实上,在某些方面,您面临的问题与我提到的一些系统 不同 。在那些系统中,常见的情况是每个节点始终连接(无分区),延迟非常低,并且他们希望这种常见情况更快。在您的情况下,您可能会假设相反的情况:这两个部分通常不连接,或者如果它们连接则存在高延迟,因此解决冲突是因为常态,而不是例外。所以你需要决定如何解决这个冲突 - 如果一个人在一个设备上添加一个会议并在另一个设备上添加一个不同的会议会发生什么(最有可能的是,将两者都保留为两个会议......),你怎么知道一台设备修改了 pre-existing 会议但没有添加第二个会议(矢量时钟?独特的会议 ID?等)所以冲突解决最终修复了现有会议而不是添加第二个会议?等等。一旦你这样做了,你在两个分区上存储数据的位置(可能是客户端和服务器中完全不同的数据库实现)以及你发送更新的协议成为实现细节。

您还需要考虑另一个问题。我们什么时候进行这些协调?在我上面列出的许多系统中,协调发生在读取时:如果客户端想要读取数据,而我们突然在两个可访问的节点上看到两个冲突的版本,我们就会协调。在您的日历应用程序中,您需要一种稍微不同的方法:客户端可能只会在未连接时尝试读取(使用)日历。你需要利用他连接时难得的机会来调和所有的分歧。此外,您可能需要 "push" 更改 - 例如,如果服务器上的数据发生更改,可能需要告知客户端 "hey, I have some changed data, come and reconcile",因此 end-user 将立即看到关于例如,远程添加的新会议(例如,可能由共享同一日历的不同用户添加)。你需要弄清楚你想如何做到这一点。同样,没有像 "use Cassandra".

这样的神奇解决方案