[翻译]来自Rails Envy的Rails Cache教程 part2

本文深入讲解Rails中的Action缓存和Fragment缓存原理及使用方法,包括缓存的生成、读取流程,以及如何针对不同场景进行高效缓存管理和清理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文地址: http://railsenvy.com/2007/3/20/r ... ing-tutorial-part-2

Part1地址: http://www.ruby-lang.org.cn/forums/thread-3158-1-1.html

本教程的编写顺序是按照各个缓存的效率来排序的,Page缓存最快,所以在第一篇教程就介绍了,这篇教程就介绍其它的几种缓存。

Action 缓存

Action缓存和Page缓存十分相似,唯一的区别就是对页面的请求会触及Rails服务器并且filter还是会运行。类似下面代码这样设置Action缓存:

CODE:

class BlogController < ApplicationController
  layout 'base'
  before_filter :authenticate  # <--- Check out my authentication
  caches_action :list, :show
可以从代码中看到,用户必须通过认证才能访问list action,当对list进行请求时可以从 /log/development.log看到如下日志

CODE:

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/目录下缓存文件,而不是像Page缓存那样直接输出.html文件,是输出.cache文件。如果你在单个应用中包含了一个以上的自域名,缓存文件的路径会包含主机和端口(localhost:3000)。这种情况下每个自域名会缓存在各自的目录下。

如果打开“list.cache”文件,就会看到其中的内容是完整的静态html页面,就像Page缓存的一样。那它们之间有什么区别呢?

如果我们再次对页面做出请求(经过上面的请求之后),来看看 /log/development.log

CODE:

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]
如你所见, 前置过滤器Authentication执行了,接着读取了缓存文件,然后输出。所以在这个情况下,依然是输出那个完整的缓存文件,并且还做了用户认证的检查。

这里值得注意的是,action执行前前置过滤器必须被执行。另一方面,你也可能会缓存到一个错误的html文件。

怎样清理Action缓存

如教程Part1中,缓存必须在数据发生变化后被清除掉。这里也是用sweepers,只不过在 /app/sweepers/blog_sweeper.rb中"expire_page" 要改为 "expire_action":

CODE:

# 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)
也可以用以下的rake任务来清除 Action 缓存和 Fragment 缓存:

CODE:

rake tmp:cache:clear这个rake任务会删除所有的.cache文件。

Fragment Caching

使用了缓存之后会让应用变得飞快,但是由于这是动态的web应用,缓存整个页面并且总能命中这是不实际的,所以有了Fragment缓存。Fragment缓存能缓存页面中的一部分。

要对Blog应用中Post列表进行Fragment进行缓存就要编辑 /app/views/blog/list.rhtml

CODE:

<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,来看看第一次和第二次请求中发生了什么:

CODE:

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]
有没有感觉到一些冗余?
在第一次请求时生成了缓存,第二次时读取缓存,但是对Posts的SQL查询进行了两次。我们已经对SQL查询的结果缓存到页面中了,我们在缓存之后不用再进行SQL查询了,是吗?

解决这个问题的其中的一个方法就是编辑 /controllers/blog_controller.rb,在查询前加上一个条件检查:

CODE:

def list
  unless read_fragment({})
    @post = Post.find(:all, :order => 'created_on desc', :limit => 10) %>
  end
end
现在只会在没有缓存的时候才有查询出现。或者有人会想,把这个请求放到view中的cache do代码块中不就好了吗?但是这个是MVC架构啊。

猜猜怎样清除Fragment缓存

CODE:

# Expire the blog list fragment
expire_fragment(:controller => 'blog', :action => 'list')
没错吧?

在分页中使用Fragment缓存

默认情况下,缓存的命名是通过直接查找当前的controller和action名字来决定的。也就是说controller 为 "blog"并且 action 为 "list"就会缓存到 " /localhost:3000/blog/list.cache"。

在分页的情况下,就要把分页的页码加到缓存文件中。

CODE:

blog_controller.rb应该类似下面那样处理:

CODE:

  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决定了前两个参数,这个无法改变,所以可以省略。

/views/blog/list.rhtml如下:

CODE:

<% cache ({:page => params[:page] || 1}) do %>
     ...  All of the html to display the posts ...
<% end %>
这段代码会在第一次访问 /blog/list时,会创建 /localhost:3000/blog/list.page=1.cache 的缓存文件,按理可知访问分页2时会创建缓存文件 /localhost:3000/blog/list.page=2.cache。 Pretty cool!

对Fragment缓存高级命名机制

大多数时候要对cache进行命名,这里有个示例:

话说现代的Web设计中每个页面都有一个用户自定义的导航目录(nav menu)。这里是一个列出用户要完成的项目任务(task)的导航目录:

CODE:

<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>
这个会在站点的每个页面中显示,如果能在用户第一次访问页面时缓存起来,在之后的访问就不用总是打扰数据库了。那就要对代码进行一点点改动:

CODE:

<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

Fragment缓存命名示例
这里有些用户命名缓存的例子:

CODE:

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"
怎样一次sweep多个缓存

上面的分页的Fragment缓存最后生成了很多缓存文件,已知的清除方法只是:

CODE:

expire_fragment(:controller => 'blog', :action => 'list', :page => 1)
expire_fragment(:controller => 'blog', :action => 'list', :page => 2)
expire_fragment(:controller => 'blog', :action => 'list', :page => 3)
.....
但是这里还有一个方法来做这些事情:

CODE:

expire_fragment(%r{blog/list.*})这样会查找blog/list下的所有cache文件。使用这个方法要明白下面几点:

  1. 不能在Memcached(下面会提到)下运行。
  2. 这种方法会对所有的cache文件进行正则匹配,如果你的系统中有4,000+个cache文件,这会让你的系统处理很久。
  3. 如果你不慎写错了正则表达式,那可能会出大问题。
    •  


Action/Fragment缓存的几种缓存机制

Page 缓存只能使用文件系统。Action 和 Fragment缓存就有几种选择:

  1. File Store - (默认) - 把缓存放在磁盘中 (默认在 /tmp/cache/ 目录)。
  2. Memory Store - Rails服务器的处理方法,放在内存。
  3. DRb Store - 使用DRb服务器。
  4. MemCache Store - MemCache是一个高性能的缓存处理程序。注意它不是用Ruby实现的。
    •  


作者认为FileStore是一开始最好的选择,在以后升级为其它的方式(如MemCache)也不难,要改变缓存文件存储的位置可以编辑 config/environment.rb

CODE:

ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory" ActiveRecord查询缓存

这是第四种缓存机制,对这种机制并没有过多的文档,它只出现在 Rails Edge中,现在来介绍一下。

ActiveRecord并不需要做配置,在默认情况下会自动运行。看看这个action:

CODE:

class BlogController < ApplicationController
  def some_complex_thing
      post = Post.find(1)
      
      # .... Do alot of other stuff

      post_again = Post.find(1)
  end
end
在没有AR缓存的情况下,SQL查询会进行两次。现在使用了AR缓存后,在第二次的查询会读取第一次查询时生成的缓存。这样就不用担心在单个的Action中两次相同的查询会查询数据库两次了。

不过这里的例子不够直观,但想像复杂的认证代码。很多时候可能会在多种认证方式中进行用户数据的查询,而且这是发布在几个不同地方的代码中的,如果缓存了结果,无疑对性能有提高。

查询缓存在action中第一次查询时缓存,在action结束时清除掉缓存。这里没有真正的存储,当 inserts/updates/deletes 执行时,全部缓存就会刷新。如果你要在多个actions或users间缓存数据,请使用MemCache。

参考:
关于fragment缓存的失效可以参看martin推荐的 timed_fragment_cache插件
关于AR的缓存,可以看看 这篇文章的, 这篇文章yudi有翻译
 
1. 用户与身体信息管理模块 用户信息管理: 注册登录:支持手机号 / 邮箱注册,密码加密存储,提供第三方快捷登录(模拟) 个人资料:记录基本信息(姓名、年龄、性别、身高、体重、职业) 健康目标:用户设置目标(如 “减重 5kg”“增肌”“维持健康”)及期望周期 身体状态跟踪: 体重记录:定期录入体重数据,生成体重变化曲线(折线图) 身体指标:记录 BMI(自动计算)、体脂率(可选)、基础代谢率(根据身高体重估算) 健康状况:用户可填写特殊情况(如糖尿病、过敏食物、素食偏好),系统据此调整推荐 2. 膳食记录与食物数据库模块 食物数据库: 基础信息:包含常见食物(如米饭、鸡蛋、牛肉)的名称、类别(主食 / 肉类 / 蔬菜等)、每份重量 营养成分:记录每 100g 食物的热量(kcal)、蛋白质、脂肪、碳水化合物、维生素、矿物质含量 数据库维护:管理员可添加新食物、更新营养数据,支持按名称 / 类别检索 膳食记录功能: 快速记录:用户选择食物、输入食用量(克 / 份),系统自动计算摄入的营养成分 餐次分类:按早餐 / 午餐 / 晚餐 / 加餐分类记录,支持上传餐食照片(可选) 批量操作:提供常见套餐模板(如 “三明治 + 牛奶”),一键添加到记录 历史记录:按日期查看过往膳食记录,支持编辑 / 删除错误记录 3. 营养分析模块 每日营养摄入分析: 核心指标计算:统计当日摄入的总热量、蛋白质 / 脂肪 / 碳水化合物占比(按每日推荐量对比) 微量营养素分析:检查维生素(如维生素 C、钙、铁)的摄入是否达标 平衡评估:生成 “营养平衡度” 评分(0-100 分),指出摄入过剩或不足的营养素 趋势分析: 周 / 月营养趋势:用折线图展示近 7 天 / 30 天的热量、三大营养素摄入变化 对比分析:将实际摄入与推荐量对比(如 “蛋白质摄入仅达到推荐量的 70%”) 目标达成率:针对健
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值