上次讲到一篇关于web浏览器的cache ,而本篇要讲述的是web服务器端的cache。
Web服务器端的cache通过减少对web应用程序的访问和对数据库的访问来减少服务端的负载以及提升客户端的响应速度。
Rails本身提供了对web服务器端cache的很好支持,当然还有一些更加方便的plugin如cache_fu 。
Cache需要关注的问题有几点:
1. Cache对象
2. 在何时以及如何清理cache
3. Cache的存储规则
4. Cache的存储方式
在来研究这几点之前,首先打开rails的cache。
打开Rails的cache很简单,只要在环境对应的配置文件config/environments/*.rb中设置即可:
config.action_controller.perform_caching = true
接下来让我们来一点点看一下Rails如何处理这些问题。
Cache对象
Cache对象可以是page,action或者一个fragment。
a. Page cache
Page cache是最高效的cache机制,对页面的请求完全由web服务器处理,而不需要经过rails应用程序。
让我们通过一个例子来看看rails如何处理page cache:
class ProductsController < ActionController caches_page :index def index; end end
caches_page方法启动这个page cache。在第一次访问products/index时,rails会生成一个文件index.html。对于接下来的request,web服务器会用这个文件来响应请求。
缺点:
1. 适用面比较窄,比如对于需要认证的页面就无法处理。
2. Page cache会忽略参数。如果客户端访问/products/list?page=1,rails会把页面缓存成:/products/list.html。如果客户端下次访问/products/list?page=2时,也会返回/products/list.html。
b. Action cache
我们已经看到,page cache不能处理需要认证的页面。Action cache就是为了解决这个问题,action cache与page cache的区别在于:request会经过web server并且被rails application接收,直到所有的before filters被处理。
例子:
class ProductsController < ActionController before_filter :authenticate, :only => [ :edit, :create ] caches_page :index caches_action :edit def index; end def edit; end end
c. Fragment cache
动态web应用程序的页面往往由多个components组成,而每个component经常需要采取不同的cache、expire策略。针对这个问题,rails提供了fragment cache。
比如对于这个页面:
<% Order.find_recent.each do |o| %> <%= o.buyer.name %> bought <% o.product.name %> <% end %> <% cache do %> All available products: <% Product.find(:all).each do |p| %> <%= link_to p.name, product_url(p) %> <% end %> <% end %>
我们没有cache需要实时更新的order部分,而cache了product list部分。
这个cache块会绑定到相应的action,cache页面也会存于相应的action目录下。所以如果一个页面有多个component被cache,则需要添加suffix来区分它们:
<% cache(:action => 'recent', :action_suffix => 'all_prods') do %> All available products: ...
也可以无需绑定到相应的action,而赋予一个全局的key:
<% cache(:key => ['all_available_products', @latest_product.created_at].join(':')) do %> All available products: <% end %>
清理cache
对于上面几种情况,都有相应的cache清理方法。
比如对于page cache和action cache,可以通过在需要清理的action里面调用expire方法来清理:
def create expire_page :action => :index expire_action :action => :edit end
Fragment cache的清理则是这样:
expire_fragment(:controller => 'products', :action => 'recent', :action_suffix => 'all_prods) expire_fragment(:key => ['all_available_products', @latest_product.created_at].join(':'))
但如上面这样到处写expire方法,显然不是最好的方式。Rails针对此提供了sweeper机制:把cache清理移入一个observer类,此类会监测一个对象的变化,并且通过相应的钩子来清理此对象相关的cache。
class StoreSweeper < ActionController::Caching::Sweeper # This sweeper is going to keep an eye on the Product model observe Product # If our sweeper detects that a Product was created call this def after_create(product) expire_cache_for(product) end # If our sweeper detects that a Product was updated call this def after_update(product) expire_cache_for(product) end # If our sweeper detects that a Product was deleted call this def after_destroy(product) expire_cache_for(product) end private def expire_cache_for(record) # Expire the list page now that we added a new product expire_page(:controller => '#{record}', :action => 'list') # Expire a fragment expire_fragment(:controller => '#{record}', :action => 'recent', :action_suffix => 'all_products') end end
Cache sweeper在controller里面就是一个after或者aroud filter:
class ProductsController < ActionController before_filter :authenticate, :only => [ :edit, :create ] caches_page :list caches_action :edit cache_sweeper :store_sweeper, :only => [ :create ] def list; end def create end def edit; end end
Cache的存储规则
Cache的存储规则即如何存储cache文件,即cache的存储路径和文件名。
我们可以告诉rails我们想要的存储路径,当然,大部分的时候rails已经有默认的存储路径。而且在不会产生冲突的时候,我们也不需要重新设定。
Page cache:
class WeblogController < ActionController::Base caches_page :show, :new end
Page cache生成的cache文件路径类似于:weblog/show/5.html 和 weblog/new.html。
Action cache 和 Fragment cache:
Action cache本质上是在fragment cache的基础上加上一个around filter实现的。所以,它的cache文件名命名规则和fragment cache是一样的:根据host和当前路径。所以,一个url: www.somesites.com/lists/show/1 对应的action cache路径是“www.somesites.com/lists/show/1”。
当一个action有多个不同的路由,它们需要cache到不同的路径时,我们可以通过设定:cache_path选项来根据不同的情况指定cache路径:
caches_action :feed, :cache_path => Proc.new { |controller| controller.params[:user_id] ? controller.send(:user_list_url, controller.params[:user_id], controller.params[:id]) : controller.send(:list_url, controller.params[:id]) }
对于action cache,我们需要区分不同路由的情况。而对于fragment cache,我们需要区分同一个action下的多个fragments的情况,或者是当这个action已经有action cache(占用了默认路径)时:
<% cache(:action => "list", :action_suffix => "all_topics") do %> # 这会使cache存储于"/topics/list/all_topics",以区分使用了不同suffix的fragments。 expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
Cache存储方式
Rails提供了多种cache存储方法。
a. ActiveSupport::Cache::MemoryStore
在同一进程的内存中保存cache,所以如果有多个rails application,它们之间不能共享cache。
ActionController::Base.cache_store = :memory_store
b. ActiveSupport::Cache::FileStore
磁盘存储,这是默认的cache存储方式:
ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
c. ActiveSupport::Cache::DRbStore
Cache存储于一个单独的共享DRb进程,所有的服务器都可以和这个进程通信。但需要维护一个额外的进程:
ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"
d. MemCached
这种方式跟DRb相似,并且是最流行的production cache方式。
它有几大特性:
- 群组和负载均衡:多个memcached服务器可以进行负载均衡。
- 支持基于时间的过期机制。
- 等。。。
e. ActiveSupport::Cache::SynchronizedMemoryStore
相较于MemoryStore,这是一种线程安全的cache存储。
f. ActiveSupport::Cache::CompressedMemCacheStore
相较于MemoryStore,它对所cache的文件进行了压缩。
g. 自定义cache存储
ActionController::Base.cache_store = MyOwnStore.new("parameter")
更多内容请参考:http://guides.rubyonrails.org/caching_with_rails.html
http://api.rubyonrails.org/classes/ActionController/Caching/Pages.html
http://api.rubyonrails.org/classes/ActionController/Caching/Actions.html
http://api.rubyonrails.org/classes/ActionController/Caching/Fragments.html
1 楼 jiafuguang 2013-08-14 10:50