用户形象图片

Ruby on Rails缓存(第二部分)
2007-06-18 11:31

原文http://hi.baidu.com/%D0%C7203/blog/item/05ed4ee98256d53eb90e2d33.html

1、为什么要讲解rails缓存
实施rails缓存,你需要了解最快的一种缓存方式,来储存信息:
1、页面缓存 - 最快
2、Action缓存 - 第二
3、Fragment 片段缓存 - 第三
根据上面的列表,你使用的缓存越多,访问速度越快(译者:对于大型项目来说的确要把速度和性能放到第一位)


2、Action缓存
action缓存和page缓存非常的相似,唯一的不同是,action缓存中,页面请求会执行rails服务和你定义的filters(译者:书上定义是过滤器,这个概念让我迷糊了一阵子,尤其是刚开始接触rails的时候)。执行action缓存,你的controller需要这样写:

class BlogController < ApplicationController
    layout 'base'
    before_filter :authenticate  # <--- Check out my authentication
  caches_action :list, :show
正如你看到的,用户需要进行身份验证,才能看到list这个action,当我们第一次访问list这个action的时候,你可以在/log/development.log中看到:
Processing BlogController#list (for 127.0.0.1 at 2007-03-04 12:51:24) [GET]
Parameters: {"action"=>"list", "controller"=>"blog"}
Checking Authentication
Post Load (0.000000) SELECT * FROM posts ORDER BY created_on LIMIT 10
Rendering blog/list
Cached fragment: localhost:3000/blog/list (0.00000)
Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.00000 (0%) | 200 OK [http://localhost/blog/list]
看到 Cached fragment: localhost:3000/blog/list 这句了吗?表明一个文件已经产生了,你可以在这里发现它:
/tmp/cache/localhost:3000/blog/list.cache
默认情况下,action缓存将会在/tmp/cache/目录下存放缓存文件,这些缓存文件是 .cache 文件,而不是页面缓存的 .html 文件。缓存文件的路径还包括域名和端口号(localhost:3000,测试的默认地址),当我们使用其他的子域名时,每个子域名都会有自己的缓存路径。
如果你打开list.cache这个文件,你会发现里面都是html内容,就像页面缓存一样。那么两者的区别在哪呢?
当我们再次访问这个地址的时候(刚才是第一次访问),你可以看到/log/development.log
Processing BlogController#list (for 127.0.0.1 at 2007-03-04 13:01:31) [GET]
Parameters: {"action"=>"list", "controller"=>"blog"}
Checking Authentication
Fragment read: localhost:3000/blog/list (0.00000)
Completed in 0.00010 (10000 reqs/sec) | DB: 0.00000 (0%) | 200 OK [http://localhost/blog/list]
可以看到,用于用户验证的before_filter已经执行,需要缓存的action也被执行,所以我们产生了一个访问快速的html静态页缓存。我们的应用执行了用户验证。

必须要重点提醒的是,你的 before_filters 要在你的 caches_action 之前,并在你的controller顶部,否则可能得到错误的html缓存。


3、清除Action缓存
就像上一张文章提到的,需要重新缓存(比如添加了新文章),需要让已有缓存过期。我们可以用sweepers做同样的事情。我们只需要将/app/sweepers/blog_sweeper.rb中的expire_page 改成 expire_action:
# Expire the list page now that we posted a new blog entry
expire_action(:controller => 'blog', :action => 'list')
    
# Also expire the show page, incase we just edited a blog entry
expire_action(:controller => 'blog', :action => 'show', :id => record.id)
当然,另一个清除action缓存和片段缓存的方式是执行一个rake任务:
rake tmp:cache:clear
这将删除所有的.cache文件


4、片段(Fragment )缓存
目前位置我们已经处理了缓存整个页面信息的工作。但是对于动态页面,我们并不需要完全这样做。下面介绍一下片段缓存。
片段缓存可以缓存view文件上的一个部分。
为了缓存一个所有blog文章列表,我们来编辑一下/app/views/blog/list.rhtml
<strong>My Blog Posts</strong>
<% cache do %>
    <ul>
      <% for post in @posts %>
         <li><%= link_to post.title, :controller => 'blog', :action => 'show', :id => post %><
/
li>
       <% end %>
    </ul
>

<% end %>
“cache do”将在/tmp/cache/localhost:3000/blog/list.cache生成一个片段缓存文件。并使用当前的controller和action对它进行命名。这样当下一次执行到“cache do”的时候,这个缓存文件将被读取。看一下 /log/development.log 这个文件中记录的第一次和第二次访问的情况吧,仔细看!
Processing BlogController#list (for 127.0.0.1 at 2007-03-17 22:02:16) [GET]
Authenticating User
  Post Load (0.000230)   SELECT * FROM posts
Rendering blog/list
Cached fragment: localhost:3000/blog/list (0.00267)
Completed in 0.02353 (42 reqs/sec) | Rendering: 0.01286 (54%) | DB: 0.00248 (10%) | 200 OK [http://localhost/blog/list]


Processing BlogController#list (for 127.0.0.1 at 2007-03-17 22:02:17) [GET]
Authenticating User
  Post Load (0.000219)   SELECT * FROM posts
Rendering blog/list
Fragment read: localhost:3000/blog/list (0.00024)
Completed in 0.01530 (65 reqs/sec) | Rendering: 0.00545 (35%) | DB: 0.00360 (23%) | 200 OK [http://localhost/blog/list]
看到一些不同了吗?(译者:我用红色标出了。)
第一次,片段被缓存,第二次则读取了改缓存。但是值得注意的是,得到改文章列表的数据库查询语句还是被执行了两次。但是我们希望在读取缓存的时候,不用去再执行数据库查询语句的啊!
注意,我们缓存的仅仅是在 <%cache do%>和<%end%>之间的内容,而其他的内容每次访问还是会被执行的。所以我们可以在/controllers/blog_controller.rb中增加一个条件:
def list
  unless read_fragment({})
    @post = Post.find(:all, :order => 'created_on desc', :limit => 10) %>
    end
end
这样,数据库查询语句只有在缓存页面未被读取的时候执行。当然,我们也可以将数据库查询语句放到“cache do”之间,但是,我们绝对不希望把model中的方法放到view中,这毕竟是mvc框架!(译者:《Rails敏捷开发》在这个问题上可不这么坚定哦!)

5、清除片段缓存
# Expire the blog list fragment
expire_fragment(:controller => 'blog', :action => 'list')
呵呵,上面介绍过的

6、使用片段缓存解决分页缓存
如果我想对我的列表的进行分页,那该如何对每个页面进行缓存呢?
你可能已经意识到,缓存文件的命名是根据他所在的controller和action进行综合命名的。所以当你的controller名称为blog,action为list的时候,你的缓存文件将为/localhost:3000/blog/list.cache
所以缓存分页,需要给缓存一个名字(译者:唯一的名字)。我们的blog_controller.rb将进行改写:
 def list
    unless read_fragment({:page => params[:page] || 1})  # Add the page param to the cache naming
      @post_pages, @posts = paginate :posts, :per_page => 10
    end
  end
当然我可以写 "read_fragment({:controller => 'blog', :action => 'list', :page => params[:page] || 1})" ,但是默认的,rails会默认前两个参数,所以我不用写的那么全面(译者:这是rails的灵活的地方,当然为了让其他人明白或者便于自己记忆,你可以写上。这种做法在rails很多地方有体现)。
改写一下/views/blog/list.rhtml
<% cache ({:page => params[:page] || 1}) do %>
       ...    All of the html to display the posts ...
<% end %
>
这样,当我访问/blog/list的时候,rails将自动缓存/localhost:3000/blog/list.page=1.cache这个文件,当我访问第二页的时候,将会缓存/localhost:3000/blog/list.page=2.cache。Cool!

7、片段缓存的高级命名
大多数时候,我们想给缓存文件起一个名字,那就看一下这个例子吧。

我们需要给我们的每个用户界面增加一个导航条,这个导航条是需要根据每个用户自己设定的项目进行显示的。(译者:用户不同,导航条的功能按钮可能不同)
<div id="nav-bar">
    <strong>Your Tasks</strong>
       <ul>
           <% for task in Task.find_by_member_id(session[:user_id]) %>
             <li><%= task.name %><
/
li>
           <% end %>
        </ul
>

</div>
如果在每个页面显示这个导航条,我还是希望要它能被缓存起来的,这样我可以不同在每个页面都读取一下数据库。但是这个例子中,导航条需要根据不同的用户进行不同的显示,所以我需要对上面的代码进行一个修改:
<div id="nav-bar">
    <strong>Your Tasks</strong>
       <% cache (:controller => "base", :action => "user_tasks", :user_id => session[:user_id]) do %>
       <ul>
           <% for task in Task.find_by_member_id(session[:user_id]) %>
             <li><%= task.name %><
/
li>
           <% end %>
       </ul
>

       <% end %>
</div
>
如果我的user_id是1,那么生成的缓存文件将是/localhost:3000/base/user_tasks.user_id=1.cache,注意:我有一个controller名称为base,里面包含一个名字为user_tasks的action吗?不!这样写只是为了命名方便,这个controller其实不必真的存在的。


8、片段缓存的命名实例
下面是一些其他的片段缓存命名的例子:
cache ("turkey") => "/tmp/cache/turkey.cache"
cache (:controller => 'blog', :action => 'show', :id => 1) => "/tmp/cache/localhost:3000/blog/show/1.cache"
cache ("blog/recent_posts") => "/tmp/cache/blog/recent_posts.cache"
cache ("#{request.host_with_port}/blog/recent_posts") => "/tmp/cache/localhost:3000/blog/recent_posts.cache"

9、同时清理多种片段缓存
上面介绍的分页缓存,将会生成很多的缓存文件,如果想把没一个过期,恐怕要这么来写;
expire_fragment(:controller => 'blog', :action => 'list', :page => 1)
expire_fragment(:controller => 'blog', :action => 'list', :page => 2)
expire_fragment(:controller => 'blog', :action => 'list', :page => 3)
.....
当然简单的方式是:
expire_fragment(%r{blog/list.*})
你可以在blog/list下面发现所有的缓存文件,这种方法显然很简洁,但是有3件事情一定要注意:
1、这种方式对memcache无效!(作者将会有文章进行讲解,他会告诉我的。)
2、在执行的时候,它会检查每一个文件名是否符合这个正则表达式,所以当你删除400多个缓存的时候,这个将会很大程度降低你的系统性能的(译者:还有比删除rails的session文件更恐怖的事情吗?)
3、注意你的正则表达式,写错的话将会删除掉你不想删的东西的。


10、在哪里储存action或者片段缓存
页面缓存,第一部分提到的,只能在文件系统里储存,对于action缓存和片段缓存,倒是可以有几个方案选择。
1、文件系统,默认的,将缓存的信息文件放入你的硬盘。默认在/tmp/cache/ 这个目录里。
2、内存中
3、DRb Store分布式储存,缓存文件将在"Distributed Ruby"处理过程中储存(译者:没太明白!),当然一个ruby进程可以和所有的服务进行沟通。(原文:DRb Store - Cached pages are stored in a "Distributed Ruby" Process. Basically a separate ruby process that all of your servers can communicate with)
4、memcache储存,memcache是专门为缓存设计的,虽然他不是ruby写的,但是他相当的快,很多大型项目在使用他。(原文:It's not written in Ruby, but it's damn fast and it's what all the big boys use)
对于ruby项目,那个方案比较好呢?开始我们可以使用基本的文件储存,当你的网站越来越知名的时候,你可以使用memcache进行缓存处理。
我觉得使用文件缓存是一个很好的把握缓存技术的开始,如果你想改变缓存方式,你只需要对config/environment.rb进行一个修改即可。
ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"

11、ActiveRecord查询缓存
最近,第四种缓存方式将在rails上发布,即“ActiveRecord缓存”或称“SQL查询缓存”。该技术目前尚未正式公开,仅在 Rails Edge 中有介绍。
ActiveRecord 查询缓存的功能,和它的字面意思是一样的。而且你不用对它进行任何的设置处理,因为它默认是启动的。我们可以用一个action做一个实验:
class BlogController < ApplicationController
  def some_complex_thing
        post = Post.find(1)
      
      # .... Do alot of other stuff

        post_again = Post.find(1)
  end
end
在使用ActiveRecord查询缓存前,这种查询会执行两次,但是在使用了查询缓存后,同样的写法,数据库查询只会执行一次,第二次同样的查询将会读取缓存。Cool!但是这需要在同一个action中。

这个例子并不是非常全面的反应查询缓存,但是想想当需要进行身份验证的时候,你可能多次的需要进行数据库查询,已保持用户的验证,(译者:比如根据用户cookies中的id查找并得到当前用户实例),这时,查询缓存将会非常有用!

但是,缓存查询只是在一个单一的action开始的时候生效,在action结束的时候就失效了。这里并未真正的设计到储存,所以当进行了增删改的时候,整个缓存将被刷新。如果你需要在多个action或者用户间缓存同一个信息,还是看一下 memcached 吧。(译者:这方面将会和作者和其他人沟通,争取尽快的拿到好的解决方案)


12、高级缓存的选择
除了上面介绍的三种(很快将是四种)缓存方法,还有一些其他的缓存方法介绍给大家:

如果你不想每次都查询数据库,而需要对一个model进行缓存的话,
Geoffrey Grosenbach有一篇文章 nice tutorial 和Robot Co-Op 的 cached_model ,你可以看一下。(译者:貌似值得一译啊)

如果想缓存一个model中的方法,你可以看看
this library ,作者 Yurii Rashkovskii.

如果你想给action缓存增加一些功能,可以看一下 Action_Cache plugin

译者:如果还有其他的好方法,请和本文作者联系,或者和我联系。

结束语:
缓存对于大型网站系统来说是非常有用的技术,如果你有更好的意见,请与我们联系:
本文作者:
GreggPollack [GreggPollack at Gmail.com], Jason Seifer[jseifer at gmail.com]
本文译者:里克[guxing203 at Gmail.com]

有翻译不对或值得商榷的地方,麻烦留言,大家帮助我进步!
回到帖子顶部