【问题标题】:Synchronize two lists of objects同步两个对象列表
【发布时间】:2014-10-27 14:19:50
【问题描述】:
问题
我有两个对象列表。每个对象包含以下内容:
-
GUID(允许确定对象是否相同——来自业务
观点)
-
Timestamp(每次更新到当前 UTC
对象已更改)
-
Version(正整数;每次递增
对象已更改)
-
Deleted(布尔标志;改为“true”
实际对象删除)
-
Data(一些有用的有效载荷)
- 任何其他字段(如果需要)
接下来,我需要根据这些规则同步两个列表:
- 如果带有一些
GUID 的对象仅出现在一个列表中,则应将其复制到另一个列表中
- 如果两个列表中都出现了带有一些
GUID 的对象,则应将具有较少Version 的实例替换为具有较大Version 的实例(如果版本相同,则无需执行)
现实世界的要求:
- 每个列表有 50k+ 个对象,每个对象大约 1 Kb
- 列表放置在通过 Internet 连接的不同机器上(例如,移动应用程序和远程服务器),因此,算法不应过多浪费流量或 CPU
- 大多数时间(例如,96%)列表在同步过程之前已经同步,因此,算法应该以最小的努力来确定它
- 如果有任何差异,大多数情况下它们都很小(更改/添加了 3-5 个对象)
- 如果一个列表为空(而另一个列表仍有 50k+ 项),则应继续进行
解决方案 #1(当前已实施)
- 客户端存储上次同步成功时间(比如
T)
- 要求所有具有
Timestamp > T 的对象的两个列表(即最近修改;在生产中它是... > (T - day) 以获得更好的鲁棒性)
- 这些最近修改的对象列表是天真的同步的:
- 仅在第一个列表中显示的项目将保存到第二个列表中
- 仅在第二个列表中显示的项目会保存到第一个列表中
- 与其他项目的
Version 进行比较并保存到专用列表(如果需要)
过程:
缺点:
- 取决于
T,这使得算法很脆弱:同步最新更新很容易,但很难确保列表完全同步(使用最少的T,如 1970-01-01只是挂起同步过程)
我的问题:
- 是否有任何通用/最佳实践/经过验证的方法来同步对象列表?
- 对于我的情况,有 [比 #1] 更好的解决方案吗?
附:已查看,而非重复:
【问题讨论】:
标签:
algorithm
list
data-synchronization
【解决方案1】:
总结
所有答案都有一些有价值的点。总而言之,这是我正在寻找的编译答案,基于最终实现的工作同步系统:
一般情况下,使用Merkle trees。它们在比较大量数据方面非常有效。
如果可以,每次需要时从头开始重建哈希树。
检查重建哈希树所需的时间。很可能它非常快(例如,在我的情况下,在 Nexus 4 上为 20k 个项目重建树需要约 2 秒:从 DB 获取数据需要 1.8 秒 + 构建树需要 0.2 秒;服务器的执行速度要快约 20 倍),所以你不要不需要将树存储在数据库中并在数据更改时对其进行维护(我的第一次尝试是仅重建相关分支——实现起来并不太复杂,但非常脆弱)。
不过,如果没有进行任何数据修改,缓存和重用树是可以的。一旦发生修改,使整个缓存失效。
技术细节
- GUID 长度为 32 个字符,没有任何连字符/大括号,小写;
- 我使用高度为 4 的 16 叉树,其中每个分支都与 GUID 的字符相关。它可以实现为实际的树或地图:
-
0000 →(具有 GUID 0000* 的项目的哈希)
-
0001 →(带有 GUID 0001* 的项目的哈希)
- ...
-
ffff →(具有 GUID ffff* 的项目的哈希);
-
000 →(散列的散列 000_)
- ...
-
00 →(散列的散列 00_)
- ...
- () →(根哈希,即哈希的哈希
_)
因此,这棵树有 65536 个叶子,需要 2 Mb 的内存;每片叶子覆盖〜N / 65536个数据项。二叉树在内存方面的效率会提高 2 倍,但实现起来更难。
可能的改进
- 实现在没有负载的情况下传输
(GUID, Version) 对以用于轻量级请求。
【解决方案2】:
想到两个建议,第一个可能是您已经在做的事情:
1) 不要发送时间戳 > T 的整个项目列表。相反,发送时间戳 > T 的对象的 (UUID, Version) 元组列表。然后另一方可以确定需要更新哪些对象从此。将这些 UUID 发回以请求实际对象。这样可以避免在时间戳 > T 的情况下发送完整的对象,但在另一端已经更新(或已经存在最新版本)。
2) 不要一次处理完整列表,而是分块处理,即先同步 10%,然后再同步下 10%,以此类推,以避免一次传输过多数据以进行大同步(并允许重新启动点如果连接中断)。这可以通过例如从校验和等于 1 模 10 的所有 UUID 开始,然后是 1 模 10 等。
另一种可能性是主动同步,例如异步发布机会,可能通过 UCP(相对于 TCP 不可靠)。当您需要最新信息时,您仍然需要同步,但很可能大部分信息都是最新的。
【解决方案3】:
您需要存储的不是上次同步的时间,而是上次同步时对象的状态(例如对象数据的哈希值)。然后将每个列表与存储的列表进行比较,并找出每一侧发生了哪些对象变化。
这比依赖时间要可靠得多,因为时间要求双方都有同步的计时器来提供精确的时间(大多数系统并非如此)。出于同样的原因,您基于时间 + 版本检测更改的想法可能比最初看起来更容易出错。
此外,您最初不会传输对象数据,而只会传输 GUID。
顺便说一句,我们已经制作了一个框架(免费提供源代码),可以准确解决您的问题。我没有提供链接,因为一些有才华的人会抱怨。