Rails 的缓存策略


> Copy from https://devcenter.heroku.com/articles/caching-strategies



做`web`应用开发的,总免不了会遇到一些页面总是诡异的加载慢的情况. 在`heroku` 上如果请求总是耗时长,它会拖住你的 `dynos`, 然后影响到你的整个应用的性能. 使用 new relic 来决定哪些页面或者数据库请求很慢. 测试最长耗时的请求, 如果他们因为数据库或者一些 `api` 的事务的问题就应该使用底层的缓存,如 `Rails.cache.read/write/fetch `来缓存信息

`Heroku` 推荐使用 `automagic` 的缓存库如` cache-money` 或者 `cache-fu`, 我们苦寻了很多的简单实用的缓存方案. 但是并没有满意的. 总体来说,这个缓存是一个应用特指的努力方式, 下面会说到怎么能够出色得完成性能提升.
如果你已经配置了使用 [memcached ](https://devcenter.heroku.com/articles/building-a-rails-3-application-with-memcache)

rails 它将会自动的通过它来缓存 action 和片段.

### Http caching
通过[ http header 来实现缓存](https://devcenter.heroku.com/articles/http-caching-ruby-rails) 是一个技术活儿, 它能够轻松的通过一点简单的代码修改就能够实现.

###页面缓存

Rails有一个内置的[页面缓存机制](http://guides.rubyonrails.org/caching_with_rails.html#page-caching) 它能够在本地文件系统创建文件. heroku 中有一个临时的文件存储,所以 page 缓存是 OK 的. 但是被指望它太多, 你应该使用动作或者片段缓存来实现, 或者可以使用 `Rack::Cache` 作为一个反转代理来避免请求到你的程序.

###动作缓存
如果你的页面需要认证或者前后过滤. 那么这个内容就可以通过 内置的[动作缓存](http://guides.rubyonrails.org/caching_with_rails.html#action-caching) 来搞定

简单得通过添加 `caches_action :<action_name>`  到控制器来开启特定动作的缓存, 如果你的模板包含动态的元素(如头部文件中的用户名邮件地址等) 有能够渲染这个模板即使已经缓存了这个动作的内容, 使用 `:layout => false` 标识来完成这个, 最后, 你还可以使用` expire_action` 这个命令来移除动作缓存当有新数据的时候.

下面的代码会说明上面提到的例子:

```ruby
# products_controller.rb
class ProductsController < ActionController

  before_filter :authenticate
  caches_action :index
  caches_action :show, :layout => false

  def index
    @products = Product.all
  end

  def show
    @product = Product.find(params[:id])
  end

  def create
    expire_action :action => :index
  end

end
```


###片段缓存

片段缓存是一个很棒的机制用来缓存小部件或者部分,它同样使用了 memcache. 举例, 如果你的 app 要 list 商品这样:

```ruby
# index.html.erb
<%= render :partial => "product", :collection => @products %>

# _product.html.erb
<div><%= link_to product, product.name %>: <%= product.price%></div>
<div><%= do_something_comlicated%></div>

```

之后你会轻松的缓存每一个单独的商品的片段通过片段缓存, rails 会自动生成一个缓存 key 如果你穿给它一个 activerecord 对象:

```ruby
# _product.html.erb
<% cache(product) do %>
   <div><%= link_to product, product.name %>: <%= product.price%></div>
   <div><%= do_something_comlicated%></div>
<% end %>
```

另外一个片段缓存机制就是缓存小部件或者其他的页面里分离的部分,不需要经常刷新的部分在每次页面刷新的时候.举个例子, 如果你要 list 最畅销的商品,你就可以缓存这个部件,我们假定我们每一小时刷新一次这个信息.

```ruby
# index.html.erb
<% cache("top_products", :expires_in => 1.hour) do %>
  <div id="topSellingProducts">
    <% @recent_product = Product.order("units_sold DESC").limit(20) %>
    <%= render :partial => "product", :collection => @recent_products %>
  </div>
<% end %>
```
 ### 底层的缓存

底层的缓存通过使用了` Rails.cache` 对象直接就缓存了各种信息.但是呢,这个方式是有成本的,数据库查询或者 api 调用就是一般这样用的.
最有效的方式来实现底层的缓存就是通过使用` Rails.cache.fetch` 方法. 它将会读取一个值,从 cache 里,或者它会执行一段传给它的代码块然后返回结果.

```ruby
>> Rails.cache.fetch('answer')
==> "nil"
>> Rails.cache.fetch('answer') {1 + 1}
==> 2
Rails.cache.fetch('answer')
==> 2
```

考虑到下面的例子,一个查询有一个产品模型, 通过一个类方法,可以返回所有的堆栈信息,还有一个实例方法可以查询商品的价格,数据通过这些方法返回将会是很好的底层缓存实现:

```ruby
# product.rb

def Product.out_of_stock
  Rails.cache.fetch("out_of_stock_products", :expires_in => 5.minutes) do
    Product.all.joins(:inventory).conditions.where("inventory.quantity = 0")
  end
end

def competing_price
  Rails.cache.fetch("/product/#{id}-#{updated_at}/comp_price", :expires_in => 12.hours) do
    Competitor::API.find_price(id)
  end
end
```

注意, 刚才我们生成了一个缓存 key 基于一个对象 model 的 id 和更新 update_at 属性, 这个是一个通用的约束,他有一个好处就是能够作废这个 key, 当产品被更新了之后, 也就是说, 当你使用实例级别的底层的缓存,就使用这个方式来生成 key.