客户端 - 服务器同步模式/算法?

时间:2009-01-05 13:26:04

标签: client-server data-synchronization

我觉得必须有客户端 - 服务器同步模式。但我完全没有谷歌一个。

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

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

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

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

有什么想法吗?

7 个答案:

答案 0 :(得分:82)

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

您有几个用例。

  • 同步更改。您的更改日志(或增量历史记录)方法看起来很好。客户端将其增量发送到服务器;服务器整合并将增量分配给客户端。这是典型的情况。数据库将此称为“事务复制”。

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

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

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

答案 1 :(得分:25)

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

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

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

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

所以,我们抓住了我们对这篇文章的了解,我认为这对于对主题感兴趣的每个人都非常有用=>基于核心数据的iOS应用程序中的数据同步(http://blog.denivip.ru/index.php/2014/04/data-syncing-in-core-data-based-ios-apps/?lang=en

答案 2 :(得分:23)

你真正需要的是Operational Transform(OT)。在许多情况下,这甚至可以解决冲突。

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

答案 3 :(得分:12)

问题不是很明确,但如果我是你,我会调查optimistic locking。 它可以使用服务器为每条记录返回的序列号来实现。当客户端尝试保存记录时,它将包含从服务器接收的序列号。如果序列号与接收到更新时数据库中的序列号匹配,则允许更新并且序列号递增。如果序列号不匹配,则不允许更新。

答案 4 :(得分:3)

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

我首先将任何设备上的所有更改(插入,更新或删除)记录到“历史记录”表中。因此,例如,如果有人在“联系人”表中更改了他们的电话号码,则系统将编辑contact.phone字段,并添加具有action = update,field = phone,record = [contact 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冲突检查,而只瞄准足以消除冲突的密钥长度,并进行某种错误检查以防万一。但是,历史记录表以及最近更新的增量下载或需要时进行的完整下载的组合一直运行良好。

答案 5 :(得分:0)

对于delta(更改)同步,您可以使用pubsub模式将更改发布回所有订阅的客户端,pusher等服务可以执行此操作。

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

答案 6 :(得分:0)

此页面清楚地描述了大多数数据同步情况,包括模式和示例代码:Data Synchronization: Patterns, Tools, & Techniques

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