【问题标题】:Synchronize Data across multiple occasionally-connected-clients using EventSourcing (NodeJS, MongoDB, JSON)使用 EventSourcing(NodeJS、MongoDB、JSON)跨多个偶尔连接的客户端同步数据
【发布时间】:2017-03-01 01:32:06
【问题描述】:

我在服务器和多个客户端之间实现数据同步时遇到了问题。 我阅读了有关事件溯源的信息,我想用它来完成同步部分。

我知道这不是技术问题,更多的是概念

我只是将所有事件实时发送到服务器,但客户端被设计为不时离线使用。

这是基本概念:

服务器存储每个客户端应该知道的所有事件,它不会重播这些事件来提供数据,因为主要目的是在客户端之间同步事件,使它们能够重播所有本地事件。

客户端拥有一个 JSON 存储,同时保存所有事件从存储/同步的事件中重建所有不同的集合。

由于客户端可以离线修改数据,因此具有一致的同步周期并不重要。考虑到这一点,服务器应在合并不同事件时处理冲突,并在发生冲突时询问特定用户。

所以,对我来说主要问题是确定客户端和服务器之间的差异以避免将所有事件发送到服务器。我也对同步过程的顺序有问题:先推送更改,先拉取更改?

我目前构建的是服务器端的默认 MongoDB 实现,它在我的所有查询中隔离特定用户组的所有文档(目前仅处理身份验证和服务器端数据库工作)。 在客户端上,我围绕 NeDB 存储构建了一个包装器,使我能够拦截所有查询操作以创建和管理每个查询的事件,同时保持默认查询行为不变。我还通过实现由客户端生成并且是文档数据的一部分的自定义 ID 来补偿 neDB 和 MongoDB 的不同 ID 系统,这样重新创建数据库就不会弄乱 ID(同步时,这些 ID应该在所有客户端之间保持一致)。

事件格式如下所示:

{
   type: 'create/update/remove',
   collection: 'CollectionIdentifier',
   target: ?ID, //The global custom ID of the document updated
   data: {}, //The inserted/updated data
   timestamp: '',
   creator: //Some way to identify the author of the change
}

为了节省客户端的一些内存,我会在一定数量的事件上创建快照,这样完全重播所有事件会更有效率。

所以,缩小问题范围:我能够在客户端重放事件,我还能够在客户端和服务器端创建和维护事件,合并事件在服务器端也应该不是问题,使用现有工具复制整个数据库也不是一个选项,因为我只同步数据库的某些部分(甚至不是整个集合,因为文档被分配了它们应该同步的不同组) .

但我遇到的问题是

  • 同步时确定从客户端发送什么事件的过程(避免发送重复事件,甚至所有事件)
  • 确定要发送回客户端的什么事件(避免发送重复事件,甚至是所有事件)
  • 同步事件的正确顺序(推/拉更改)

我想问的另一个问题是,以类似修订的方式将更新直接存储在文档上是否更有效?

如果我的问题不清楚、重复(我发现了一些问题,但它们在我的场景中对我没有帮助)或缺少什么,请发表评论,我会尽我所能保持它很简单,因为我刚刚写下了所有可以帮助您理解这个概念的内容。

提前致谢!

【问题讨论】:

    标签: javascript node.js mongodb synchronization event-sourcing


    【解决方案1】:

    这是一个非常复杂的主题,但我会尝试某种形式的答案。

    看到您的图表,我的第一个反应是考虑分布式数据库如何在它们之间复制数据并在一个节点出现故障时恢复。这通常通过gossiping 完成。

    八卦轮确保数据保持同步。时间戳修订保持在两端按需合并,比如当节点重新连接时,或者只是在给定的时间间隔(通过套接字等发布批量更新)。

    Cassandra 或 Scylla 等数据库引擎每轮合并使用 3 条消息。

    演示:

    节点 A 中的数据

    { id: 1, timestamp: 10, data: { foo: '84' } }
    { id: 2, timestamp: 12, data: { foo: '23' } }
    { id: 3, timestamp: 12, data: { foo: '22' } }
    

    节点 B 中的数据

    { id: 1, timestamp: 11, data: { foo: '50' } }
    { id: 2, timestamp: 11, data: { foo: '31' } }
    { id: 3, timestamp: 8, data: { foo: '32' } }
    

    第 1 步:同步

    它列出了所有文档的 id 和 last upsert 时间戳(随意更改这些数据包的结构,这里我使用详细的 JSON 来更好地说明这个过程)

    Node A -> Node B

    [ { id: 1, timestamp: 10 }, { id: 2, timestamp: 12 }, { id: 3, timestamp: 12 } ]
    

    第 2 步:确认

    收到此数据包后,节点 B 会将收到的时间戳与其自己的时间戳进行比较。对于每个文档,如果它的时间戳较旧,只需将其放在 ACK 有效负载中,如果它较新,则将其与数据一起放置。如果时间戳相同,显然什么也不做。

    Node B -> Node A

    [ { id: 1, timestamp: 11, data: { foo: '50' } }, { id: 2, timestamp: 11 }, { id: 3, timestamp: 8 } ]
    

    第 3 步:ACK2

    如果提供了 ACK 数据,节点 A 会更新其文档,然后将最新的数据发送回节点 B,以获取未提供 ACK 数据的数据。

    Node A -> Node B

    [ { id: 2, timestamp: 12, data: { foo: '23' } }, { id: 3, timestamp: 12, data: { foo: '22' } } ]
    

    这样,两个节点现在都以两种方式合并了最新数据(以防客户端离线工作) - 无需发送所有文档。

    在您的情况下,您的事实来源是您的服务器,但例如,您可以使用 WebRTC 在客户端之间轻松实现对等八卦。

    希望这在某种程度上有所帮助。

    Cassandra training video

    Scylla explanation

    【讨论】:

    • 感谢您的遮阳篷!这是一个非常有趣的方法!它易于实现,并且由于我的服务器控制合并,我可以通过一些默认规则而不是询问用户来解决冲突(如果这导致头痛,我仍然可以实现一些客户端提示)。那么在我的情况下,服务器将是节点 A,并将所有 ID 和时间戳一起发送?当集合变得更大时,可能会有很多数据(即使它只是 ID 和时间戳),但我也可能会找到一个有效的解决方案。
    • 我也喜欢我可以在不同客户端之间实现这一点的想法,这将使我能够更快地为同一网络中的客户端同步。
    • 您可以根据对您有意义的内容为要传输的内容创建业务规则。您可以查看存储在客户端存储中的最新时间戳并将其作为 pre-SYN 步骤发送,以便您获得最新的更改。另一种选择是按主题标记八卦轮次,并在给定时间同步您需要的主题。你决定。
    • 很高兴能帮上忙,我很想知道结果如何 :)
    • 一旦实施,我将在我的用例中更新我的最终概念,因为这很难研究:)
    【解决方案2】:

    我认为避免所有事件顺序和重复问题的最佳解决方案是使用 pull 方法。通过这种方式,每个客户端都会维护其最后导入的事件状态(例如使用跟踪器),并向服务器询问在最后一个事件之后生成的事件。

    一个有趣的问题是检测业务不变量的破坏。为此,您还可以在客户端上存储应用命令的日志,如果发生冲突(事件由其他客户端生成),您可以重试命令日志中的命令执行。您需要这样做,因为某些命令在重新执行后不会成功;例如,客户端在其他用户同时删除该文档后保存该文档。

    【讨论】:

    • 感谢您的遮阳篷!那么,当每个客户端从服务器获取最新状态时,客户端何时或如何将他们的更改推送到服务器?我更愿意完全在客户端解决冲突(或者由更改导致冲突的特定客户端),所以在这种情况下,客户端命令日志是一个很好的细节。
    • 就像在 Git 中一样,你拉,解决冲突然后推
    猜你喜欢
    • 2018-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-04
    • 2012-11-27
    • 2019-11-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多