本課時(shí)講解 Rails 中如何使用緩存。
Rails 提供了三種方式的緩存,頁(yè)面緩存,方法緩存和片段緩存,在 Rails 4 之前的版本里,它包含在 Rails 中,但是從 4.x 開(kāi)始,三種緩存中的兩種轉(zhuǎn)為 gem 形式,只有片段緩存保留在 Rails 默認(rèn) 中。
在開(kāi)發(fā)環(huán)境下,緩存是關(guān)閉的,如果要測(cè)試它,需要更改配置:
config.action_controller.perform_caching = true
在產(chǎn)品環(huán)境下,它默認(rèn)是 true。
Rails 4.x 將頁(yè)面緩存轉(zhuǎn)為 gem,使用的時(shí)候需要加入到 gemfile 中。
我們?cè)O(shè)置一下緩存路徑,在 config/environments/development.rb
config.action_controller.page_cache_directory = "#{Rails.root.to_s}/public"
頁(yè)面緩存是將整個(gè)頁(yè)面,生成一份靜態(tài)的 html 頁(yè)面,這個(gè)頁(yè)面會(huì)保存在剛才設(shè)置的目錄中。Rails 在顯示該地址的時(shí)候,會(huì)優(yōu)先查找 public 是否有同名的 html 文件優(yōu)先顯示。
我們把 show 方法加入到頁(yè)面緩存中:
class ProductsController < ApplicationController
...
caches_page :show
當(dāng)?shù)谝淮卧L問(wèn)時(shí),會(huì)創(chuàng)建該緩存文件:
Write page /path/to/project/public/products/3.html (9.5ms)
再次訪問(wèn)時(shí),便直接讀取該文件,而不再執(zhí)行 show 方法了。
這樣做的好處是,可以把一些經(jīng)常訪問(wèn)的頁(yè)面作為頁(yè)面緩存。缺點(diǎn)是,這種頁(yè)面不能有太多用戶(hù)的個(gè)人信息,因?yàn)檫@個(gè)頁(yè)面對(duì)所有人訪問(wèn)都是相同的內(nèi)容。如果必須考慮個(gè)人信息,可以改為 js 形式,或者使用方法緩存(Action Cache)。
當(dāng)這個(gè)緩存頁(yè)面內(nèi)容更改時(shí),可以刪掉該文件,再次訪問(wèn)時(shí)會(huì)自動(dòng)創(chuàng)建。也可以在 update 內(nèi)加入過(guò)期的命令:
def update
respond_to do |format|
if @product.update(product_params)
expire_page action: 'show', id: @product.id
...
else
...
end
end
end
更新資料后會(huì)自動(dòng)過(guò)期該文件。
Expire page /path/to/project/public/products/3.html (1.0ms)
方法緩存和頁(yè)面緩存的區(qū)別是:它會(huì)執(zhí)行對(duì)應(yīng)的 action 中的代碼。頁(yè)面緩存直接讀取緩存文件,不執(zhí)行 action 中的代碼。
頁(yè)面緩存的 gem 在這里。
我們給方法增加方法緩存:
class ProductsController < ApplicationController
...
caches_action :index, layout: false
訪問(wèn)該頁(yè)面,會(huì)創(chuàng)建一個(gè)片段緩存(fragment cache)文件:
Write fragment views/localhost:3000/products (5.9ms)
該片段緩存為當(dāng)前整個(gè)頁(yè)面,我們?cè)黾?layout: false 參數(shù),這樣,片段緩存只包含該 action 對(duì)應(yīng)的模板內(nèi)容,而不包含 layout。我們?cè)O(shè)計(jì)的代碼,將用戶(hù)信息放置在 layout 中,登錄后會(huì)顯示用戶(hù)名。所以 layout 是不應(yīng)該放到緩存中的。
但是,因?yàn)槲覀兘o index 方法增加了搜索功能,而該方法已經(jīng)加入到了緩存中,所以,搜索是還是顯示的緩存內(nèi)容。這里可以做調(diào)整,要么將搜索放到專(zhuān)用的非緩存方法中,要么搜索時(shí)過(guò)時(shí)該緩存。
片段緩存,是 Rails 默認(rèn)使用的緩存方式,它指的是視圖(view)中,緩存局部?jī)?nèi)容:
<% cache do %>
分類(lèi):
<% Catalog.all.each do |catalog| %>
<%= link_to catalog.name, catalog %>
<% end %>
<% end %>
這里把經(jīng)常訪問(wèn)的分類(lèi)列表,加入到了緩存中,避免每次頁(yè)面訪問(wèn)該部分都讀取數(shù)據(jù)庫(kù)。
我們可以給 cache 方法增加一些參數(shù):
<% cache(action: 'new', action_suffix: 'all_products') do %>
它產(chǎn)生的緩存 key 是:
Write fragment views/localhost:3000/products/new?action_suffix=all_products/02c540e3ab26f72d5e9273d5824c204e (60.0ms)
也可以直接命名緩存 key:
<% cache( "all_products" ) do %>
它產(chǎn)生的緩存 key 是:
Write fragment views/all_products/cc926a692262d0e538f07d5dd5d54942 (15.1ms)
或者直接緩存一個(gè)實(shí)例:
<% cache @product do %>
它產(chǎn)生的緩存 key 是:
Write fragment views/products/3-20150620164035711340000/b0699b1b8be94ebd1bfcfe74a21571f8 (21.5ms)
可見(jiàn),緩存是產(chǎn)生一個(gè) key: value 結(jié)構(gòu)的數(shù)據(jù)。key 來(lái)自于實(shí)例的 cache_key 方法:
p = Product.last
p.cache_key
=> "products/3-20150620164035711340000"
p.updated_at = nil
p.cache_key
=> "products/3"
該方法會(huì)讀取 updated_at 字段值,這樣,每當(dāng)該實(shí)例更改的時(shí)候,會(huì)自動(dòng)更新 updated_at 字段,相當(dāng)于自動(dòng)更新了緩存。
我們可以使用
expire_fragment(action: 'new', action_suffix: 'all_products')
expire_fragment("all_products")
過(guò)期這些片段緩存
緩存產(chǎn)生的是 key: value 結(jié)構(gòu)的數(shù)據(jù),所以我們可以使用支持該解構(gòu)的數(shù)據(jù)庫(kù)來(lái)保存緩存。在 config/environments/production.rb 中有 cache_store 的選項(xiàng):
# Use a different cache store in production.
config.cache_store = :mem_cache_store
這里有四個(gè)選項(xiàng)可以使用::memory_store, :file_store, :mem_cache_store, :null_store。在 手冊(cè)里還介紹了 JRuby 的 Ehcache。
緩存和 Ruby 進(jìn)程使用共同的內(nèi)存,默認(rèn)大小為32M,如果超出這個(gè)范圍,會(huì)移除掉舊的記錄。我們可以更改這個(gè)限制:
config.cache_store = :memory_store, { size: 64.megabytes }
但是多個(gè) Rails 應(yīng)用不會(huì)共享該緩存。它不適合大型的部署,適合小型的,低訪問(wèn)量的應(yīng)用。
config.cache_store = :file_store, "/path/to/cache/directory"
緩存利用文件系統(tǒng)來(lái)存放緩存文件,雖然可以在多個(gè)應(yīng)用間共享緩存,但是不建議在產(chǎn)品環(huán)境下使用。這種方式會(huì)不斷的增加硬盤(pán)使用,直到手動(dòng)清空所有緩存。
Rails 默認(rèn)使用這種方式。
這種方式使用 Memcached 最為后端緩存服務(wù),它提供了高性能的、集中式的緩存服務(wù),可以在多個(gè)應(yīng)用間共享緩存,這是一種適合中大型商業(yè)應(yīng)用的選擇。
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
使用 Memcached 需要安裝 dalli,操作時(shí):
Rails.cache.read('key')
Rails.cache.write('key', value)
Rails.cache.fetch('key') { value }
這是一種適合開(kāi)發(fā)和測(cè)試環(huán)境的配置,它不會(huì)儲(chǔ)存任何東西,但是可以正常調(diào)試 Rails.cache 中的方法。
config.cache_store = :null_store
Redis 作為一個(gè)高性能的內(nèi)存型數(shù)據(jù)庫(kù),也可以作為緩存服務(wù)。我們先安裝 redis 的 gem:
gem 'redis-rails'
gem "hiredis"
增加配置:
config.cache_store = :redis_store, {
host: 127.0.0.1,
port: 6379,
password: 123456,
db: 1,
namespace: "cache" }
現(xiàn)在,越來(lái)越多的 Rails 項(xiàng)目和 redis 配合使用,比如下一節(jié)要介紹的異步服務(wù),還有大量非結(jié)構(gòu)化的數(shù)據(jù),也可以?xún)?chǔ)存在 redis 中。比如站內(nèi)短信息,好友動(dòng)態(tài),或者好友列表,都可以通過(guò) redis 的命令快速實(shí)現(xiàn),較之關(guān)系型數(shù)據(jù)庫(kù)擁有更快的讀寫(xiě)速度,且更適合儲(chǔ)存非結(jié)構(gòu)化數(shù)據(jù)。
非結(jié)構(gòu)化數(shù)據(jù)庫(kù)是指其字段長(zhǎng)度可變,并且每個(gè)字段的記錄又可以由可重復(fù)或不可重復(fù)的子字段構(gòu)成的數(shù)據(jù)庫(kù),用它不僅可以處理結(jié)構(gòu)化數(shù)據(jù)(如數(shù)字、符號(hào)等信息)而且更適合處理非結(jié)構(gòu)化數(shù)據(jù)(全文文本、圖象、聲音、影視、超媒體等信息)。來(lái)自百度百科
我們可以在 Rails 項(xiàng)目?jī)?nèi)部,使用 Rails.cache.fetch 來(lái)讀取緩存,如果不存在,將返回 nil,如果傳入 block,會(huì)將 block 中的結(jié)果寫(xiě)入緩存,并將其返回。比如:
class Product < ActiveRecord::Base
def competing_price
Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do
Competitor::API.find_price(id)
end
end
end
在 fetch 中可以設(shè)置過(guò)期時(shí)間。
更多 API 信息可以查看 這里。