草庐IT

javascript - 使用 EventSourcing(NodeJS、MongoDB、JSON)跨多个偶尔连接的客户端同步数据

coder 2023-05-30 原文

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

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

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

这是基本概念:

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

客户端有一个 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
}

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

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

但我遇到的问题是:

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

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

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

提前致谢!

最佳答案

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

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

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

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

演示:

Node A 中的数据

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

Node 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 步:确认

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

Node B -> Node A

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

第 3 步:ACK2

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

Node A -> Node B

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

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

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

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

Cassandra training video

Scylla explanation

关于javascript - 使用 EventSourcing(NodeJS、MongoDB、JSON)跨多个偶尔连接的客户端同步数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42506404/

有关javascript - 使用 EventSourcing(NodeJS、MongoDB、JSON)跨多个偶尔连接的客户端同步数据的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐