流星在众多客户之间共享大量收藏品时的效率如何?


100

想象以下情况:

  • 1,000个客户端连接到显示“ Somestuff”集合内容的Meteor页面。

  • “ Somestuff”是一个包含1,000个项目的集合。

  • 有人将新项目插入“ Somestuff”集合

会发生什么:

  • Meteor.Collection客户端上的所有都会更新,即将插入转发给所有客户端(这意味着向1000个客户端发送了一条插入消息)

在服务器的CPU方面,确定需要更新哪个客户端的成本是多少?

仅将插入的值而不是整个列表转发给客户是否准确?

这在现实生活中如何运作?有这样规模的基准或实验吗?

Answers:


119

简短的答案是,只有新数据通过网络发送。运作方式如下。

Meteor服务器的三个重要部分用于管理订阅:publish函数,它定义订阅提供哪些数据的逻辑;的蒙戈驱动器,其手表更改数据库中; 与合并盒,它结合了所有客户的活动订阅,然后再将它们通过网络客户端。

发布功能

每当Meteor客户端订阅一个集合时,服务器都会运行 publish函数。发布功能的工作是找出其客户端应具有的文档集,并将每个文档属性发送到合并框中。它为每个新的订阅客户端运行一次。您可以将任何所需的JavaScript放入发布函数中,例如使用任意复杂的访问控制this.userId。publish函数通过调用将数据发送到合并框中this.addedthis.changedthis.removed。有关更多详细信息,请参见 完整的发布文档

大多数的发布功能,没有淤泥与周围的低级别 addedchangedremovedAPI,虽然。如果发布函数返回一个蒙戈光标,流星服务器自动连接蒙戈驱动器的输出(insertupdate,和removed回调)到合并框的输入(this.addedthis.changedthis.removed)。您可以在发布功能中预先进行所有权限检查,然后将数据库驱动程序直接连接到合并框,而无需任何用户代码,这非常干净。启用自动发布功能后,即使隐藏了这一点,服务器也会自动为每个集合中的所有文档设置查询,并将其推入合并框。

另一方面,您不仅限于发布数据库查询。例如,您可以编写发布功能,从发布商内部的设备读取GPS位置Meteor.setInterval,或从另一个Web服务轮询旧式REST API。在这种情况下,你会通过调用低级别发出更改合并盒addedchangedremovedDDP API。

Mongo司机

蒙戈司机的工作是看Mongo的数据库更改到现场查询。这些查询会连续运行,并通过调用added,随着结果的更改返回更新。removedchanged回调。

Mongo不是实时数据库。因此,驱动程序轮询。它为每个活动的实时查询保留最后查询结果的内存副本。在每个轮询周期中,它会将新结果与先前保存的结果进行比较,计算出的最小集addedremovedchanged 事件描述的差别。如果多个调用者为同一个实时查询注册了回调,则驱动程序仅监视该查询的一个副本,并以相同的结果调用每个已注册的回调。

每次服务器更新集合时,驱动程序都会重新计算该集合上的每个实时查询(未来版本的Meteor将公开一个扩展API,以限制更新时重新计算哪些实时查询。)驱动程序还会在10秒计时器上轮询每个实时查询,以查询捕获绕过Meteor服务器的带外数据库更新。

合并框

在的工作合并盒是将结果合并(addedchangedremoved 所有的客户的主动发布功能的调用)到一个单一的数据流。每个连接的客户端都有一个合并框。它拥有客户端minimongo缓存的完整副本。

在您只有一个订阅的示例中,合并框本质上是传递。但是,更复杂的应用程序可以具有多个可能重叠的订阅。如果两个订阅都在同一文档上设置了相同的属性,则合并框将决定哪个值具有优先级,并将其仅发送给客户端。我们尚未公开用于设置订阅优先级的API。目前,优先级由客户端订阅数据集的顺序确定。客户端进行的第一个订阅具有最高优先级,第二个订阅具有第二高的优先级,依此类推。

因为合并框保留了客户端的状态,所以无论发布功能如何发送,合并框都可以发送最少的数据以使每个客户端保持最新状态。

更新时会发生什么

因此,现在我们为您的方案奠定了基础。

我们有1,000个关联的客户。每个订阅了相同的实时Mongo查询(Somestuff.find({}))。由于每个客户端的查询都相同,因此驱动程序仅运行一个实时查询。有1,000个活动合并框。并且每个客户的发布功能在实时查询中注册了addedchangedremoved,该实时查询输入到合并框之一中。没有其他任何连接到合并框。

首先是Mongo司机。当一个客户将一个新文档插入时Somestuff,它将触发重新计算。Mongo驱动程序针对所有文档重新运行查询Somestuff,将结果与内存中的前一个结果进行比较,发现有一个新文档,并分别调用1,000个注册的insert回调。

接下来,发布功能。这里几乎没有发生什么:1,000个insert回调中的每个回调都通过调用将数据推送到合并框中added

最后,每个合并框都会根据其客户端缓存的内存副本检查这些新属性。在每种情况下,它都会发现这些值尚未出现在客户端上,并且不会遮盖现有的值。因此,合并框DATA在与其客户端的SockJS连接上发出DDP 消息,并更新其服务器端内存中副本。

CPU总成本是进行一个Mongo查询的成本,再加上1,000个合并框的成本,这些合并框用于检查其客户端的状态并构造新的DDP消息有效负载。流经网络的唯一数据是发送到1,000个客户端中的每个客户端的一个JSON对象(对应于数据库中的新文档),以及从原始插入到客户端的一条RPC消息到服务器

最佳化

这是我们绝对计划的。

  • 更高效的Mongo驱动程序。我们 在0.5.1中优化了驱动程序,以使每个不同的查询仅运行一个观察者。

  • 并非每个数据库更改都应触发重新计算查询。我们可以进行一些自动化的改进,但是最好的方法是让开发人员指定需要重新运行哪些查询的API。例如,对于开发人员来说显而易见的是,将消息插入一个聊天室不应使对第二个房间中的消息的实时查询无效。

  • Mongo驱动程序,发布功能和合并框不需要在同一进程中甚至在同一台机器上运行。一些应用程序运行复杂的实时查询,并且需要更多的CPU来监视数据库。其他人只有几个截然不同的查询(想象一个博客引擎),但是可能有许多连接的客户端-这些需要更多的CPU用于合并框。分离这些组件将使我们能够独立缩放每个组件。

  • 许多数据库支持在更新行时触发并提供旧行和新行的触发器。使用该功能,数据库驱动程序可以注册触发器,而不用轮询更改。


关于如何使用Meteor.publish发布非光标数据有任何示例吗?如答案中提到的传统rest API的结果?
Tony

@Tony:在文档中。查看房间计数示例。
Mitar

值得注意的是,在0.7、0.7.1、0.7.2版本中,Meteor切换到OpLog Observe Driver进行大多数查询(异常是skip$near并且$where包含查询),这在CPU负载,网络带宽上效率更高,并允许扩展应用程序服务器。
imslavko 2014年

如果不是每个用户都看到相同的数据该怎么办。1.他们订阅了不同的主题.2。它们具有不同的角色,因此在同一主要主题内,有些消息不应该传递给它们。
tgkprog 2014年

@debergalis关于缓存失效,也许你会发现,从我的论文的想法vanisoft.pl/~lopuszanski/public/cache_invalidation.pdf值得的
qbolec

29

从我的经验来看,从0.7.0.1版本开始,在Meteor中与许多客户共享共享巨大的收藏集实际上是行不通的。我将尽力解释原因。

如上述文章以及https://github.com/meteor/meteor/issues/1821中所述,流星服务器必须在合并框中为每个客户端保留已发布数据的副本。这是使Meteor魔术发生的原因,而且还导致任何大型共享数据库被重复保存在节点进程的内存中。即使在对静态集合使用可能的优化时(如中)(是否有办法告诉流星集合是静态的(永远不会改变)?),我们也遇到了Node进程的CPU和内存使用方面的巨大问题。

在我们的案例中,我们向每个客户端发布了1.5万个文档,这些文档是完全静态的。问题在于,在连接时将这些文档复制到客户端的合并框(在内存中)基本上会使Node进程在100%CPU上占用将近一秒钟的时间,并导致大量额外的内存使用。这本质上是不可扩展的,因为任何连接的客户端都将使服务器瘫痪(同时连接将相互阻塞),并且内存使用量将随着客户端数量的增加而线性增加。在我们的案例中,即使传输的原始数据只有约5MB ,每个客户端也造成了约60MB的内存使用。

在我们的例子中,因为集合是静态的,所以我们通过将所有文档作为.json文件发送(由nginx压缩)并将其加载到匿名集合中,从而解决了这个问题,从而仅传输了约1MB的数据,而没有额外的CPU或节点进程中的内存,加载时间更快。此集合上的所有操作都是通过使用_id服务器上较小的出版物中的s 完成的,从而保留了Meteor的大部分优点。这使该应用程序可以扩展到更多客户。此外,由于我们的应用程序大部分是只读的,因此,由于每个Node实例都是单线程的,因此通过在Nginx后面运行多个Meteor实例并进行负载平衡(尽管使用单个Mongo),进一步提高了可伸缩性。

但是,在多个客户端之间共享大型可写集合的问题是Meteor需要解决的工程问题。可能有比为每个客户端保留所有内容的副本更好的方法,但是这需要认真思考一下分布式系统问题。当前大量使用CPU和内存的问题无法解决。


@Harry oplog在这种情况下无关紧要;数据是静态的。
Andrew Mao

为什么不对服务器端minimongo副本进行比较?也许这一切都在1.0中更改了?我的意思是通常它们是我希望的一样,即使它回调的功能也将是相似的(如果我遵循的是,该功能也会存储在其中,并且可能会有所不同。)
MistereeDevlord 2014年

@MistereeDevlord更改的区别和客户端数据的缓存现在是分开的。即使每个人都具有相同的数据,并且只需要一个差异,每个客户端的缓存也会有所不同,因为服务器无法完全相同地对待它们。相对于现有实现,这绝对可以做得更好。
Andrew Mao

@AndrewMao如何将压缩文件发送给客户端时确保其安全,即只有登录的客户端才能访问它?
FullStack

4

您可以用来回答此问题的实验:

  1. 安装测试流星: meteor create --example todos
  2. 在Webkit检查器(WKI)下运行它。
  3. 检查内容 跨线路移动 XHR消息。
  4. 请注意,整个集合不会在电线上移动。

有关如何使用WKI的提示,请查看本文。它有点过时了,但仍然有效,尤其是对于这个问题。



By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.