同步两个对象列表

Nik*_*sik 5 algorithm list data-synchronization

问题

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

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

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

  • 如果某些对象GUID仅出现在一个列表中,则应将其复制到另一个列表中
  • 如果GUID两个列表中都出现了一些对象,Version则应将具有较少的实例替换为具有较大的实例Version(如果版本相同,则无关紧要)

实际要求:

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

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

  1. 客户端存储上次同步成功的时间(比如T
  2. 两个列表都要求所有具有Timestamp> T(即最近修改过的对象;在生产中它是 ... > ( T- day ) 以获得更好的健壮性)
  3. 这些最近修改的对象列表是天真同步的:
    • 仅出现在第一个列表中的项目将保存到第二个列表中
    • 仅出现在第二个列表中的项目将保存到第一个列表中
    • 其他项目将它们的Version's 进行比较并保存到专用列表(如果需要)

过程:

  • 小改动效果很好
  • 几乎符合要求

缺点:

  • 取决于T,这使算法变得脆弱:同步上次更新很容易,但很难确保列表完全同步(使用T1970-01-01 之类的最小化只是挂起同步过程)

我的问题:

  1. 是否有任何通用/最佳实践/经过验证的方法来同步对象列表?
  2. 对于我的案例,是否有更好的 [比 #1] 解决方案?

PS 已查看,不重复:

Nik*_*sik 5

概括

\n\n

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

\n\n
    \n
  1. 一般来说,使用默克尔树。它们在比较大量数据方面非常高效。

  2. \n
  3. 如果可以的话,每次需要时都从头开始重建哈希树。\n检查重建哈希树所需的时间。最有可能的是它非常快(例如,在我的例子中,在 Nexus 4 上重建 20k 项的树大约需要 2 秒:从数据库获取数据需要 1.8 秒 + 构建树需要 0.2 秒;服务器的执行速度大约快 20 倍),所以您不需要将树存储在数据库中并在数据更改时维护它(我的第一次尝试是仅重建相关分支 \xe2\x80\x94 它实现起来不太复杂,但非常脆弱)。

  4. \n
  5. 尽管如此,如果根本没有进行数据修改,缓存和重用树是可以的。一旦发生修改,使整个缓存失效。

  6. \n
\n\n

技术细节

\n\n
    \n
  • GUID 长度为 32 个字符,不带任何连字符/大括号,小写;
  • \n
  • 我使用高度为 4 的 16 叉树,其中每个分支都与 GUID 的字符相关。它可以被实现为实际的树或地图:\n
      \n
    • 0000\xe2\x86\x92 (具有 GUID 的项目的哈希值0000*
    • \n
    • 0001\xe2\x86\x92 (具有 GUID 的项目的哈希值0001*
    • \n
    • ...
    • \n
    • ffff\xe2\x86\x92 (具有 GUID 的项目的哈希值ffff*);
    • \n
    • 000\xe2\x86\x92 (哈希值的哈希值000_
    • \n
    • ...
    • \n
    • 00\xe2\x86\x92 (哈希值的哈希值00_
    • \n
    • ...
    • \n
    • () \xe2\x86\x92 (根哈希,即哈希的哈希_
    • \n
  • \n
\n\n

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

\n\n
    \n
  • 我必须实现这些方法:

    \n\n
      \n
    • getHash()\xe2\x80\x94 返回根哈希值;用于主要检查(如上所述,\nin 96% 这就是我们需要测试的全部);
    • \n
    • getHashChildren(x)\xe2\x80\x94 返回哈希值列表x_(最多 16 个);用于有效的、单一请求发现数据差异;
    • \n
    • findByIdPrefix(x)\xe2\x80\x94 返回带有 GUID 的项目x*,x 必须恰好包含 4 个字符;用于请求叶子项目;
    • \n
    • count(x)\xe2\x80\x94 返回具有 GUID 的项目数x*;当相当小时,我们可以取消逐个分支地检查树并通过单个请求传输一堆项目;
    • \n
  • \n
  • 就每个分支传输少量数据而言,它的响应非常灵敏(您可以随时检查进度)+对于意外终止(例如,由于网络故障)非常稳健,并且可以轻松地从如果需要的话最后一点。

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

可能的改进

\n\n
    \n
  • (GUID, Version)针对轻量化请求实现无负载的传输对。
  • \n
\n