客户端和服务器数据库的同步

89
我正在寻找一些通用的策略,用于将位于中央服务器上的数据与不总是在线的客户端应用程序进行同步。在我的情况下,我有一个带有SQLite数据库的Android手机应用程序和一个带有MySQL数据库的PHP Web应用程序。 用户将能够在手机应用程序和Web应用程序上添加和编辑信息。我需要确保在一个地方做出的更改反映在所有其他地方,即使手机无法立即与服务器通信。 我不关心如何从手机传输数据到服务器或反之亦然。我提到我的特定技术只是因为我不能使用例如MySQL可用的复制功能等功能。 我知道客户端 - 服务器数据同步问题已经存在了很长时间,想了解有关处理该问题的模式的信息 - 文章,书籍,建议等等。 我想了解处理同步的通用策略,以比较其优劣和权衡。
6个回答

103
你需要先决定一个关于哪一方在冲突更改时被认为是“权威”的总体策略。例如:假设记录#125在1月5日晚上10点在服务器上被更改,同一条记录在其中一台手机上(称之为客户端A)在1月5日晚上11点被更改。上次同步是在1月3日。然后用户在比如说1月8日重新连接。确定需要更改的内容“很容易”,因为客户端和服务器都知道上次同步的日期,所以自上次同步以来任何创建或更新的内容(有关此内容的更多信息请参见下文)都需要进行协调。所以,假设唯一更改的记录是#125。你可以决定其中之一自动“获胜”并覆盖另一个,或者你需要支持一个协调阶段,在该阶段用户可以决定哪个版本(服务器或客户端)是正确的,覆盖另一个。这个决定非常重要,你必须权衡客户端的“角色”。特别是如果不仅存在客户端和服务器之间的潜在冲突,而且不同的客户端可以更改相同的记录。[假设第二个客户端(客户端B)可以修改#125,那么客户端B有可能还会提供另一个版本的相同记录,使之前的冲突解决变得无意义]
关于上面提到的“创建或更新”问题...如果一条记录是在客户端中发起的,如何正确地识别它?
假设您的应用程序管理一个业务联系人列表。如果客户端A说要添加一个新创建的约翰·史密斯,并且服务器上有一条由客户端D昨天创建的约翰·史密斯...您会创建两条记录吗?因为您不能确定它们是否是不同的人?您是否还要要求用户解决此冲突?
客户端是否拥有某些数据的“所有权”?即使客户端B被设置为区域5数据的“权威”,客户端A是否可以修改/创建区域5的记录?(这将使一些冲突解决更容易,但可能在您的情况下不可行)。
总之,主要问题是:
  • 如何定义"身份",考虑到分离的客户端可能在创建新记录之前没有访问过服务器。
  • 无论解决方案多么复杂,以前的情况都可能导致数据重复,因此您必须预见如何定期解决这些问题以及如何通知客户端,他们认为是“记录#675”实际上已经合并/被替代了“记录#543”
  • 决定冲突是否将通过命令解决(例如,“如果自上次同步以来服务器版本已更新,则服务器版本始终优先于客户端”),还是需要手动干预
  • 如果采用命令,尤其是如果您决定客户端具有优先权,您还必须注意如何处理其他尚未同步的客户端,这些客户端可能会有更多的更改。
  • 前面的项目不考虑数据的粒度(为了更简单地描述事物)。只需说,与我的示例中的“记录”级别进行推理相比,您可能会发现记录变化时在字段级别上记录更为适当。或者一次处理一组记录(例如,个人记录+地址记录+联系人记录),将它们的聚合视为某种“元记录”。

参考文献:

  • 当然,关于这个问题的更多信息,请参考维基百科

  • Vdirsyncer作者的简单同步算法

  • OBJC数据同步文章

  • SyncML®:同步和管理移动数据(O'Reilly Safari图书)。

  • 无冲突复制数据类型

  • 乐观复制 YASUSHI SAITO(惠普实验室)和MARC SHAPIRO(微软研究有限公司)- ACM计算调查,第5卷,第N号,2005年3月。

  • Alexander Traud,Juergen Nagler-Ihlein,Frank Kargl和Michael Weber。 2008年。通过重用SyncML进行循环数据同步。在移动数据管理的第9届国际会议(MDM'08)论文集中。 IEEE计算机学会,华盛顿特区,美国,165-172。 DOI=10.1109/MDM.2008.10 http://dx.doi.org/10.1109/MDM.2008.10

  • Lam,F.,Lam,N.和Wong,R。 2002年。移动XML数据的高效同步。在信息和知识管理的第11届国际会议论文集(美国弗吉尼亚州麦克林,2002年11月04日至09日)。 CIKM'02。 ACM,纽约,NY,153-160。 DOI=http://doi.acm.org/10.1145/584792.584820

  • Cunha,P.R。和Maibaum,T.S。 1981年。资源&平衡抽象数据类型+同步-面向消息的编程方法论。在软件工程的第5届国际会议论文集(美国加利福尼亚州圣迭戈,1981年3月9日至12日)。国际软件工程会议。 IEEE出版社,皮斯卡特维,NJ,263-272。

(最后三个来自ACM数字图书馆,不知道您是否是会员或是否可以通过其他渠道获取这些内容)。

来自Dr.Dobbs网站:

  • 使用SQL Server CE和SQL RDA创建应用程序,作者Bill Wagner,2004年5月19日(为桌面和移动PC - Windows/.NET设计应用程序的最佳实践)

来自arxiv.org:

  • 一个无冲突复制的JSON数据类型 - 本文描述了一个JSON CRDT实现(无冲突复制数据类型 - CRDT - 是一类支持并发修改并保证此类并发更新收敛的数据结构家族)。

谢谢您的回答。我非常有兴趣阅读关于解决您所提出问题的常用/可能方案(优缺点、比较)的内容。 - Scott Saunders
我想你已经查过维基百科以及它们链接的内容了,对吧? - p.marino
3
这篇文章提供了关于该问题的非常重要的信息,非常好。有一个遗漏的点:同步已删除的记录。 - Stefan Steinegger
7
我倾向于将“删除”视为“更新”的一个特殊情况,尤其是在这种情况下我倾向于采用“逻辑删除”而不是“物理删除”。因此,对我来说,主服务器或从服务器上的“删除”意味着“特殊布尔值已被翻转”,而不仅仅是其他任何东西。 - p.marino
谢谢。我已经添加了另一篇文章(dr.dobbs)的链接,并将在发现其他内容时更新参考文献。 - p.marino
显示剩余2条评论

11
我建议在每个表中都有一个时间戳列,并且每次插入或更新时,都要更新每行受影响的时间戳值。然后,您需要迭代所有表,检查时间戳是否比目标数据库中的时间戳更新。如果较新,则检查是否需要插入或更新。
观察1:注意物理删除,因为从源数据库中删除了行,您必须在服务器数据库中执行相同操作。您可以通过避免物理删除或记录每个删除的时间戳来解决这个问题。例如:DeletedRows = (id,table_name,pk_column,pk_column_value,timestamp)因此,您必须读取DeletedRows表的所有新行,并使用table_name,pk_column和pk_column_value在服务器上执行删除操作。
观察2:请注意外键约束,因为向与另一个表相关联的表中插入数据可能会失败。在数据同步之前,您应该停用每个外键约束。

4
时钟必须同步。 - tofutim

6
如果有人遇到类似的设计问题,并需要在多个Android设备之间同步更改,我建议检查Google Cloud Messaging for Android(GCM)。
我正在研究一种解决方案,其中在一个客户端上进行的更改必须传播到其他客户端。我刚刚实现了一个概念验证实现(服务器和客户端),它可以完美地工作。
基本上,每个客户端向服务器发送增量更改。例如,资源ID ABCD1234的值从100更改为99。
服务器根据其数据库验证这些增量更改,批准更改(客户端同步)并更新其数据库或拒绝更改(客户端不同步)。
如果服务器批准更改,则通过GCM通知其他客户端(不包括发送增量更改的客户端),并发送携带相同增量更改的多播消息。客户端处理此消息并更新其数据库。
很酷的是,如果这些设备在线,这些更改几乎可以即时传播!!!而且我不需要在这些客户端上实现任何轮询机制。
请记住,如果设备离线时间过长,并且GCM队列中有超过100条消息等待传递,GCM将会丢弃这些消息,并在设备重新联机时发送特殊消息。在这种情况下,客户端必须与服务器进行完全同步。
此外,请查看此教程以开始CGM客户端实现。

5
这篇文章是为正在使用Xamarin框架的开发者准备的(请参见https://stackoverflow.com/questions/40156342/sync-online-offline-data)。使用Azure的离线数据同步是实现这一目标的一种非常简单的方法,因为它允许按需从服务器推送和拉取数据。读操作在本地完成,写操作在按需推送;如果网络连接中断,则将写操作排队,直到恢复连接,然后执行。
实现起来相当简单:
1)在Azure门户中创建一个移动应用程序(您可以在此处免费尝试:https://tryappservice.azure.com/
2)将客户端连接到移动应用程序。https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-xamarin-forms-get-started/ 3)设置本地存储库的代码:
const string path = "localrepository.db";

//Create our azure mobile app client
this.MobileService = new MobileServiceClient("the api address as setup on Mobile app services in azure");

//setup our local sqlite store and initialize a table
var repository = new MobileServiceSQLiteStore(path);

// initialize a Foo table
store.DefineTable<Foo>();

// init repository synchronisation
await this.MobileService.SyncContext.InitializeAsync(repository);
var fooTable = this.MobileService.GetSyncTable<Foo>();

4) 然后推送和拉取您的数据,以确保我们拥有最新的更改:

await this.MobileService.SyncContext.PushAsync();
await this.saleItemsTable.PullAsync("allFoos", fooTable.CreateQuery());

https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-xamarin-forms-get-started-offline-data/


1
我建议您也看一下Symmetricds。它是一个可用于Android系统的SQLite复制库。您可以使用它来同步客户端和服务器数据库,我还建议在服务器上为每个客户单独设置数据库。尝试将所有用户的数据保存在一个MySQL数据库中并不总是最好的选择,特别是如果用户数据将快速增长。

0
让我们把它称为CUDR同步问题(我不喜欢CRUD - 因为创建/更新/删除是写操作,应该成对出现)。
问题也可以从“先离线写入”或“先在线写入”的角度来看。先离线写入方法存在唯一标识符冲突的问题,而且相同事务需要进行多次网络调用,增加了风险(或成本)......
个人认为先在线写入方法更容易管理(因此它将成为单一的真相来源 - 从那里同步所有其他内容)。 先在线写入方法将要求不允许用户首先离线写入 - 他们将通过在线写入获得ok响应来进行离线写入。
他可以首先阅读离线数据,然后在网络可用时从在线获取数据并更新本地数据库,然后更新ui....
避免唯一标识符冲突的一种方法是使用唯一用户ID + 表名或表ID + 行ID(由SQLite生成)的组合,然后与其一起使用同步布尔标志列......但仍然必须先在线完成注册以获得唯一ID,用于生成所有其他ID......这里的问题也将是如果时钟没有同步 - 就像有人上面提到的一样......

进一步说,离线写入的方法在应用程序卸载时会出现问题,所有未上传到在线服务器的数据都将被删除。 - DragonFire
1
创建在时间上是最先的,所以把它们放在最前面是有意义的。读取通常是最常见的操作,因此在 C 之后放置 R 更有意义。删除是记录生命周期中最后发生的事情,因此将它们放在最后是有意义的。然后,哇!这是一个可发音的单词!有什么不喜欢的呢? - iconoclast
1
为什么UUID不能解决唯一标识符的问题? - iconoclast
@iconoclast 你的可能更有意义... - DragonFire

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接