發佈(Publication)和訂閱(Subscription)是 Meteor 的最基本最重要的概念之一,可是若是你是剛剛開始接觸 Meteor 的話,也是有些難度的。javascript
這已經致使很多誤解,好比認爲 Meteor 是不安全的,或者說 Meteor 應用沒法處理大量數據等等。java
人們起初會感受這些概念很迷惑很大程度上是由於 Meteor 像變魔法同樣替你作了不少事兒。儘管這些魔法最終看起來頗有效,可是它們掩蓋了後臺真正作的工做(好像魔術同樣)。因此讓咱們剝去魔法的外衣來看看究竟發生了什麼。數據庫
首先,讓咱們回顧一下2011年以前,當 Meteor 尚未誕生的時候的老日子。好比說咱們要創建一個簡單的 Rails app。當用戶來咱們的站點,客戶端(舉例說瀏覽器)向咱們的服務器端的 app 發送請求。瀏覽器
App 的第一個任務就是搞清楚這個客戶請求什麼數據。這個多是搜索結果的第12頁、瑪麗的用戶信息、鮑勃的最新20條微博,等等等等。 你能夠想一想成爲一個書店的夥計在書架之間幫你尋找你要的書。緩存
當正確的數據被找到,這個 App 的下一個任務就是把數據轉換成好看的,人類可讀的 HTML 格式(對於 API 而言是 JSON 串)。安全
用書店來舉例,那就至關因而把你剛買的書包好,而後裝入一個漂亮的袋子。這就是著名的 MVC(模型-視圖-控制器)模式中的視圖部分。服務器
最終,App 把 HTML 代碼送到客戶端。這個 App 的任務也就交差了。它能夠去買瓶啤酒而後等着下一個請求。架構
讓咱們看看 Meteor 相對之下是多麼的特別。正如咱們看到的,Meteor 的關鍵性創新在於 Rails 程序只跑在服務器上,而一個 Meteor App 還包括在客戶端(瀏覽器)上運行的客戶端組件。app
t分佈式
推送數據庫子集到客戶端
這就至關於書店的夥計不只僅在書店裏幫你找書,還跟你回家,天天晚上讀給你聽(這聽起來怪怪的)。
這種架構可讓 Meteor 作更多很酷的事情,其中一件主要的就是 Metoer 變得數據庫無處不在。簡單說,Meteor 把你的數據拿出一部分子集複製到客戶端。
這樣後兩個主要結果:第一,服務器再也不發送 HTML 代碼到客戶端,而是發送真實的原始數據,讓客戶端決定如何處理線傳數據。第二,你能夠沒必要等待服務器傳回數據,而是當即訪問甚至修改數據(延遲補償 latency compensation)。
一個 App 的數據庫可能用上萬條數據,其中一些還多是私用和保密敏感數據。顯而易見咱們不能簡單地把數據庫鏡像到客戶端去,不管是安全緣由仍是擴展性緣由。
因此咱們須要告訴 Meteor 那些數據子集是須要送到客戶端,咱們將用發佈功能來作這個事兒。
讓咱們來回到 Microscope。這裏是咱們 App 數據庫中的全部帖子:
數據庫中的全部帖子數據
儘管實際上不存在可是咱們仍是假設咱們的帖子中有幾條由於言語不當被打了特殊標記的。咱們須要把他們留在數據庫中可是不但願讓用戶看到(發送去客戶端)。
咱們第一個任務就是告訴 Meteor 那些數據咱們要發送去客戶端。咱們告訴 Meteor 咱們只發佈沒有打標記的帖子。
排除作過標記的帖子
這裏是對應的代碼,在服務器端代碼中。
// 在服務器端 Meteor.publish('posts', function() { return Posts.find({flagged: false}); });
這樣就保證了客戶端不管如何也沒法看到有標記的帖子了。
下一步來了解一下Meteor的DDP
基本上咱們能夠把發佈/訂閱模式想象成爲一個漏斗,從服務器端(數據源)過濾數據傳送到客戶端(目標)。
這個漏斗的專屬協議叫作 DDP(分佈式數據協議 Distributed Data Protocol 的縮寫)。若是想了解 DDP 的更多細節,能夠經過看 Matt DeBergalis(Meteor 創始人之一)在 Real-time 大會上的講演視頻,或者來自 Chris Mather 的這個截屏視頻,來學習關於這個概念更多的細節。
就算是咱們想把打了標記的帖子也發送給客戶端,咱們也不能把成千上萬的帖子一股腦都發出去。咱們須要一個機制讓客戶端來肯定那些子集是他們在某個特別時候特別須要的,這就是訂閱這個功能的用途。
經過 MiniMongo,客戶端 MongoDB 的應用,你訂閱的數據會被鏡像到客戶端。
舉個例子,讓咱們如今瀏覽一下 Bob Smith 的我的頁面,這裏只會顯示他的帖子。
訂閱 Bob 的帖子鏡像到客戶端
首先,咱們給發佈功能加一個參數
// 在服務器端 Meteor.publish('posts', function(author) { return Posts.find({flagged: false, author: author}); });
而後咱們在客戶端訂閱這個發佈時定義同一個參數。
// 在客戶端 Meteor.subscribe('posts', 'bob-smith');
這就是咱們讓 Meteor 程序在客戶端可以具備可伸縮性:不去訂閱所有數據,而是指選擇你如今須要的數據去訂閱。這樣的話,你就能夠避免消耗大量的客戶端內存,不管服務器端的總數據量有多大。
如今 Bob 的帖子恰巧涵蓋了多個類別(好比:「JavaScript」、「Ruby」和「Python」)。也許咱們仍然須要把 Bob 的全部帖子都裝入內存,可是咱們如今只想顯示屬於「JavaScript」類別的帖子。這就是「查找」的用途。
在客戶端選擇一個數據子集
正如咱們在服務器上作的同樣,咱們用了 Posts.find()
函數來選擇數據的子集
// 在客戶端 Template.posts.helpers({ posts: function(){ return Posts.find({author: 'bob-smith', category: 'JavaScript'}); } });
如今咱們應該明白訂閱和發佈機制了,讓咱們在深刻了解一些常見的應用模式。
若是你從頭開始創建一個 Meteor 項目(好比,使用 meteor create
命令),系統會自動包含並啓用一個叫作autopublish
的包。讓咱們說說這個包是幹什麼的。
autopublish
的目的是讓 Meteor 應用有個簡單的起步階段,它簡單地直接把服務器上的_所有數據_鏡像到客戶端,所以你就不用管發佈和訂閱了。
自動發佈
那麼這到底是如何工做的呢?假設在服務器端咱們有一個集合叫作 posts
。自動發佈包就會自動地把 Mongo 數據庫中這個集合的全部的數據(帖子)發送到客戶端的名爲 ‘posts’
的集合中(假設客戶端的確有這樣一個集合)。
所以,若是你使用自動發佈,你就不須要考慮發佈。數據一致,並且事情變得十分簡單。固然,這樣的話會有一個明顯的問題,就是你的全部數據都被緩存到全部用戶的電腦中。
基於這個緣由,自動發佈只在你起步階段且還未考慮發佈以前時使用。
一旦你刪除掉 autopublish
這個包,你立刻就會發如今瀏覽器上沒有數據了。一個簡單的解決方法就是重複自動發佈所作的工做, 那就是發佈全部數據。好比:
Meteor.publish('allPosts', function(){ return Posts.find(); });
一旦你刪除掉 autopublish
這個包,你立刻就會發如今瀏覽器上沒有數據了。一個簡單的解決方法就是重複自動發佈所作的工做, 那就是發佈全部數據。好比:
Meteor.publish('allPosts', function(){ return Posts.find(); });
發佈全部集合
咱們仍是發佈了全部集合,可是至少咱們如今能夠本身控制哪一個集合咱們發佈哪一個不發佈。好比如今這個例子,咱們發佈了 Posts
集合可是並無發佈 Comments
。
下一步咱們要作的是發佈集合中的_部分_記錄。好比咱們只發布來自於某個做者的帖子:
Meteor.publish('somePosts', function(){ return Posts.find({'author': 'Tom'}); });
發佈集合的一部分
若是你已經閱讀了 Meteor 發佈文檔,你可能被諸如 added()
和 ready()
之類的用來設置客戶端記錄屬性的函數搞暈了,並且還糾結於彷佛咱們歷來沒有使用過這些方法。
緣由在於 Meteor 提供了十分重要的簡化:_publishCursor()
方法。你也沒有看到咱們用這個方法對吧?也許咱們沒有直接用,可是若是你在發佈函數中返回了一個遊標(好比,Posts.find({'author':'Tom'})
),那個就是 Meteor 使用這個方法的時候。
當 Meteor 看到 somePosts
發佈函數返回了一個遊標,它會調用 _publishCursor()
去 —— 你猜猜看 —— 自動發佈這個遊標。
下面就是 _publishCursor()
作的工做:
.added()
函數來完成的).observe()
來監控遊標,使用 .added()
, .changed()
和 removed()
來增刪改)。因此在上述的例子中,咱們能夠保證用戶只會在客戶端緩存中獲得他們感興趣的帖子(在這裏例子中是 Tom 發的帖子)。
咱們已經看到如何發佈部分帖子,可是咱們還須要再精簡!讓咱們看看如何只發佈指定的部分字段。
如同之前咱們使用 find()
返回一個遊標,如今咱們來去掉一些字段。
Meteor.publish('allPosts', function(){ return Posts.find({}, {fields: { date: false }}); });
發佈部分字段
實際上,咱們能夠同時使用上述兩種技術,只發布做者是 Tom 的帖子,而且隱藏 date 日期字段:
Meteor.publish('allPosts', function(){ return Posts.find({'author': 'Tom'}, {fields: { date: false }}); });
咱們已經從發佈全部集合的全部文檔的全部字段(經過 autopublish
),到發佈_個別_集合的_個別_文檔的_個別_字段。
這已經覆蓋了 Meteor 的發佈的基本內容,並且這些基本技巧已經足夠涵蓋大部分的用例了。