简而言之,只有新数据会通过网络发送。以下是其工作原理。
Meteor服务器有三个重要的部分来管理订阅:发布函数(publish function)定义订阅提供的数据逻辑;Mongo驱动程序(Mongo driver)监视数据库更改;合并盒子(merge box)将客户端的所有活动订阅组合并通过网络发送给客户端。
发布函数
每次Meteor客户端订阅一个集合时,服务器都会运行一个发布函数。发布函数的工作是找出其客户端应该拥有的文档集,并将每个文档属性发送到合并盒子中。它为每个新的订阅客户端运行一次。您可以在发布函数中放置任何JavaScript代码,例如使用this.userId
进行任意复杂的访问控制。发布函数通过调用this.added
、this.changed
和this.removed
将数据发送到合并盒子中。请参见完整的发布文档以获取更多详细信息。
大多数发布函数不需要涉及低级的
added
、
changed
和
removed
API。如果一个发布函数返回一个Mongo游标,Meteor服务器会自动将Mongo驱动程序的输出(
insert
、
update
和
removed
回调)连接到合并框的输入(
this.added
、
this.changed
和
this.removed
)。这很棒,你可以在发布函数中提前进行所有权限检查,然后直接将数据库驱动程序连接到合并框,而不需要任何用户代码的干扰。当自动发布被打开时,甚至这一小部分也会被隐藏:服务器会自动设置一个查询来获取每个集合中的所有文档,并将它们推送到合并框中。
另一方面,您不仅限于发布数据库查询。例如,您可以编写一个发布函数,从
Meteor.setInterval
内部的设备读取GPS位置,或者从另一个Web服务中轮询遗留REST API。在这些情况下,您将通过调用低级别的
added
、
changed
和
removed
DDP API向合并框发出更改。
Mongo驱动程序
Mongo驱动程序的工作是监视Mongo数据库以查看实时查询的更改。这些查询持续运行,并通过调用
added
、
removed
和
changed
回调返回更新的结果。
Mongo不是一个实时数据库,因此驱动程序会进行轮询。它在内存中保留每个活动查询的最后一个查询结果副本。在每个轮询周期中,它将新结果与先前保存的结果进行比较,计算描述差异的added
、removed
和changed
事件的最小集合。如果多个调用者为同一活动查询注册回调,则驱动程序只监视查询的一个副本,并使用相同的结果调用每个已注册的回调。
每次服务器更新集合时,驱动程序会重新计算该集合上的每个实时查询(Meteor的未来版本将公开一个扩展API,用于限制在更新时重新计算哪些实时查询)。驱动程序还会定期轮询每个实时查询以捕获绕过Meteor服务器而对数据库进行的更新操作。
合并盒子
合并盒子的工作是将客户端的所有活动发布函数的结果(added
、changed
和removed
调用)合并成单个数据流。每个连接的客户端都有一个合并盒子。它保存了客户端的minimongo缓存的完整副本。
在您的例子中,只有一个订阅时,合并框本质上是一个传递。但更复杂的应用程序可能有多个订阅,这些订阅可能会重叠。如果两个订阅都在同一文档上设置了相同的属性,则合并框决定哪个值具有优先权,并仅将其发送给客户端。我们尚未公开设置订阅优先级的API。目前,优先级由客户端订阅数据集的顺序确定。客户端进行的第一个订阅具有最高优先级,第二个订阅次之,依此类推。
由于合并框保存客户端的状态,因此它可以发送最少量的数据以保持每个客户端的最新状态,无论发布函数提供了什么数据。
更新时发生了什么
现在,我们已经为您的场景做好了准备。
我们有1,000个连接的客户端。每个客户端都订阅了相同的实时Mongo查询(
Somestuff.find({})
)。由于每个客户端的查询都相同,因此驱动程序仅运行一个实时查询。有1,000个活动的合并框。每个客户端的发布函数在其中一个合并框中注册了
added
、
changed
和
removed
,这些函数会将实时查询的结果馈送到合并框中。除此之外,没有其他内容连接到合并框上。
首先是Mongo驱动程序。当其中一个客户端向
Somestuff
插入新文档时,它会触发重新计算。Mongo驱动程序会重新运行
Somestuff
中所有文档的查询,将结果与内存中的上一个结果进行比较,发现有一个新文档,并调用每个已注册的1,000个
insert
回调函数。
接下来是发布函数。这里几乎没有什么事情要发生:每个1,000个
insert
回调函数通过调用
added
将数据推送到合并框中。
最后,每个合并框都会将这些新属性与其客户端缓存的内存副本进行比较。在每种情况下,它发现这些值尚未出现在客户端上,并且不会掩盖现有值。因此,合并框在SockJS连接上向其客户端发出DDP DATA
消息,并更新其服务器端内存副本。
总CPU成本是对一个Mongo查询进行差异化的成本,加上1,000个合并框检查其客户端状态并构造新的DDP消息有效负载的成本。通过线路传输的唯一数据是发送到每个1,000个客户端的单个JSON对象,对应于数据库中的新文档,以及从进行原始插入的客户端向服务器发送的一个RPC消息。
优化
以下是我们已经计划好的内容。
更高效的Mongo驱动程序。我们在0.5.1中优化了驱动程序,使其每个不同查询只运行一个观察器。
并非每个数据库更改都应该触发查询的重新计算。我们可以进行一些自动化改进,但最好的方法是提供API让开发人员指定哪些查询需要重新运行。例如,对于开发人员而言,将消息插入一个聊天室显然不应该使第二个房间的实时查询失效。
Mongo驱动程序、发布函数和合并盒子不需要在同一进程中运行,甚至不需要在同一台机器上运行。有些应用程序运行复杂的实时查询,并需要更多的CPU来监视数据库。其他应用程序只有几个不同的查询(比如博客引擎),但可能有很多连接的客户端——这些需要更多的CPU来进行合并操作。分离这些组件将使我们能够独立地扩展每个部分。
许多数据库支持触发器,当更新行并提供旧行和新行时触发。有了这个功能,数据库驱动程序可以注册触发器而不是轮询更改。
skip
、$near
和$where
的查询)已经切换到OpLog Observe Driver,这种方式在CPU负载、网络带宽方面更加高效,并且允许应用服务器进行扩展。 - imslavko