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

我有一种感觉,肯定存在客户端-服务器同步模式。但我完全没能做到。

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

对于这种情况,是否有任何模式/良好的实践,或者如果你不知道任何模式/良好的实践,你会采取什么方法?

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

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

任何想法吗?

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

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

您有几个用例。

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

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

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

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

你真正需要的是操作变换 (OT)。这甚至可以在许多情况下迎合冲突。

这仍然是一个活跃的研究领域,但已经有各种OT算法的实现。我从事这方面的研究已经有好几年了,所以如果你对这条路线感兴趣,请告诉我,我很乐意为你提供相关资源。

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

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

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

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

基于对现有的现成解决方案的回顾,我们可以勾画出几个主要的同步类,这些同步对象的粒度不同:

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

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

大约8年前,我为一个应用程序构建了一个这样的系统,我可以分享一些随着应用程序使用量的增长而发展的方法。

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

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

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

下一个问题是历史表比整个数据库的其余部分大10倍左右。这使得存储成本很高,而且对历史表的任何维护都可能是痛苦的。保留整个表允许用户回滚之前的任何更改,但这开始感觉有点过头了。因此,我在同步过程中添加了一个例程,如果设备上次下载的历史项不再存在于历史表中,服务器就不会给它最近的历史项,而是给它一个包含该帐户所有数据的文件。然后我添加了一个cronjob来删除超过90天的历史项。这意味着用户仍然可以回滚小于90天的更改,如果他们至少每90天同步一次,更新将像以前一样递增。但如果等待时间超过90天,该应用程序将取代整个数据库。

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

如果我今天重新开始,我将跳过ID冲突检查,只以一个足以消除冲突的键长度为目标,并进行某种错误检查以防万一。(YouTube似乎使用了11个字符的随机id。)历史表和最近更新的增量下载或需要时的完整下载的组合运行良好。

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

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