同步两个对象列表

时间:2014-10-27 14:19:51

标签: algorithm list data-synchronization

问题

我有两个对象列表。每个对象包含以下内容:

  • GUID(允许确定对象是否相同 - 来自业务 观点)
  • Timestamp(每次更新当前UTC 对象改变了)
  • Version(正整数;每次递增 对象改变了)
  • Deleted(布尔标志;改为切换为“true” 实际对象删除)
  • Data(一些有用的有效载荷)
  • 任何其他字段,如果需要

接下来,我需要根据这些规则同步两个列表:

  • 如果某个GUID的对象仅在一个列表中显示,则应将其复制到另一个列表
  • 如果两个列表中都显示了一些GUID的对象,那么Version较少的实例应该替换为Version更大的实例(如果版本相同则无关)

现实世界的要求:

  • 每个列表都有50k +对象,每个对象大约是1 Kb
  • 列表放在通过互联网连接的不同机器上(例如,移动应用程序和远程服务器),因此,算法不应浪费流量或CPU太多
  • 大部分时间(比方说,96%)列表已经在同步过程之前同步,因此,算法应该用最少的努力来确定它
  • 如果存在任何差异,大部分时间它们都很小(3-5个对象已更改/添加)
  • 如果一个列表为空(并且其他列表仍有50k +项目)
  • ,则应该继续

解决方案#1(目前已实施)

  1. 客户端存储上次同步成功时间(比如T
  2. 要求两个列表都包含Timestamp>的所有对象。 T(即最近修改过的;在制作过程中......>(T - )以获得更好的稳健性)
  3. 这些最近修改过的对象列表是天真地同步的:
    • 仅在第一个列表中显示的项目将保存到第二个列表
    • 仅在第二个列表中显示的项目将保存到第一个列表
    • 其他项目的Version被比较并保存到专有列表(如果需要)
  4. PROCS:

    • 适用于小改动
    • 几乎符合要求

    缺点:

    • 取决于T,这会使算法变得脆弱:很容易同步最后的更新,但很难确保列表完全同步(使用像{1970}这样的最小T -01-01只挂起同步过程)

    我的问题:

    1. 是否有任何通用/最佳实践/证明的方法来同步对象列表?
    2. 我的案例有没有比#1更好的解决方案?
    3. P.S。已经查看过,不重复:

3 个答案:

答案 0 :(得分:3)

摘要

所有答案都有一些值得的。总而言之,这是我正在寻找的编译答案,基于最终实现的工作同步系统:

  1. 一般来说,使用Merkle trees 。它们在比较大量数据方面非常有效。

  2. 如果可以,每次需要时都从头开始重建哈希树。 检查重建哈希树所需的时间。最有可能的是它非常快(例如,在我的情况下,Nexus 4重建树的20k项目需要~2秒:从DB获取数据需要1.8秒+构建树需要0.2秒;服务器执行速度提高约20倍),因此,您不需要将树存储在数据库中并在数据更改时进行维护(我的第一次尝试只是重建相关的分支 - 它实现起来并不复杂,但非常脆弱)。

  3. 然而,如果没有进行任何数据修改,缓存和重用树就可以了。修改发生后,使整个缓存无效。

  4. 技术细节

    • GUID是32个字符长,没有任何连字符/大括号,小写;
    • 我使用高度为4的16-ary树,其中每个分支与GUID的char相关。它可以实现为实际树或地图:
      • 0000→(带有GUID 0000*的项目的哈希值)
      • 0001→(带有GUID 0001*的项目的哈希值)
      • ...
      • ffff→(带有GUID ffff*的项目的哈希);
      • 000→(哈希哈希000_
      • ...
      • 00→(哈希哈希00_
      • ...
      • ()→(根哈希,即散列哈希_

    因此,树有65536个叶子,需要2 Mb的内存;每片叶子覆盖~N / 65536个数据项。二进制树在内存方面的效率提高了2倍,但实现起来却更难。

    • 我必须实现这些方法:

      • getHash() - 返回根哈希值;用于初步检查(如上所述, 96%,这是我们需要测试的全部内容;
      • getHashChildren(x) - 返回哈希值x_列表(最多16个);用于有效的单一请求发现数据差异;
      • findByIdPrefix(x) - 返回GUID为x*的项目,x必须包含4个字符;用于请求叶子项目;
      • count(x) - 返回GUID为x*的商品数量;当合理地小时,我们可以逐个检查树检查并转移一堆项目;
    • 就每个分支传输少量数据进行同步而言,它非常敏感(您可以随时检查进度)+对于意外终止非常可靠(例如,由于网络如果需要,可以从最后一点轻松重启。

    • 重要:有时您会遇到有冲突的状态:{version_1 = version_2,但是hash_1!= hash_2}:在这种情况下,你必须做出一些决定(可能是用户的帮助或比较时间戳作为最后的手段)并用另一个重写某个项目来解决冲突,否则你&# 39; ll最终得到未同步和不可同步的哈希树。

    可能的改进

    • 为轻量级请求实现无负载的传输(GUID, Version)对。

答案 1 :(得分:1)

我想到了两个建议,第一个建议可能是你已经做过的事情:

1)不要发送带有时间戳的整个项目列表>相反,发送带有时间戳>的对象的(UUID,Version)元组列表。然后另一方可以找出需要更新的对象。发回那些UUID以请求实​​际对象。这样可以避免在有时间戳的情况下发送完整对象。 T,但在另一方面已经更新(或已经有最新版本)。

2)不要立即处理完整列表,而是以块为单位,即首先同步10%,然后是下一个10%等,以避免为大同步一次传输过多数据(并允许如果连接断开,则重新启动点)。这可以通过例如从所有UUID开始,校验和相当于1模10,然后1模10等。

另一种可能性是主动同步,例如异步发布机会,可能通过UCP(不可靠而不是TCP)。当您需要当前信息时,您仍然需要同步,但大多数情况都是最新的。

答案 2 :(得分:1)

您需要在上次同步时不存储上次同步的时间,而是存储对象的状态(例如,对象数据的哈希)。然后,将每个列表与存储的列表进行比较,找到每侧更改的对象。

这比依靠时间更可靠,因为时间要求双方都有同步计时器,这给出了精确的时间(在大多数系统中并非如此)。出于同样的原因,您基于时间+版本检测更改的想法可能比最初看起来更容易出错。

此外,您最初不会传输对象数据,只会传输GUID。

顺便说一下,我们已经制作了一个框架(免费提供源代码),可以解决您的问题。我没有给出这个链接,因为一些有才华的人会抱怨。