Meteor 發(fā)布和訂閱

2022-06-30 13:55 更新

發(fā)布和訂閱

發(fā)布(Publication)和訂閱(Subscription)是 Meteor 的最基本最重要的概念之一,但是如果你是剛剛開始接觸 Meteor 的話,也是有些難度的。

這已經(jīng)導(dǎo)致不少誤解,比如認(rèn)為 Meteor 是不安全的,或者說 Meteor 應(yīng)用無法處理大量數(shù)據(jù)等等。

人們起初會(huì)感覺這些概念很迷惑很大程度上是因?yàn)?Meteor 像變魔法一樣替你做了很多事兒。盡管這些魔法最終看起來很有效,但是它們掩蓋了后臺(tái)真正做的工作(好像魔術(shù)一樣)。所以讓我們剝?nèi)ツХǖ耐庖聛砜纯淳烤拱l(fā)生了什么。

過去的日子

首先,讓我們回顧一下2011年之前,當(dāng) Meteor 還沒有誕生的時(shí)候的老日子。比如說我們要建立一個(gè)簡(jiǎn)單的 Rails app。當(dāng)用戶來我們的站點(diǎn),客戶端(舉例說瀏覽器)向我們的服務(wù)器端的 app 發(fā)送請(qǐng)求。

App 的第一個(gè)任務(wù)就是搞清楚這個(gè)客戶請(qǐng)求什么數(shù)據(jù)。這個(gè)可能是搜索結(jié)果的第12頁(yè)、瑪麗的用戶信息、鮑勃的最新20條微博,等等等等。 你可以想想成為一個(gè)書店的伙計(jì)在書架之間幫你尋找你要的書。

當(dāng)正確的數(shù)據(jù)被找到,這個(gè) App 的下一個(gè)任務(wù)就是把數(shù)據(jù)轉(zhuǎn)換成好看的,人類可讀的 HTML 格式(對(duì)于 API 而言是 JSON 串)。

用書店來舉例,那就相當(dāng)于是把你剛買的書包好,然后裝入一個(gè)漂亮的袋子。這就是著名的 MVC(模型-視圖-控制器)模式中的視圖部分。

最終,App 把 HTML 代碼送到客戶端。這個(gè) App 的任務(wù)也就交差了。它可以去買瓶啤酒然后等著下一個(gè)請(qǐng)求。

Meteor 的方式

讓我們看看 Meteor 相對(duì)之下是多么的特別。正如我們看到的,Meteor 的關(guān)鍵性創(chuàng)新在于 Rails 程序只跑在服務(wù)器上,而一個(gè) Meteor App 還包括在客戶端(瀏覽器)上運(yùn)行的客戶端組件。

推送數(shù)據(jù)庫(kù)子集到客戶端

這就相當(dāng)于書店的伙計(jì)不僅僅在書店里幫你找書,還跟你回家,每天晚上讀給你聽(這聽起來怪怪的)。

這種架構(gòu)可以讓 Meteor 做更多很酷的事情,其中一件主要的就是 Metoer 變得數(shù)據(jù)庫(kù)無處不在。簡(jiǎn)單說,Meteor 把你的數(shù)據(jù)拿出一部分子集復(fù)制到客戶端

這樣后兩個(gè)主要結(jié)果:第一,服務(wù)器不再發(fā)送 HTML 代碼到客戶端,而是發(fā)送真實(shí)的原始數(shù)據(jù),讓客戶端決定如何處理線傳數(shù)據(jù)。第二,你可以不必等待服務(wù)器傳回?cái)?shù)據(jù),而是立即訪問甚至修改數(shù)據(jù)延遲補(bǔ)償 latency compensation)。

發(fā)布

一個(gè) App 的數(shù)據(jù)庫(kù)可能用上萬條數(shù)據(jù),其中一些還可能是私用和保密敏感數(shù)據(jù)。顯而易見我們不能簡(jiǎn)單地把數(shù)據(jù)庫(kù)鏡像到客戶端去,無論是安全原因還是擴(kuò)展性原因。

所以我們需要告訴 Meteor 那些數(shù)據(jù)子集是需要送到客戶端,我們將用發(fā)布功能來做這個(gè)事兒。

讓我們來回到 Microscope。這里是我們 App 數(shù)據(jù)庫(kù)中的所有帖子:

數(shù)據(jù)庫(kù)中的所有帖子數(shù)據(jù)

盡管實(shí)際上不存在但是我們還是假設(shè)我們的帖子中有幾條因?yàn)檠哉Z(yǔ)不當(dāng)被打了特殊標(biāo)記的。我們需要把他們留在數(shù)據(jù)庫(kù)中但是不希望讓用戶看到(發(fā)送去客戶端)。

我們第一個(gè)任務(wù)就是告訴 Meteor 那些數(shù)據(jù)我們發(fā)送去客戶端。我們告訴 Meteor 我們只發(fā)布沒有打標(biāo)記的帖子。

排除做過標(biāo)記的帖子

這里是對(duì)應(yīng)的代碼,在服務(wù)器端代碼中。

// 在服務(wù)器端
Meteor.publish('posts', function() {
  return Posts.find({flagged: false}); 
});

這就保證客戶端無論如何也無法看到打了標(biāo)記的帖子了。這就是 Meteor App 如何做到安全性的:保證只發(fā)布你讓這個(gè)當(dāng)前用戶看到的數(shù)據(jù)。

DDP

基本上我們可以把發(fā)布/訂閱模式想象成為一個(gè)漏斗,從服務(wù)器端(數(shù)據(jù)源)過濾數(shù)據(jù)傳送到客戶端(目標(biāo))。

這個(gè)漏斗的專屬協(xié)議叫做 DDP(分布式數(shù)據(jù)協(xié)議 Distributed Data Protocol 的縮寫)。如果想了解 DDP 的更多細(xì)節(jié),可以通過看 Matt DeBergalis(Meteor 創(chuàng)始人之一)在 Real-time 大會(huì)上的講演視頻,或者來自 Chris Mather 的這個(gè)截屏視頻,來學(xué)習(xí)關(guān)于這個(gè)概念更多的細(xì)節(jié)。

訂閱

就算是我們想把打了標(biāo)記的帖子也發(fā)送給客戶端,我們也不能把成千上萬的帖子一股腦都發(fā)出去。我們需要一個(gè)機(jī)制讓客戶端來確定那些子集是他們?cè)谀硞€(gè)特別時(shí)候特別需要的,這就是訂閱這個(gè)功能的用途。

通過 MiniMongo,客戶端 MongoDB 的應(yīng)用,你訂閱的數(shù)據(jù)會(huì)被鏡像到客戶端。

舉個(gè)例子,讓我們現(xiàn)在瀏覽一下 Bob Smith 的個(gè)人頁(yè)面,這里只會(huì)顯示他的帖子。

訂閱 Bob 的帖子鏡像到客戶端

首先,我們給發(fā)布功能加一個(gè)參數(shù):

// 在服務(wù)器端
Meteor.publish('posts', function(author) {
  return Posts.find({flagged: false, author: author});
});

然后我們?cè)诳蛻舳?em>訂閱這個(gè)發(fā)布時(shí)定義同一個(gè)參數(shù)。

// 在客戶端
Meteor.subscribe('posts', 'bob-smith');

這就是我們讓 Meteor 程序在客戶端能夠具有可伸縮性:不去訂閱全部數(shù)據(jù),而是指選擇你現(xiàn)在需要的數(shù)據(jù)去訂閱。這樣的話,你就可以避免消耗大量的客戶端內(nèi)存,無論服務(wù)器端的總數(shù)據(jù)量有多大。

查找

現(xiàn)在 Bob 的帖子恰巧涵蓋了多個(gè)類別(比如:“JavaScript”、“Ruby”和“Python”)。也許我們?nèi)匀恍枰?Bob 的所有帖子都裝入內(nèi)存,但是我們現(xiàn)在只想顯示屬于“JavaScript”類別的帖子。這就是“查找”的用途。

在客戶端選擇一個(gè)數(shù)據(jù)子集

正如我們?cè)诜?wù)器上做的一樣,我們用了 Posts.find() 函數(shù)來選擇數(shù)據(jù)的子集:

// 在客戶端
Template.posts.helpers({
  posts: function(){
    return Posts.find({author: 'bob-smith', category: 'JavaScript'});
  }
});

現(xiàn)在我們應(yīng)該明白訂閱和發(fā)布機(jī)制了,讓我們?cè)谏钊肓私庖恍┏R姷膽?yīng)用模式。

自動(dòng)發(fā)布

如果你從頭開始建立一個(gè) Meteor 項(xiàng)目(比如,使用 meteor create 命令),系統(tǒng)會(huì)自動(dòng)包含并啟用一個(gè)叫做 autopublish 的包。讓我們說說這個(gè)包是干什么的。

autopublish 的目的是讓 Meteor 應(yīng)用有個(gè)簡(jiǎn)單的起步階段,它簡(jiǎn)單地直接把服務(wù)器上的_全部數(shù)據(jù)_鏡像到客戶端,因此你就不用管發(fā)布和訂閱了。

自動(dòng)發(fā)布

那么這究竟是如何工作的呢?假設(shè)在服務(wù)器端我們有一個(gè)集合叫做 posts。自動(dòng)發(fā)布包就會(huì)自動(dòng)地把 Mongo 數(shù)據(jù)庫(kù)中這個(gè)集合的所有的數(shù)據(jù)(帖子)發(fā)送到客戶端的名為 ‘posts’ 的集合中(假設(shè)客戶端的確有這樣一個(gè)集合)。

因此,如果你使用自動(dòng)發(fā)布,你就不需要考慮發(fā)布。數(shù)據(jù)一致,而且事情變得十分簡(jiǎn)單。當(dāng)然,這樣的話會(huì)有一個(gè)明顯的問題,就是你的所有數(shù)據(jù)都被緩存到所有用戶的電腦中。

基于這個(gè)原因,自動(dòng)發(fā)布只在你起步階段且還未考慮發(fā)布之前時(shí)使用。

發(fā)布全部集合

一旦你刪除掉 autopublish 這個(gè)包,你馬上就會(huì)發(fā)現(xiàn)在瀏覽器上沒有數(shù)據(jù)了。一個(gè)簡(jiǎn)單的解決方法就是重復(fù)自動(dòng)發(fā)布所做的工作, 那就是發(fā)布所有數(shù)據(jù)。比如:

Meteor.publish('allPosts', function(){
  return Posts.find();
});

發(fā)布所有集合

我們還是發(fā)布了所有集合,但是至少我們現(xiàn)在可以自己控制哪個(gè)集合我們發(fā)布哪個(gè)不發(fā)布。比如現(xiàn)在這個(gè)例子,我們發(fā)布了 Posts 集合但是并沒有發(fā)布 Comments。

發(fā)布部分集合

下一步我們要做的是發(fā)布集合中的_部分_記錄。比如我們只發(fā)布來自于某個(gè)作者的帖子:

Meteor.publish('somePosts', function(){
  return Posts.find({'author': 'Tom'});
});

發(fā)布集合的一部分

幕后真相

如果你已經(jīng)閱讀了 Meteor 發(fā)布文檔,你可能被諸如 added()ready() 之類的用來設(shè)置客戶端記錄屬性的函數(shù)搞暈了,而且還糾結(jié)于似乎我們從來沒有使用過這些方法。

原因在于 Meteor 提供了十分重要的簡(jiǎn)化:_publishCursor() 方法。你也沒有看到我們用這個(gè)方法對(duì)吧?也許我們沒有直接用,但是如果你在發(fā)布函數(shù)中返回了一個(gè)游標(biāo)(比如,Posts.find({'author':'Tom'})),那個(gè)就是 Meteor 使用這個(gè)方法的時(shí)候。

當(dāng) Meteor 看到 somePosts 發(fā)布函數(shù)返回了一個(gè)游標(biāo),它會(huì)調(diào)用 _publishCursor() 去 —— 你猜猜看 —— 自動(dòng)發(fā)布這個(gè)游標(biāo)。

下面就是 _publishCursor() 做的工作:

  • 它檢查服務(wù)器端的集合的名稱
  • 它從游標(biāo)中找到所有的符合要求的文檔,然后發(fā)送到客戶端的同名集合中。(它使用了.added() 函數(shù)來完成的)
  • 當(dāng)新的文檔加入集合了,或者刪除或者改變了,它會(huì)把這些改動(dòng)發(fā)送到客戶端的集合。(它使用 .observe() 來監(jiān)控游標(biāo),使用 .added(), .changed()removed() 來增刪改)。

所以在上述的例子中,我們可以保證用戶只會(huì)在客戶端緩存中得到他們感興趣的帖子(在這里例子中是 Tom 發(fā)的帖子)。

發(fā)布部分字段

我們已經(jīng)看到如何發(fā)布部分帖子,但是我們還需要再精簡(jiǎn)!讓我們看看如何只發(fā)布指定的部分字段。

如同以前我們使用 find() 返回一個(gè)游標(biāo),現(xiàn)在我們來去掉一些字段。

Meteor.publish('allPosts', function(){
  return Posts.find({}, {fields: {
    date: false
  }});
});

發(fā)布部分字段

實(shí)際上,我們可以同時(shí)使用上述兩種技術(shù),只發(fā)布作者是 Tom 的帖子,并且隱藏 date 日期字段:

Meteor.publish('allPosts', function(){
  return Posts.find({'author': 'Tom'}, {fields: {
    date: false
  }});
});

總結(jié)

我們已經(jīng)從發(fā)布所有集合的所有文檔的所有字段(通過 autopublish),到發(fā)布_個(gè)別_集合的_個(gè)別_文檔的_個(gè)別_字段。

這已經(jīng)覆蓋了 Meteor 的發(fā)布的基本內(nèi)容,而且這些基本技巧已經(jīng)足夠涵蓋大部分的用例了。

有時(shí),你需要進(jìn)一步來組合、連接或融合發(fā)布。我們?cè)谝院蟮恼鹿?jié)中講到這些內(nèi)容!

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)