幾個場景
首先,讓我先來帶您瀏覽幾個 ChangingThePresent.org 中的頁面吧。我將顯示站點中幾個需要緩存的地方。然后,再指出我們?yōu)槠渲忻總€地方所做出的選擇以及為實現(xiàn)這些頁面所使用的代碼或策略。尤其會重點討論如下內容:
- 全靜態(tài)頁面
- 幾乎無變化的全動態(tài)的頁面
- 動態(tài)頁面片段
- 應用程序數(shù)據
先來看看靜態(tài)頁面。幾乎每個站點都會有靜態(tài)頁面,如圖 1 所示,其中還有我們的條款和條件??梢酝ㄟ^單擊 register 然后再選擇是否接受用戶協(xié)議來瀏覽相應頁面。對于 ChangingThePresent 而言,我們從此頁中刪除了所有動態(tài)內容以便 Apache 能夠對它進行緩存。按照我們 Apache 中配置的規(guī)則,這些內容永遠都不會由 Rails 服務器生成。因此,我根本無需對其考慮 Rails 緩存。
圖 1. 用戶協(xié)議
接下來,再來看看全動態(tài)頁面。理論上講,ChangingThePresent 可以有一些動態(tài)構建的頁面,但是這些頁面一般很少變化。由于幾乎所有頁面都會顯示用戶是否登錄,因此我們并不怎么關注這種緩存。
再下來,看看頁面分段緩存。圖 2 中所示的主頁原來是完全靜態(tài)的,現(xiàn)在,有一些元素變成了動態(tài)的。每天,頁面都會顯示一系列禮物,這些禮物有的是隨機選的,有的則由我們的管理員選定。請注意在標題為 “A Few of our Special Gifts for Mother's Day” 節(jié)下的那些禮物,同時也請注意在最右邊顯示為 “l(fā)ogin.” 的那個鏈接。此鏈接取決于用戶是否登錄。我們不能緩存整個頁。頁面每天只能改變一次。
圖 2. 主頁
最后再考慮應用程序。除非是在 15 年之前進行網絡沖浪,否則您現(xiàn)在遇到的有趣站點全部都是動態(tài)的。現(xiàn)代的應用程序大都分層,而且可以通過在層間添加緩存來使這些分層更加有效。ChangingThePresent 在數(shù)據庫層采用了一些緩存。接下來,我將深入討論不同類型的緩存,還會介紹我們?yōu)?ChangingThePresent 都采用了何種緩存。
緩存靜態(tài)內容
Mongrel 是一種 Web 服務器,由 Zed Shaw 利用 2500 行 Ruby 和 C 編寫而成。這個小型的服務器占用內存極少,非常適合 Ruby Web 應用程序,例如 Rails、Nitro、Iowa 等等。Mongrel 可運行于 UNIX? 和 Linux? 上,也可運行在 Win32 上。Mongrel 也經常可以作為代理運行在另一個 Web 服務器(例如 Apache 或 Litespeed)的后端,但這不是必需的 —— 因為 Mongrel 是一種 HTTP 服務器,可以與所有您偏好的 HTTP 工具結合使用。
除了圖像之外,有關緩存靜態(tài)數(shù)據的內容,可講的內容不多。由于我們的網站是一個慈善性質的門戶網站,這意味著我們需要更多地關注用戶的感受,比如多加入一些圖像或視頻。但我們的 Web 服務器 Mongrel 并不能很好地服務靜態(tài)數(shù)據,因此我們使用 Apache 來服務圖像內容。
我們現(xiàn)在正在著手轉向采用圖形加速器 Panther Express 來緩存最經常被使用的圖像以使其能夠更快地被我們的客戶訪問到。要采取這種策略,我們將需要一個子域 images.changingThePresent.org。Panther Express 直接在圖像的本地緩存中提供圖像服務,然后再向我們發(fā)送請求。由于 Panther 服務并不知道我們何時會更改圖像,所以我們使用 HTTP 報頭來使其到期失效,如下所示:
HTTP 緩存失效報頭
HTTP/1.1 200 OK
Cache-Control: max-age=86400, must-revalidate
Expires: Tues, 17 Apr 2007 11:43:51 GMT
Last-Modified: Mon, 16 Apr 2007 11:43:51 GMT
注意這些不是 HTML 報頭。它們與 Web 頁面內容獨立構建。Web 服務器將負責構建這些 HTTP 報頭。像這樣一篇有關 Rails 的文章系列若詳細介紹 Web 服務器配置,未免有點偏題,所以我將直接切入可用 Rails 框架進行控制的緩存內容這一主題(有關 Web 服務器配置的更多內容,請參見 參考資料 中的相關鏈接)。
頁面緩存
如果動態(tài)頁面不經常更改,可以使用頁面級的緩存。比如,Blog 和公告牌使用的就是這種緩存。通過頁面緩存,Rails 就可以用來構建動態(tài) HTML 頁,并將此頁存儲在公共目錄,這樣,應用程序服務器就可以像服務其他靜態(tài)頁面一樣來服務這個動態(tài)頁。
如果頁面已經被緩存,那么就不需要引入 Rails,頁面緩存是 Rails 內速度最快的一種緩存。在最底層,頁面緩存實際上在 Rails 中非常容易實現(xiàn)。頁面和分段緩存二者均在控制器級別發(fā)生。您需要告知 Rails 如下內容:
- 想要緩存哪些頁面?
- 當頁面內容更改時,您如何能在緩存中讓該頁面到期失效?
可以通過在控制器類中使用 caches_page 指令來啟用頁面緩存。例如,若要在 about_us_controller 緩存 privacy_policy 和 user_agreement 頁面,可以輸入如下代碼:
清單 2. 啟用頁面緩存
class AboutController ApplicationController
caches_page :privacy_policy, :user_agreement
end
讓頁面到期失效則可以通過 expire_page 指令來實現(xiàn)。若要在 Rails 調用 new_pages 動作時使上述頁面到期失效,可以使用如下代碼:
清單 3. 使頁面失效
class AboutController ApplicationController
caches_page :privacy_policy, :user_agreement
def new_pages
expire_page :action => :privacy_policy
expire_page :action => :user_agreement
end
end
另外,有幾個小問題需要注意,比如 URL。URL 不能依賴于 URL 參數(shù)。例如,應該使用 gifts/water/1 而非 gifts/water?page=1。在 routes.rb 中使用這類 URL 將非常容易。比如,我們的頁面中總是有一個選項卡參數(shù)用來顯示哪個選項卡被當前選中。若要將此選項卡作為 URL 的一部分,我們會有如下的路由規(guī)則:
清單 4. 選項卡的路由規(guī)則
復制代碼 代碼如下:
map.connect 'member/:id/:tab', :controller => 'profiles', :action => 'show'
對于具有頁面參數(shù)的那些列表以及依賴于 URL 參數(shù)的其他頁面,也需要采用相同的做法。此外,還需要考慮安全性問題。
如果頁面已經在緩存內,那么就不會用到 Rails 框架,服務器并不能為您管理安全性。Web 服務器將更樂于在緩存內呈現(xiàn)任何頁面,而不管用戶是否對其擁有查看的權限。所以,如果您很關心頁面可由誰查看,那么就不要使用頁面緩存。
如果只是想緩存簡單的靜態(tài)頁面,那么了解上述內容就應該足夠了。只要內容簡單,實現(xiàn)起來就不難。
當想要緩存更為復雜的內容時,就需要進行一些權衡取舍了。由于想要緩存的頁面高度動態(tài),所以到期失效邏輯就會變得更加復雜。要處理復雜的到期失效邏輯,將需要編寫和配置定制清理器(sweeper)。在某些控制器擊發(fā)時,這些類會從緩存內刪除選定的元素。
多數(shù)定制清理器都會觀察某些模型對象,并根據更改擊發(fā)邏輯來使一個或多個緩存頁面到期失效。清單 5 顯示了一種典型的緩存清理器。在此清理器中,開發(fā)人員可以定義一個活動記錄事件,比如 after_save。當此事件擊發(fā)時,清理器也會擊發(fā),并可讓緩存內的特定頁面到期失效。這個事例所顯示的到期失效基于 expire_page 方法。而很多嚴格的應用程序大都直接使用 Ruby 優(yōu)秀的文件系統(tǒng)實用工具來顯式地刪除所緩存的頁面。
清單 5. 一個典型的觀察器
class CauseController ApplicationController
cache_sweeper :cause_sweeper
...
class CauseSweeper ActionController::Caching::Sweeper
observe Cause
def after_save(record)
expire_page(:controller => 'causes', :action => 'show',
:id => record.id)
cause.nonprofits.each do |nonprofit|
expire_page(:controller => 'nonprofits', :action => 'show',
:id => nonprofit.id)
end
end
end
現(xiàn)在,您可能會開始感覺到頁面緩存的些許缺點了:復雜性。您雖然可以很好地進行頁面級的緩存,但固有的復雜性卻讓應用程序很難測試,進而會增加系統(tǒng)內出現(xiàn) bug 的可能性。而且,如果頁面針對每個用戶都會有所不同,或者希望緩存進行過身份驗證的頁面,那么將需要使用頁面緩存之外的方式。對于 ChangingThePresent,我們必須處理兩種情況,原因是我們必須基于用戶是否登錄來更改基本布局上的鏈接。對于大多數(shù)頁面,我們甚至都不會考慮使用頁面級緩存。為了讓您能夠深入了解頁面級的緩存,在本文的 參考資料 部分,我特意給出了到一系列有關頁面級緩存的優(yōu)秀文章的鏈接。接下來,將來深入探究另一種形式的整頁緩存 —— 動作緩存。
動作緩存
至此,您已經了解了頁面緩存的主要的優(yōu)勢及主要的缺點:對于多數(shù)頁面檢索而言,根本無需考慮使用 Rails。頁面緩存的優(yōu)勢是速度快。缺點是缺少靈活性。如果想要基于應用程序內的條件 — 例如,身份認證 — 來緩存整個頁面,那么可以使用動作緩存。
動作緩存與頁面緩存的工作方式大體相同,但在流程上稍有差別。Rails 在呈現(xiàn)動作前會實際調用控制器。如果由該動作呈現(xiàn)的頁面已經存在于緩存內,那么 Rails 就會在緩存內呈現(xiàn)頁面而不是重新加以呈現(xiàn)。由于現(xiàn)在使用了 Rails,因此動作緩存的速度要比頁面緩存慢一些,但其優(yōu)點還是很明顯的。幾乎所有的 Rails 認證模式都會在控制器上使用 before 過濾器。動作緩存讓您能夠利用認證及控制器上的任何過濾器。
從語句構成的角度來看,動作緩存與頁面緩存也應該十分類似,只有指令不太一樣。清單 6 顯示了如何使用 caches_action 指令。
清單 6. 啟用動作緩存
class AboutController ApplicationController
caches_action :secret_page, :secret_list
end
緩存到期失效以及清理器的工作方式也應該相同。我們不使用動作緩存的原因與我們不使用頁面緩存的原因是一樣的,但分段緩存對我們來說更重要一些。
緩存頁面分段
借助部分緩存,可以緩存頁面的一部分,所緩存的內容很多時候都是布局之類的。要使用分段緩存,開發(fā)人員需要先確定分段,方法是通過在 Web 頁面上直接放上 rhtml 指令來包圍一塊內容,如清單 7 所示。在 ChangingThePresent.org 上,我們使用分段緩存來緩存首頁和其他的幾頁。所有的這些頁均使用了數(shù)據庫密集訪問而且大都是我們最受歡迎的頁面。
清單 7. 確定緩存分段
% cache 'gifts_index' do %>
h3>
Here, you can make the world a better place with a single gift. Donation gifts
are also a wonderful way to honor friends and family. Just imagine what we
can achieve together.
/h3>
h2 class="lightBlue">%= @event_title %>/h2>
div id="homefeatureitems">
% for gift in @event_gifts %>
%= render :partial => 'gifts/listable', :locals => { :gift => gift } %>
% end %>
/div>
...
% end %>
在清單 7 中,cache 幫助程序標識所要緩存的分區(qū)。第一個參數(shù)是標識此緩存分區(qū)的惟一名稱。第二個參數(shù)包含代碼塊 — 即第一個 do 和最后一個 end 之間的代碼 — 此代碼塊準確地確定了要緩存的 RHTML 分區(qū)。
我們的網站只有一個主頁,所以命名這個頁面非常容易。在其他地方,我們使用一種特定的方法來決定此網頁的 URL 以便惟一標識緩存分段。例如,當我們?yōu)樘囟ǖ膬热荩ū热缡澜绾推交驕p少貧困)而進行代碼緩存時,我們需要使用清單 8 中的代碼。代碼會為之尋找永久 url,也稱為 permalink。
清單 8. 通過 URL 標識緩存分段
% cache @cause.permalink(params[:id]) do %>
通常,當緩存單獨頁面時,需要用清理器使之過期失效。有時,使用簡單的基于時間的對象過期更為容易和簡潔。默認地,Rails 并不提供這類機制,但有一種插件名為 timed_fragment_cache 可以實現(xiàn)這一目的。借助這個插件,我可以指定超時,可以在緩存了的內容中指定,也可以在為此頁提供了動態(tài)數(shù)據的控制器代碼中指定。例如,清單 9 所示的代碼就可以為此頁面構建動態(tài)數(shù)據。when_fragment_expired 方法只有在相關的緩存分段過期時才會執(zhí)行。此方法接受參數(shù),用來指定超時的時長,它還接受一個代碼塊,用來指定當內容過期時哪些內容需要重建。我也可以選擇在 rhtml 頁面中指定超時和緩存方法,但我更愿意使用基于控制器的方法。
清單 9. 基于時間的緩存到期
def index
when_fragment_expired 'causes_list', 15.minutes.from_now do
@causes = Cause.find_all_ordered
end
end
如果能夠容忍數(shù)據稍微有些陳舊,那么使用定時的到期機制將可以極大地簡化緩存策略。對于每個被緩存的元素,只需指定想要緩存的內容、可生成動態(tài)內容的任何控制器動作以及超時。與頁面緩存類似,如果需要,也可以使用 expire_fragment :controller => controller, :action => action, :id => id 方法顯式讓內容到期。此方法的工作方式與緩存動作和緩存頁面的到期失效是一樣的。接下來,我將介紹如何配置此后端。
Memcached
至此為止,我已經介紹了 Ruby on Rails 的頁面和分段緩存模型??催^了 API 之后,現(xiàn)在就可以定義緩存后的數(shù)據的去處了。默認地,Rails 將把緩存后的頁面放入文件系統(tǒng)。緩存后的頁面和動作都會進入公共目錄??梢耘渲镁彺婧蟮姆侄蔚拇鎯ξ恢?。為此,需要用到內存存儲、文件系統(tǒng)(在所定義的目錄)、數(shù)據庫或稱為 memcached 的服務。對于 ChangingThePresent.org,我們使用 memcached。
可以將 Memcached 想象為一個大型的 hash 圖,這個圖可通過網絡獲得?;趦却娴木彺嫠俣瓤?,而基于網絡的緩存的可伸縮性比較好。有了插件支持,Rails 就可使用 memcached 來緩存分段和 ActiveRecord 模型。要使用它,需要安裝 memcached(更多信息,請參看 參考資料)并在 environment.rb(或其他的環(huán)境配置文件,比如 production.rb)對它進行配置。
清單 10. 配置緩存
config.action_controller.perform_caching = true
memcache_options = {
:c_threshold => 10_000,
:compression => false,
:debug => false,
:readonly => false,
:urlencode => false,
:ttl => 300,
:namespace => 'igprod',
:disabled => false
}
CACHE = MemCache.new memcache_options
清單 10 顯示了一種典型的配置,其中第一行 config.action_controller.perform_caching = true 將啟用緩存。接下來的一行將準備緩存選項。注意,這里的諸多選項是為了讓您可以獲得更多的調試數(shù)據、禁用緩存和定義該緩存的名稱空間。在 參考資料 部分給出的 memcached 站點可以找到有關配置選項的更多信息。
模型緩存
我們使用的最后一種緩存是基于模型的緩存。我們使用的是稱為 CachedModel 的緩存插件的一種定制版本。模型緩存實際上是一種有限形式的數(shù)據庫緩存。緩存很容易按模型啟用。
要想讓模型使用緩存解決方案,只需擴展 CachedModel 類,而非擴展 ActiveRecord,如清單 11 所示。 CachedModel 擴展 ActiveRecord::Base。ActiveRecord 并非全對象關系型映射層。此框架極大地依賴于 SQL 來執(zhí)行復雜的特性,而且如果需要,用戶可以很容易降至 SQL。直接使用 SQL 會使緩存出問題,因為緩存層必須處理完整的結果集,而不是單獨一個數(shù)據庫行。處理完整的結果集常常會問題不斷,而且如果沒有支持應用程序的深層邏輯,這幾乎不太可能。正由于這個原因,CachedModel 的焦點才會放到緩存單個模型對象上,并只加速返回單行結果的查詢。
清單 11. 使用 CachedModel
大多數(shù) Rails 應用程序都會重復訪問多個條目,例如用戶對象。模型緩存在很多情況下都可以明顯地使速度加快。對于 ChangingThePresent,我們剛剛開始加速基于模型的緩存。
結束語
Ruby 雖然是一門生產率極高的語言,但若從性能角度考慮,該語言解釋性的特性讓它并不那么理想。大多數(shù)主要的 Rails 應用程序都將會通過有效利用緩存來彌補某些不足。對于 ChangingThePresent.org,我們主要使用分段緩存,并通過控制器使用基于時間的方法來使緩存分段到期失效。這種方式很適合我們的網站,即使其中有一些頁面會基于登錄進來的用戶有所變化。
我們還研究了使用受 memcached 支撐的 CachedModel 類所能帶來的影響。雖然我們的研究還僅限于緩存對數(shù)據庫性能所造成的影響,但早期的結果還是很有希望的。在 下一篇 文章中,我將介紹一些實用技巧,您可以使用這些技巧來為另一個真實世界中的 Rails 示例進行數(shù)據庫優(yōu)化。
您可能感興趣的文章:- 在localStorage中存儲對象數(shù)組并讀取的方法
- 詳解JavaScript中l(wèi)ocalStorage使用要點
- android webview 中l(wèi)ocalStorage無效的解決方法
- JS localStorage實現(xiàn)本地緩存的方法
- 使用jquery讀取html5 localstorage的值的方法
- Android使用緩存機制實現(xiàn)文件下載及異步請求圖片加三級緩存
- PHP緩存機制Output Control詳解
- jQuery的緩存機制淺析
- 簡單的php緩存類分享 php緩存機制
- 移動端使用localStorage緩存Js和css文的方法(web開發(fā))
- localStorage的黑科技-js和css緩存機制