245

我有一种感觉,必须有客户端-服务器同步模式。但我完全没能用谷歌搜索一个。

情况很简单——服务器是中心节点,多个客户端连接并操作相同的数据。数据可以被拆分为原子,如果发生冲突,服务器上的任何内容都具有优先级(以避免让用户解决冲突)。由于潜在的大量数据,部分同步是首选。

对于这种情况,是否有任何模式/良好做法,或者如果您不知道 - 您的方法是什么?

以下是我现在认为解决它的方法:与数据并行,将保存一个修改日志,所有事务都带有时间戳。当客户端连接时,它以合并的形式接收自上次检查以来的所有更改(服务器检查列表并删除添加的内容,然后删除,合并每个原子的更新等)。瞧,我们是最新的。

另一种方法是保留每条记录的修改日期,而不是执行数据删除,只需将它们标记为已删除。

有什么想法吗?

4

7 回答 7

93

您应该了解分布式变更管理的工作原理。查看 SVN、CVS 和其他管理增量工作的存储库。

您有几个用例。

  • 同步更改。您的更改日志(或增量历史)方法看起来很适合这个。客户端将他们的增量发送到服务器;服务器将增量合并并分发给客户端。这是典型的案例。数据库将此称为“事务复制”。

  • 客户端已失去同步。通过备份/恢复或由于错误。在这种情况下,客户端需要从服务器获取当前状态而不通过增量。这是从大师到细节的副本,该死的增量和性能。这是一次性的事情;客户端坏了;不要试图优化这一点,只需实现一个可靠的副本。

  • 客户怀疑。在这种情况下,您需要将客户端与服务器进行比较,以确定客户端是否是最新的并且需要任何增量。

您应该遵循按顺序编号每个更改的数据库(和 SVN)设计模式。这样,客户端可以在尝试同步之前提出一个简单的请求(“我应该有什么修订?”)。即便如此,查询(“自 2149 年以来的所有增量”)对于客户端和服务器来说处理起来非常简单。

于 2009-01-05T13:47:02.237 回答
35

作为团队的一员,我做了很多涉及数据同步的项目,所以我应该有能力回答这个问题。

数据同步是一个相当广泛的概念,有太多要讨论的地方。它涵盖了一系列不同的方法及其优缺点。这是基于两个视角的可能分类之一:同步/异步、客户端/服务器/点对点。同步实施严重依赖于这些因素、数据模型复杂性、传输和存储的数据量以及其他要求。因此,在每种特定情况下,选择应该支持满足应用程序要求的最简单的实现。

基于对现有现成解决方案的回顾,我们可以描述几个主要的同步类别,同步对象的粒度不同:

  • 整个文档或数据库的同步用于基于云的应用程序,例如 Dropbox、Google Drive 或 Yandex.Disk。当用户编辑和保存文件时,新的文件版本会完全上传到云端,覆盖之前的副本。如果发生冲突,两个文件版本都会被保存,以便用户可以选择哪个版本更相关。
  • 键值对的同步可用于具有简单数据结构的应用程序,其中变量被认为是原子的,即不分为逻辑组件。此选项类似于同步整个文档,因为值和文档都可以完全覆盖。但是,从用户的角度来看,文档是一个由许多部分组成的复杂对象,而键值对只是一个短字符串或一个数字。因此,在这种情况下,我们可以使用更简单的冲突解决策略,考虑更相关的值,如果它是最后更改的。
  • 结构为树或图的数据同步用于更复杂的应用程序,其中数据量大到足以在每次更新时发送整个数据库。在这种情况下,必须在单个对象、字段或关系级别解决冲突。我们主要关注这个选项。

因此,我们将我们的知识融入到这篇文章中,我认为这可能对每个对该主题感兴趣的人都非常有用 => 基于核心数据的 iOS 应用程序中的数据同步 ( http://blog.denivip.ru/index.php/2014/04 /data-syncing-in-core-data-based-ios-apps/?lang=en )

于 2016-03-16T06:22:00.467 回答
29

您真正需要的是运营转型(OT)。这甚至可以在许多情况下迎合冲突。

这仍然是一个活跃的研究领域,但周围有各种 OT 算法的实现。我已经参与此类研究多年了,如果您对这条路线感兴趣,请告诉我,我很乐意为您提供相关资源。

于 2009-01-05T13:50:49.763 回答
13

这个问题不是很清楚,但如果我是你,我会研究乐观锁定。它可以用服务器为每条记录返回的序列号来实现。当客户端尝试保存记录时,它将包括它从服务器接收到的序列号。如果序列号与接收更新时数据库中的内容匹配,则允许更新并且序列号递增。如果序列号不匹配,则不允许更新。

于 2009-01-05T13:32:22.437 回答
12

大约 8 年前,我为一个应用程序构建了一个这样的系统,我可以分享它随着应用程序使用量的增长而演变的几种方式。

我首先将来自任何设备的每个更改(插入、更新或删除)记录到“历史”表中。因此,例如,如果有人更改了“联系人”表中的电话号码,系统将编辑 contact.phone 字段,并添加一条历史记录 action=update, table=contact, field=phone, record=[联系人 ID],值 = [新电话号码]。然后,每当设备同步时,它都会下载自上次同步以来的历史记录项并将它们应用到其本地数据库。这听起来像上面描述的“事务复制”模式。

一个问题是当项目可以在不同的设备上创建时保持 ID 的唯一性。当我开始这个时我不知道 UUID,所以我使用自动递增 ID 并编写了一些在中央服务器上运行的复杂代码来检查从设备上传的新 ID,如果有冲突,将它们更改为唯一 ID,并且告诉源设备更改其本地数据库中的 ID。只是更改新记录的 ID 并没有那么糟糕,但是如果我在联系人表中创建一个新项目,然后在事件表中创建一个新的相关项目,现在我有我还需要的外键检查和更新。

最终我了解到 UUID 可以避免这种情况,但是到那时我的数据库变得非常大,我担心完整的 UUID 实现会产生性能问题。因此,我没有使用完整的 UUID,而是开始使用随机生成的 8 个字符的字母数字键作为 ID,并保留现有代码以处理冲突。在我当前的 8 个字符键和 UUID 的 36 个字符之间的某个地方,一定有一个最佳点可以消除冲突而不会产生不必要的膨胀,但由于我已经有了冲突解决代码,所以没有优先尝试这个.

下一个问题是历史表大约是数据库其余部分的 10 倍。这使得存储变得昂贵,并且对历史表的​​任何维护都可能很痛苦。保留整个表允许用户回滚之前的任何更改,但这开始感觉像是矫枉过正。所以我在同步过程中添加了一个例程,如果设备上次下载的历史项目不再存在于历史表中,服务器不会给它最近的历史项目,而是给它一个包含所有数据的文件那个帐户。然后我添加了一个 cronjob 来删除超过 90 天的历史记录项。这意味着用户仍然可以回滚不到 90 天的更改,如果他们至少每 90 天同步一次,更新将像以前一样是增量的。但如果他们等待的时间超过 90 天,

这一更改将历史记录表的大小减少了近 90%,因此现在维护历史记录表只会使数据库大两倍而不是十倍。这个系统的另一个好处是,如果需要,同步仍然可以在没有历史记录表的情况下工作——就像我需要做一些暂时离线的维护一样。或者我可以为不同价位的账户提供不同的回滚时间段。而且如果要下载超过 90 天的更改,完整的文件通常比增量格式更有效。

如果我今天重新开始,我会跳过 ID 冲突检查,只针对一个足以消除冲突的密钥长度,并进行某种错误检查以防万一。(看起来 YouTube 使用 11 个字符的随机 ID。)历史记录表以及最近更新的增量下载或需要时的完整下载的组合运行良好。

于 2019-01-23T01:37:35.860 回答
1

对于增量(更改)同步,您可以使用 pubsub 模式将更改发布回所有订阅的客户端,像pusher这样的服务可以做到这一点。

对于数据库镜像,一些 Web 框架使用本地迷你数据库将服务器端数据库同步到浏览器数据库中的本地,支持部分同步。检查仪表

于 2015-05-16T22:17:17.760 回答
-1

本页使用模式和示例代码清楚地描述了大多数数据同步场景:数据同步:模式、工具和技术

这是我找到的最全面的资源,考虑到整个增量同步、如何处理删除以及服务器到客户端和客户端到服务器同步的策略。这是一个很好的起点,值得一看。

于 2020-10-06T22:53:31.410 回答