rails - oyaji's Blog

rails fragement cache

之前介紹過 Page Cache,那是一個將所有動態內容都變成靜態頁面,藉由不經過 CGI ,來達到最大覆載度的技巧。但是他的應用性不夠廣泛,僅限於下面的用途


  1. 流量超級大的頁面
    通常是 index page,一個網站可能有幾百個頁面,但是通常 index page 一個頁面就佔了三到五成的流量,而且 index page 通常每個網站都是一個小時,了不起 15 分鐘更新一次,這個時候用 index page 可以大幅度增加網站覆載度。
  2. 頁面修改的頻率遠遠小於讀取的頻率
    例如 Blog 系統,我可能一天寫一篇文章,但是一天讀取我的 Blog 的人可能有幾千個,這個時候為什麼每次讀取頁面的時候都得去 DB 撈資料,組合,Render 出來呢?還不如用靜態頁面儲存,然後每次修改頁面時都重新產生新的 HTML 即可

但是,如果要利用 Page Cache 做到某些部份的功能實在有點麻煩。像是在同一個頁面裡面,有些地方是很少機會修改,希望能夠 Cache 起來,但是有些地方修改頻率超頻繁,不能夠用 Page Cache 來做。這個時候,我們就可以使用 Partial Cache 的方式來加快速度,在Rails 裡面 Partial Cache 叫做 Fragment Cache。

使用方式如下

在 View 裡面

選定要 Cache 的部份,將他用 cache block 包起來

 

<% cache do %>
我們要 cache 的 content
<% end %>
如此,當我們用 production mode 的時候,你就會發現會出現 tmp/cache/你的controller/你的action.cache 這個檔案,也就是 cache 的內容。並且 reload 的時候,那個 block 的 content 都是不變的。

理論上 cache 是 work 的,但是如果你去翻 log 時,你會發現 db 一樣會去做相關的 operation。原因是因為我們並沒有告訴 Rails ,Controller 裡面某段 code 是已經 cache 好的東西,不需要去執行。我們都知道 db 通常是最大的 bottleneck ,如果 cache 沒辦法避免 db operation,那這樣的 cache 也僅僅省去了 render 的時間,並沒有太多幫助。

在 Controller 裡面

為了避免這樣,我們要在 controller 裡面指明,當 Fragment Cache work 時,某些 operation 是可以不用執行的。

 

 

unless read_fragment :action => '這個action的名字'
跟 cache 有關的 operation
只要有 cache ,就不需要去執行的 code.....
end

 

如此如果已經 cache 過了, Rails 就會避開這段 code 的執行。

要如何 Expire Cache 

Expire Cache 也是相當的方便,在 controller 裡面使用 expire_fragment 即可。

 

expire_fragment :action => '這個action的名字'

 

以上都是最最最基本的 Fragment Cache 的機制,但是如果只知道這些東西,根本做不了太多事情,我們下次來談談比較實用的 Fragment Cache 機制。

 
 

上次介紹了如何使用最基本的 Fragment Cache,現在來加上一些小小的小 trick。上次介紹在 View 裡面要這樣使用Fragment Cache

<% cache do %>
我們要 cache 的 content
<% end %>

 

Cache 會出現在 RailsRoot/tmp/cache/你的host/你的controller/你的action.cache 這個檔案裡面。假設上面的 action 是在 lala controller 的 haha action ,那們上面的寫法跟 下面的寫法效果是一樣的

 

<% cache(:controller => 'lala' , :action => 'haha' ) do %>
我們要 cache 的 content
<% end %>

 

簡單講,就是最上面的 cache do 寫法算是預設值,寫不寫好像沒差,當這個頁面只有這裡需要 cache 時,這樣寫就很方便。不過如果這個 haha action 會依照後面接的 id 的不同顯示不同的內容時,你可以很直覺的加上 :id 這個選項

 

<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] ) do %>
我們要 cache 的 content
<% end %>

 

Cache 會出現在 RailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.cache 這個檔案裡面。簡單講,又多了一層以 action 為名的目錄,目錄裡面每個 id 都有自己的 cache file。這樣的作法可以簡單依照變數的不同區分 cache file 。

不過?

但是老實說,到目前為止,這個 fragment cache 用途還是不大。我們一個 Action 只能使用一個 Partial Cache,那如果同一個頁面有兩個以上的地方要 cache 呢?Partial Cache 的概念就是一個網站有很多個 block 可以被許多頁面共用。假設某個頁面有兩個部份要 cache ,一個是 RSS ,另外一個是 Info 這個地方。那我們依照 Rails 聖經本的方法,使用 part 變數供我們使用

 

<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'rss' ) do %>
我們要 cache 的 rss content
<% end %>
<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'info' ) do %>
我們要 cache 的 info content
<% end %>

 

如此,相關的 Cache 會產生在 RailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.part=rss.cache 還有RailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.part=info.cache 這兩個 file 裡面。仔細看就知道,他的命名機制就是 id. part=part_name.cache 這樣的寫法。很簡單吧。

到現在 Partial Cache 才從還好變成好用的階段。read_fragment 或是 exprie_fragment 都是使用

 

:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'info' 

這樣的命名機制來操作,不難吧。

還有嗎?

到現在為止,我還沒看過 :part 還不夠的情況,不過如果真的覺得用 controller , action , id , part 都不夠區分你的 cache ,還有最後一招

 

<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'rss' , :part2 => 'rss'
, :part3 => 'rss' .... ) do %>
我們要 cache 的 rss content
<% end %>

簡單講,其實 :part 只是聖經本這樣寫,所以在 Rails Fragment Cache 機制裡,id 以後的 fragment 命名方式不限制,而且數量不限制,只要你不嫌打字太累,你大可以用幾百層去命名你的 cache 。出來的 cache file 命名規則是這樣 ailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.part=rss&part2=rss&part3=rss.cache。總之隨便你怎麼命名 :p

 

后续看到的文章:
×××××××××××××××××

I am using Devise as authenticating solution in Rails and I have a cached fragment :recent_users.

I want this fragment to expire when a new user is registered, changed or removed, so I put in my(manually created) users_controller.rb

 

class UsersController < ApplicationController
    cache_sweeper :user_sweeper, :only => [:create, :update, :destroy]
...

But my fragment does not expire when new creates or changes.

My user_sweeper contains basic prescriptions

 

class UserSweeper < ActionController::Caching::Sweeper
observe User

def after_save(user)
   expire_cache(user)
end

def after_destroy(user)
  expire_cache(user)
end

private
  def expire_cache(user)
    expire_fragment :recent_users
  end
end

What am I doing wrong?

Problem solved!

I followed this steps and everything works:

 

$ mkdir app/controllers/users
$ touch app/controllers/users/registrations_controller.rb

In registrations_controller.rb

 

class Users::RegistrationsController < Devise::RegistrationsController
  cache_sweeper :user_sweeper, :only => [:create, :update, :destroy]    
end

Problem was that Registrations in Devise is a separate controller.

rails model associations

 

Rails中ActiveRecord finders不允许我们在include预加载关联对象时使用select。
 
例如这样的情况:
 
class Student < ActiveRecord::Base
   has_many :calls
   has_one :call
end

class Call < ActiveRecord::Base
   belongs_to :student
end
希望在显示student时,显示它的最近更新的call的日期。
 
改善前:
 
@students = Student.all(:include => [:call])
改善后:
 
#include eql left outer join
@students = Student.all(:select => "students.id, students.login, students.called_times,
calls.updated_at as last_called_at", :joins => "left outer join calls on students.id = calls.student_id")
 
前几天在rails performance turning中发现:
跨数据库的应用中在model层指定associations,以及用include实现的提前装载,
和through实现的多表关联,都不起作用。
但奇怪的是在console中却能正常的取到值。。。求解
 
 

rails cache 学习

 

在Rails中做缓存是简单的,要开启cache的话

config.action_controller.perform_caching = true 

默认情况下只有production是true,其他ENV都是false。

Rails中存储cache的方式多了去了,现在用的最多的应该是memcache吧。rails的guildes中有句Page caches are always stored on disk,貌似是所有的page cache都用filestore?在environment.rb中指定使用的store模式

ActionController::Base.cache_store = :file_store, "/path/to/cache/dir" 

只要上面两句话都配置好了,下面就简单的在需要cache的地方加几条语句就成

1、Fragment cache

就是缓存一个片段,将需要缓存的内容放入一个block中即可

<% cache do %>
  something to cache
<% end %>

如果要expire一个片段可以用expire_fragment方法,

expire_fragment(:controller => 'articles', :action => 'list')

2、Page cache和Action cache

class Article < AR::Base
  before_filter :authenticate, :only => [:edit, :create]
  caches_page :index
  caches_action :edit

  def index;end
  def edit;end
  def create
    expire_page :action => 'index'
    expire_action :action => 'edit'
  end
end

貌似caches_action和caches_page的区别也就只是action需要request经过一次rails stack,那也就能给要cache的action加点filter了;而page的话就完全不会经过rails了,直接就访问静态缓存的文件去了。。。

3、使用Sweeper管理cache

Swepper就是一个Observer,会在监视到一个特定的Model发生某些特定的callback时执行特定的活动,比如expire缓存

class ArticleSweeper < AC::Caching::Sweeper
  observe Article
  def after_create
    expire_page :controller => 'articles', :action => 'index'
    expire_action :controller =>' articles', :action => 'edit'
    expire_fragment :controller => 'articles', :action => 'list'
  end
end

4、浏览器端的缓存

通过stale?方法来判断Headers中的etag和last_modifiled是否与server上的一致

def show
  @article = Article.find(params[:id])
  if stale?(:etag => @article, :last_modifiled=>@article.updated_at.utc)
    respond_to do |format|
    end
   end
end

然后就是memcache相关的了,有时间再记录

 

 

cache and sweeper in rails

 

以关键字来分组显示列表,往往因为查询的数据量大,导致页面打开慢。使用cache action可以大大提高速度。

需要通过sweeper监控model的增删改动作,定时清理缓存。

models:

 

 

class TagPage < Page
  has_one :extra, :class_name => "TagPageExtra", :autosave => true
end
 
class TagPageExtra < ActiveRecord::Base
  belongs_to :tag_page
end

TagPage是扩展了以前的代码,对于新增的属性,通过TagPageExtra来处理。

controller:

class TagPagesController < ApplicationController
  caches_action :list
 
  def list
    @pages = TagPage.all(:select => "pages.id, pages.title, pages.description", :include => [:extra],
: order => "tag_page_extras.position")
  end
 
end
sweeper:
#文件放在 app/sweepers文件夹下
#并在environment中添加 文件路径
onfig.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
 
class TagPageSweeper < ActionController::Caching::Sweeper
  observe TagPage
 
   def after_create(obj)
    expire_cache_for(obj)
  end
 
  def after_destroy(obj)
    expire_cache_for(obj)
  end
 
  def after_update(obj)
    expire_cache_for(obj)
  end
 
  private
  def expire_cache_for(obj)
    expire_action(list_tag_pages_path)
  end
end
 
#注意要在application controller中声明 才会起作用
class ApplicationController < ActionController::Base
  cache_sweeper :tag_page_sweeper
end

view:

 

<%= t("records_not_found")  if @pages.blank? %>
<dl>
  <% @pages.group_by(&:description).each do |description, pages| %>
  <dt><%= link_to description.nil? ? "<b>null</b>" : "<b>#{description}</b>", tag_pages_path(:tag => description) %></dt>
  <dd>
    <ul>
    <% pages.each do |page| %>
      <li class="tag"><%= link_to page.title, edit_tag_page_path(page) %></li>
    <% end %>
    </ul>
  </dd>
  <% end %>
</dl>
 

结果:
ProcessingTagPagesController#list (for xxx.xxx.xxx.xxx at 2010-07-08 09:24:03) [GET]
Filter chain halted as [#<ActionController::Filters::AroundFilter:0xb60d27d8 @identifier=nil,
@kind=:filter, @method=#<Proc:0xb79b6ba0@/usr/lib/ruby/gems/1.8/gems/actionpack-2.3.4/lib/action_controller/caching/actions.rb:64>,
@options={:only=>#<Set: {"list"}>, :if=>nil, :unless=>nil}>] did_not_yield.
Completed in 8ms (View: 0, DB: 0) | 200 OK [http://www.xxxx.com/tag_pages/list]
这次直接读取了缓存 DB查询为0.

item_list

 
****************************

来自IBM 的文章:

真实世界中的 Rails: 

rails中的缓存    高级页面缓存    优化ActiveRecord

这个文章超好,尤其是第一篇**************

×××××××××××××××××

 

Rails缓存总览:Caching with Rails: An overview
 
大家都会用到缓存。本指南将会告诉你如何减少数据库的访问次数并且在很短时间内向用户提供他们要的东西。
 
缓存基础 Basic Caching
 
这是一个不适用任何第三方插件的关于rails自带的3种类型的缓存技术的介绍。
 
在我们开始之前,确保 config/enviroments/*.rb 中的development.rb 中 config.action_controller.perform_caching 被设置为True,默认状态下, 开发环境和测试环境这行都被注释掉的,只有生产环境中是默认被打开的。
 
Ruby代码 
config.action_controller.perform_caching = true  
 
1.1 页面缓存 Page Caching
 
页面缓存是Rails机制,允许只通过web服务器调用一个已经实际生成的页面,而不需要通过rails堆栈来呈现给用户,速度超级快。不幸的是,他不能提供很多情况下的支持,比如一个需要验证用户身份的页面的情况。并且在页面缓存中,web服务器直接调用文件系统种的一个页面缓存文件,因此还需要建立缓存失效策略,比如你更新了应该显示的页面内容等等。
 
那么,你要如何来使用这个超级快的缓存呢?简单,假设你有一个叫ProductsController的控制器 和 一个会列出所有商品的叫 list 的action。
 
Ruby代码 
class ProductsController < ActionController  
  caches_page :index  
  
  def index  
  end  
end  
 
当products/index第一次被访问的时候,Rails会创建一个名为index.html的文件,在那以后,web服务器会在下次收到products/index访问请求的时候直接调用那个静态文件。
 
默认情况下,页面缓存的文件夹在rails根目录的 public文件夹下。这个可以通过改变配置
 
Ruby代码 
setting config.action_controller.page_cache_directory  
 
来设置。修改默认的/public存储位置有利于在你可能想他别的静态文件放在public中的时候避免文件名的命名冲突。修改这个设置的同时,还需要配置你的web服务器,否则他会找不到你的缓存文件。
 
页面缓存机制会为没有扩展名的页面请求自动的添加.html扩展名,来让web服务器很容易的找到这些页面。这个也可以通过改变配置文件来进行设置:
 
Ruby代码 
config.action_controller.page_cache_extension  
 
为了在新添加商品后,让页面缓存失效,你可以像这样来写你的products控制器:
 
Ruby代码 
class ProductsController < ActionController  
  
  caches_page :index  
  
  def index; end  
  
  def create  
    expire_page :action => :index  
  end  
  
end  
 
如果你想要一个更复杂的缓存失效计划,你可以使用缓存清道夫(sweepers)来让当缓存原来的对象改变时让缓存过期。这个在在Sweepers那节有讲述。
 
注意:页面缓存会无视所有的参数,所以 /products/list?page=1 在缓存文件系统中生成的文件是 /products/list.html。如果有人访问/proucts/list?page=2 ,那么他得到的结果和 page=1 是一样的。所以你应当注意页面缓存中URL使用GET参数的情况。
 
1.2 动作缓存 Action Caching
 
不能使用页面缓存的情况之一就是你不能将其用于那些需要身份验证的页面。这个时候你就可以使用动作缓存。动作缓存的工作原理和页面缓存差不多,只是 web请求会从web服务器传递给Rails解析器和Action Pack,这样以来过滤器就可以在缓存被调用以前生效。这样你就可以使用身份验证和别的一些限制,同时输出缓存副本。
 
清除Action缓存和页面缓存的方式完全相同。
 
比方说,你只是想验证用户编辑或者创建一个产品,但是仍然缓存这些页面:
 
Ruby代码 
class ProductsController < ActionController  
  
  before_filter :authenticate, :only => [ :edit, :create ]  
  caches_page :index  
  caches_action :edit  
  
  def index; end  
  
  def create  
    expire_page :action => :index  
    expire_action :action => :edit  
  end  
  
  def edit; end  
  
end  
 
你还可以使用 :if(或者 :unless) 来传递一个 指定何时应缓存 action 的 Proc。此外,你还可以使用 :layout=> false 使缓存没有布局,这样以来布局模板中的动态信息比如已经登录的用户的用户名或者是购物车中商品的数量可以被保留。使用这项功能需要Rails2.2及更高 版本。
 
你可以传递一个:cache_option选项来修改默认的缓存路径。这会直接传递给 ActionCachePath.path_for 。这对于有多个可用路由并且需要做不同缓存的情况非常有用。如果指定了一个代码块,那么他将被当前控制器实例所调用。
 
最后,如果你在使用 memcache,你还可以传递 :expires_in 。事实上,所以 caches_action 没用到的参数都会被发送给底层缓存存储。
 
1.3 片断缓存 Fragment Caching
 
如果我们只需要把一个页面或者一个action的内容缓存起来然后发给用户那该多好。不幸的是,动态web应用通常都由很多个部份组成,他们具有不 同的页面特征。为了解决此类的动态创建的页面,在页面的不同部份需要建立不同的缓存和缓存失效机制。Rails提供了一种叫片断缓存的东西来解决这个问 题。
 
片断缓存允许视图逻辑的一个被缓存块包裹起来的片断形成一个缓存,在下次受到请求的时候发送出去。
 
举个例子,如果你想即时的显示你的网站中的全部订单并且不想在那个部份使用缓存,但是想缓存页面上列出全部商品的列表,你可以使用这段代码:
 
Ruby代码 
<% 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 %>  
 
我们示例中的缓存块将绑定到调用他的那个action并且和acion缓存放在相同的路径下。这意味着如果你的每个action中有多个缓存片断,你就应当为cache调用提供一个 action_suffix
 
Ruby代码 
<% cache(:action => 'recent', :action_suffix => 'all_prods') do %>  
  All available products:  
<% Product.find(:all).each do |p| %>  
    <%= link_to p.name, product_url(p) %>  
  <% end %>  
<% end %>  
你可以使用expire_fragment 来使缓存过期:
 
Ruby代码 
expire_fragment(:controller => 'products', :action => 'recent',   :action_suffix => 'all_prods  
 
如果你不希望缓存块绑定到调用它的那个action,也可以使用全局标示片段。像这样,通过一个键来调用缓存方法:
 
Ruby代码 
<% cache(:key => ['all_available_products', @latest_product.created_at].join(':')) do %>  
  All available products:  
<% end %>  
 
这个片段就可以在Products控制器的所有actions中使用键来调用并且使用相同的方法来使之失效:
 
Ruby代码 
expire_fragment(:key => ['all_available_products', @latest_product.created_at].join(':'))  
 
1.4  扫地大妈 Sweepers
 
缓存扫地大妈 是一种允许你把代码中的一大堆 expire_{page,action.fragment}调用放在一起的机制。这是通过把所有的清除缓存内容的工作都移动到 ActionController::Cacheing::Sweeper 类种来实现的。这个类是一个通过回调监控一个对象状态改变的监视器。当发生改变的时候,他就通过那个对象的前后过滤器或后置过滤器来让那个对象的缓存失 效。
 
继续我们的Product控制器示例,我们可以像这样通过sweeper来重写他:
 
Ruby代码 
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  
 
The sweeper has to be added to the controller that will use it. So, if we wanted to expire the cached content for the list and edit actions when the create action was called, we could do the following:
 
扫地大妈已经被添加到那些会用到他的控制器中。所以,如果我们想要在create action 被调用的时候,清除list 和 edit 两个action的缓存内容,我们可以这样做:
 
Ruby代码 
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  
    expire_page :action => :list  
    expire_action :action => :edit  
  end  
  
  def edit; end  
  
end  
 
1.5 SQL缓存   SQL Caching
 
查询缓存是一个对每个查询返回的数据集进行缓存的一个Rails特色。如果Rails在本次请求种再次发起了相同的查询,他就会使用被缓存起来的结果集而不需要再次对数据库发出请求。
 
Ruby代码 
class ProductsController < ActionController  
  
  before_filter :authenticate, :only => [ :edit, :create ]  
  caches_page :list  
  caches_action :edit  
  cache_sweeper :store_sweeper, :only => [ :create ]  
  
  def list  
    # Run a find query  
    Product.find(:all)  
  
    ...  
  
    # Run the same query again  
    Product.find(:all)  
  end  
  
  def create  
    expire_page :action => :list  
    expire_action :action => :edit  
  end  
  
  def edit; end  
  
end  
 
在上面的list 这个action,Product.find(:all)返回的结果会被缓存起来,下次再发起这个finder调用的时候就不需要再次读取数据库了。
 
1.6  缓存存储 Cache Stores
 
Rails 提供为action 缓存 和 片段缓存的数据提供很不同的存储方法。页面缓存通常是被存在磁盘上的。
 
Rails2.1 以及更新版本提供了可以缓存字符串的 ActiveSupport::Cache::Store 。一些像MemoryStore的缓存存储可以缓存任意的Ruby对象,但不要指望每个缓存存储都可以这样做。
 
钢轨 2.1 和以上提供 ActiveSupport::Cache::Store,可用于缓存的字符串。 一些缓存存储像 MemoryStore 的实现,能够缓存任意的 Ruby 对象,但不要指望能够这样做的每个缓存存储区。
 
默认的Rails包含的缓存存储提供了:
 
1) ActiveSupport::Cache::MemoryStore:一种用相同进程把所有的东西存储到内存中的缓存存储实现。如果你运行了多个 Ruby on Rails服务进程(比如 你在同时使用 mongrel_cluser 或者 Phusion Passenger),然后这意味着你的Rails服务进程实例不能够彼此共享缓存数据。如果你的应用不执行手动的缓存失效(如比你用缓存键),然后使用 MemoryStore是可以的。否则,仔细考虑是否您应该使用此缓存存储。
 
MemoryStoreis 不仅仅可以存储字符串,而且也可以存储任意Ruby对象。MemoryStoreis不是安全线程。如果你需要安全线程,请改用SynchronizedMemoryStore 的内容。
 
Ruby代码 
ActionController::Base.cache_store = :memory_store  
 
2) ActiveSupport::Cache::FileStore:缓存数据存放在磁盘上。这个默认的存储,路径是/tmp/cache. 在所有的环境下都工作得很好,并且允许所有的线程从相同应用程序文件夹中访问缓存内容。如果/tmp/cache不存在,那么默认的存储方式变成 memoryStore.
 
Ruby代码 
ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"  
 
3) ActiveSupport::Cache::DRbStore: 缓存的数据存储在一个所有服务可以与之通信的单独的共享DRb进程中。这适用于所有环境,并为所有进程保持一个缓存。但是这就要求你运行并管理一个单独的DRb线程。
 
Ruby代码 
ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"  
 
4) MemCached store: Works like DRbStore, but uses Danga’s MemCache instead. Rails uses the bundled memcached-client gem by default. This is currently the most popular cache store for production websites.
 
4) MemCached store:
 
未完---待续
 

rails cache managment

Rails 自身提供四种缓存方式,即 Page Cache, Action Cache,Fragment Cache 和 ActiveRecord Cache 这三种缓存。Page Cache 是最高效的缓存机制,他把整个页面以静态页面 HTML 的形式进行缓存,这对于不会经常发生变化的页面是非常有效的。Action Cache 是对某个 action 进行缓存,与 Page Cache 的区别在于:HTTP 请求会经过 Rails 应用服务器,直到所有的 before filters 都被处理,这种缓存就能处理 Page Cache 无法处理的如需要登录验证的页面,可以所验证的步骤加入 before filter 中,Fragment Cache 则为了缓存页面中的某一部分,同一个页面中的不同部分还可以采用不同的过期策略。对于 Rails 本身的缓存机制,我们可以写 sweeper 进行过期和清除的处理。ActiveRecord Cache 则是较新版本 Rails 中新推出的对 ActiveRecord 的缓存机制,使用 SQL 查询缓存,对于同一 action 里面同一 SQL 语句的数据库操作会使用缓存。

Rails 的缓存机制能非常有效的提升网站性能,Rails 默认是将缓存存在于文件系统中,这并不是适合生产环境下的存储方式,文件 IO 的效率有限,Rails 还支持在同一进程的内存中保存 Cache,但如果有多个 Rails application,它们之间不能共享缓存。我们这里推荐的是以 MemCached 的方式进行存储,这也是目前是流行的缓存存储方式。

Memcached 是由 Danga Interactive 开发,用于提升 LiveJournal.com 访问速度的。LiveJournal.com 每秒有几千次动态页面访问量,用户 700 万。Memcached 是一个具有极高性能的分布式内存对象缓存系统 , 基于一个存储键 / 值对的哈希表。其守护进程(daemon)是用 C 写的 , Memcached 将数据库负载大幅度降低,更好的分配资源,更快速访问。可以用各种其它语言去实现客户端。上文的介绍中已经安装了 Rails 的 Memcached 客户端,因为我们只需要在 Rails 应用程序中做如下配置:

				 
 config.cache_store = :mem_cache_store, 'localhost:11211'

便可以进行使用 MemCached 进行缓存数据。除了 Rails 本身的缓存机制,我们还直接用 Rails.cache 操作 Memcached 进行数据缓存,如,我们读取所有 blog 的数量,可以如下使用缓存:

				 
 blogs_count = Rails.cache.fetch("blogs_count") do 
    Blog.count 
 end 

Rails 自身的 ActiveRecord 作用有限,只适用同一个 action 中的 SQL 查询语句进行缓存,我们需要一个更强大的 ActiveRecord 缓存,而 cache-money 更是为了解决如此问题而推出的。当 twitter 网站变得越来越稳定,逐渐摆脱被人拿来作为"Rails 无法扩展的"典型例子的阴影时,人们便期待 twitter 开发团队能向 Rails 社区有更多的贡献,cache-money 便是在 Starling 之后 twitter 团队贡献出来的另一个插件。cache-money 和 Hibernate 的二级缓存类似,是一个读写式(write-through)缓存。在 ActiveRecord 对象更新的时候不是将缓存中的数据清除,而是直接将更新的内容写入到缓存中去。

cache-money 有许多很棒的特性,如:缓存自动清除机制 ( 利用 after_save/after_destroy) ;支持事务,由于 Rails 的 Active Record 没有提供 after_commit 机制,目前常见的缓存插件在高并发下会出现缓存更新竞争冲突,而这个特性对于解决这个问题会很有帮助,可以通过 gem 来安装 cache-money:

				 
 gem sources -a http://gems.github.com 
 sudo gem install nkallen-cache-money 
 require 'cache_money'

 

				 
 production: 
  ttl: 604800 
  namespace: ... 
  sessions: false 
  debug: false 
  servers: localhost:11211 

 development: 
   .... 

 

				 
 config = YAML.load(IO.read(File.join(Rails_ROOT, "config", "Memcached.yml")))[Rails_ENV] 
 $memcache = MemCache.new(config) 
 $memcache.servers = config['servers'] 
 $local = Cash::Local.new($memcache) 
 $lock = Cash::Lock.new($memcache) 
 $cache = Cash::Transactional.new($local, $lock) 
 class ActiveRecord::Base 
  is_cached :repository => $cache 
 end 

使用 cache-money 非常方便,不需要额外的操作,只需要在你的 Model 里面进行简单的配置,如:

				 
 class User < ActiveRecord::Base 
  index :name 
 end 
 class Post < ActiveRecord::Base 
  index [:title, :author] 
 end 
 class Article < ActiveRecord::Base 
  version 7 
  index ... 
 end 

然后便可以跟以前一样使用 Rails ActiveRecord 各种方法以及事务操作。如果你改变了数据库的表结构,你可以改变 Model 的版本号来使以前的缓存失效,而不需要重启 Memcached 服务器。

 

****************************

 

选用 Session 容器

Rails 提供了几个内建的 Session 容器。在所有我分析过的应用程序里,要么使用了将 Session 信息储存在你文件系统上独立文件的 PStore,要么用了和数据库打交道的 ActiveRecordStore。这两个方案都不甚理想,特别是拖累了缓存 Action 的页面(action cached pages)。这里提供两个好用得多的备选方案供大家参考:SQLSessionStore 和 MemCacheStore

SQLSessionStore 通过以下手段避免了 ActiveRecordStore 相关的额外性能开支:

  • 避免使用事务(对于 SQLSessionStore 的正确操作,它们并不是必要的)
  • 撤销向数据库更新“created_at”和“updated_at”的操作

如果使用 MySQL,你应当保证使用 MyISAM 表来存储 Session 数据,因为它要比 InnoDB 表快很多,并且它不会强制你使用事务处理。前不久我又为 SQLSessionStore 增加了 Postgres 支持,不过,用于 Session 存储 Postgres 看起来要比 MySQL 慢得多。因此,如果你打算使用基于数据库的 Session 存储,我推荐为 Session 表安装 MySQL 数据库(我想不出一个基于 session id 的需要连接的更好用例(use case))。

MemCacheStore 要比 SQLSessionStore快得更多。我的测评结果显示,对于缓存了 Action 的页面它能够带来了 30% 的速度提升 。你得先安装 Eric Hodel 的 memcache client ,并在 environment.rb 中做相应配置之后才能正常使用。注意:Ruby-Memcache 还是不要去试的好(实在、实在慢得让人难以忍受)。

在我自己的项目中,我更倾向于使用基于数据库的 Session 存储,原因是可以使用 Rails 命令行或者数据库软件包提供的管理工具进行简单得管理。对于 MemCacheStore 你就得自己为它编写脚本了。另一方面,对于高访问量网站来说,内存缓存方式的扩展性更好一些,并且它随支持 Session 超时(session expiry)的 Rails 一起提供。

 

 

Session优化
如果你的系统需要为每个访问者保存单独的Session信息(比如购物网站),那么session的存取速度将是影响系统性能的关键因素,目前可用的session存取策略有:
内存,快,相当快!但是如果你的应用挂了,或者由于其它什么原因需要重启,那么所有的session信息都会丢失,并且这种方式仅仅只能在单APP Server的应用中使用
文件系统,很容易使用,每个session对应一个文件,并且可以通过NFS或者NAS轻松进行容量扩展,但是速度较慢
数据库/ActiveRecordStore,使用简单(Rails的默认策略),但是很慢
数据库/SQLSessionStore,与上面一种方式类似,但是使用原始SQL取代了ActiveRecord,性能有一定提升,关于SQLSessionStore与ActiveRecordStore的对比可以参看这篇文章
memcached,比SQLSessionStore稍微快一些,可扩展性较好,但是较难获取统计信息,关于memcached与SQLSessionStore的对比,请参看这篇文章
DrbStore,在memcached不支持的一些平台上,可以选择DrbStore,但是性能比memcached要差一些,并且不支持session自动清除。
 
 

todo list

 

Comparing Open Source Reporting Tools for Use in the Enterprise
 

http://olex.openlogic.com/wazi/2008/open-source-reporting-tool-comparison-for-the-enterprise/

How to Integrate JasperReports with Ruby on Rails

http://wiki.rubyonrails.org/rails/pages/howtointegratejasperreports

 

SED&AWK学习资料

http://www.bathome.net/thread-1929-1-1.html

rails resource汇总贴

from http://wiki.jameswu.me/index.php/Ruby

一般性站点

 

英文资源

Rails现实中的应用系统
很有用,至少可以了解到有哪些站点用到了Rails
RoR官方站
既然是官方站点,必然是要经常看看的了
RoR官方教程
相当不错的文档,后面还是需要多多学习
Ruby Enterprise Edition
RoR运行在这个平台上,效率会有很大的提高。
RubyGems
 
Wiki上的RoR
 
RoR API文档
 
RoR的23个应用程序
 
RubyInside
 
Rails Magazine
 
Rails插件
官方的插件平台,可以搜索需要的插件
RubyInstaller
在Windows上使用Ruby,那么安装这个是最为方便的

工具

Html/XML

Nokogiri
一个据说比Hpricot快7倍的库

正则表达式

Ruby正则表达式
这是一个在线的正则表达式分析工具,相当不错
Ruby Expression
Ruby表达式收集,蛮多的
 

单元测试

RSpec
这算是新一代的测试框架了,超越了JUnit

中文资源

官方的中文Ruby论坛
 
JavaEye的Ruby论坛
 
ChinaOnRails
 
Rails小抄
 

编辑器相关

Rails开发工具之Vim
看起来不错,有很多人跟帖,里面有配置好的配置文件
还是得说说vim的强大

 

Rails

主机托管

Rails Machine
 
SoftLayer
看起来更加专业的一个Rails托管主机商,应该比RailsMachine更加强大。因为[1] 英文版版 里面就是从Railsmachine迁移过去的
Capistrano
Rails部署的必然选择,采用这个可以大幅度降低部署工作量

有用链接

Rails性能问题

{|ihower.tw| blog }
台湾的,下面的链接也是这个人写的。这里面关于Rails3的文章很多
Ruby on Rails 實戰手冊
台湾的一份Rails实战手册,相当不凑
http://blog.xdite.net/
另外一个台湾的Rails博客
Rails Cheat Sheet
所有关于Rails的常用命令都将搜罗进来
Capistrano
自动化部署工具
heroku
Rails部署平台

Rails方案

图表类

  1. Rails 图表解决方案汇总 -- 总结得比较全面,基本上已经满足需求

JQuery

rails和jquery ajax的结合

Beginners guide to Jquery + Ruby On Rails

REST on Rails指南5: respond_to

 

通过上一讲,我们已经对REST on Rails的基本框架有所了解,但是它是如何实现根据客户端的请求类型来返回不同类型的资源表示的呢?这就是我们这一讲所要讲的,秘密就在于respond_to。

首先让我们来看看我们在上一讲中生成的airports控制器的代码:

class AirportsController < ApplicationController
  def index
    @airports = Airport.find :all
    respond_to do |format|
      
format.html # do nothing, allow Rails to render index.rhtml
      format.js # do nothing, allow Rails to render index.rjs
      format.xml { render :xml => @airports.to_xml }
    end
  end
end

我们以index方法为例,其它方法的实现大同小异。第一行代码很容易理解,获取所有的机场信息,但是接下来的代码就比较费解了,而这也正是REST on Rails的关键所在,那个respond_to是做什么的呢?

我们知道,在HTTP协议中,客户端会在他们的HTTP首部包含一些元信息(meta-information),这些元信息按照“字段:值”的方式来组织,HTTP协议预定义了很多标准字段,其中的一个字段就是“Accept-type“,它代表发送请求的客户端能够支持或者说理解的资源表示类型,如果没有为这个键指定值,服务端会认为客户端能够理解标准的HTML文档,当然,客户端可以为这个字段指定任意的符合MIME规范的类型值,假设客户端设置这个字段为”Accept-Type: text/xml“,则服务端必须返回资源的XML表示。

所以respond_to事实上就是根据HTTP首部的Accept-Type字段来决定向客户端返回那种类型的资源表示,如果不使用respond_to,我们的实现可能会是这个样子:


class AirportsController < ApplicationController

  # Pretend that Rails will call our index action,
  # and will pass in the value of the Accept-Type header
  def index(client_format)
    @airports = Airport.find :all

    if client_format == “text/html”
      # TO DO: render the default template

    elsif client_format == “application/javascript”
      # TO DO: return some javascript

    elsif client_format == “application/xml” || client_format == “text/xml”
      # TO DO: return some XML back the client

    # … more elsif statements here for each MIME type you want to support
    end
  end
end

这很丑陋,不是吗?但它却相当直观,我想respond_to的作者可能最初也是这么写的,或者这段代码至少在他的脑海中闪现过,但立刻就被他否定了,因为它实在是太蹩脚了,所以他对这段代码进行了重整,于是有了respond_to。

respond_to do |format|
  
format.html # do nothing, allow Rails to render index.rhtml
  format.js # do nothing, allow Rails to render index.rjs
  format.xml { render :xml => @airports.to_xml }
end

但是Block内的代码看起来仍然比较古怪,事实上,如果我们理解了respond_to的设计思想,那么这段代码看起来就非常理所当然了。

respond_to基于这样的思想设计的,你不需要知道客户端的请求到底是那种类型,你只需要告诉Rails你准备支持那些类型的请求,Rails会自动帮你处理剩下的事情。

所以,这里我们告诉Rails,对于HTML和JS类型的请求,采用默认的实现,而对XML则使用我们在Block内提供的实现。

指南到这里就结束了,篇幅有限,我们只能对REST的基本概念和它在Rails中的简单实现做一个基本的介绍,REST on Rails的世界还有更多的东西等着你去探索。

我建议你尝试动手创建一个Rails应用,然后试试scaffold_resource生成器,阅读并试着理解生成的代码,然后尝试修改view和controller,事实上,比你想象的要简单的多,不是吗?

祝你好运同时期待你的反馈!

REST on Rails指南4:路由

通过上一讲我们了解到,RESTful设计的关键就是定义系统中的资源,这一讲我们将学习在Rails中,如何将请求路由到我们的资源,以及我们应该如何来处理它。

不过,有一点需要先说明:REST并不是Rails的一部分,在Rails出现之前,REST的概念已经存在很多年了,并且REST的应用也并不局限于Web,事实上,它也可以应用到其它各种应用软件的开发中。

资源就是控制器

在我们正式开始之前,我们需要首先明确,在Rails中,资源和model并不总是一对一的关系,有时资源仅仅只是你应用逻辑中的一个实体的抽象,并不需要 映射到你的数据库。但资源跟控制器总是一对一的,也就是每个资源都必须有一个与它相对应的控制器,并且你需要重新理解控制器,现在控制器只是REST接口 的具体实现,它的全部作用就是根据客户的请求返回资源的某种表示(HTML,XML等)。

所以,就像第2章讲的,我们不在需要去设计那无穷尽的API了,现在我们的控制器只需要定义7个方法:

  • show,处理针对单个资源的GET请求
  • create,处理POST请求,并将创建一个新资源
  • update,处理PUT请求,并更新指定的资源
  • destroy,处理DELETE请求,销毁一个资源
  • index,处理针对多个资源的GET请求
  • new,GET请求,返回一个用于创建资源的表单,
  • edit,GET请求,返回一个用于更新资源的表单

Rails 会帮助我们将用户的请求路由到某个合适的方法,当然,你并不需要实现这全部的7个方法,如果你的系统不允许用户创建和修改资源,那么你只需要实现 index和show方法就可以了。

不过更有可能的一种情况是你觉得这7个方法根本不够,你当然可以选择向控制器添加新的方法,但这其实是因为你的设计遗漏了一些资源,因为我建议,在你向控制器添加新方法之前,最好先重新考虑下你的设计。

方法已经定义好了,下一步的任务就是将用户的请求路由到指定的方法,在router.rb中,你可能会看到这样的路由:

map.connect '/airports/:action/:id', :controller = 'airports'

这条语句将映射/airports/open/45到airports控制器的open方法,你可以通过params[:id]获取URL中的参数45。但是REST路由有些特殊,它需要同时考虑URL和请求的类型,因此同样是发往/airports/1的请求,如果是GET请求,它需要被路由到airports的show方法,而DELETE请求则需要被路由到DELETE方法。

不过幸运的是,从Rails1.2开始,我们不再需要通过map.connect来手动的配置REST路由,map.resources会帮我们搞定一切:

map.resources 'airports'

这句话将创建如下的路由规则:

  • 针对/airports/ 的POST请求将被路由到create方法
  • 针对/airports/1 的GET请求将被路由到show方法
  • 针对/airports/1的PUT请求被路由到update方法
  • 针对/airports/1 的DELETE请求被路由到destroy方法
  • 针对/airports/ 的GET请求被路由到index方法
  • 针对/airports/new 的GET请求被路由到new方法
  • 针对/airports/1;edit 的GET请求被路由到edit方法

注意:最后一条逗号分隔的URL看起来很丑陋,但它们在Rails1.2.3中是合法的,不过不用苦恼,它们将在Rails2.0中被去除

现在我们已经完成了URL的路由,下面我们需要做的就是实现这些方法:
不过先别着急着码代码,从Rails1.2开始,我们有了一个新的生成器(generator):scaffold_resource,使用它我们可以很轻松的生成一个符合REST规范的Rails框架,它包含:

  • 资源所对应的model
  • 资源的migration文件
  • 资源所对应的控制器,控制器已经包含了REST所需的7个方法的实现
  • 这7个方法所对应的RHTML文件
  • 一条映射用户请求的路由

让我们仍然从第三讲的例子开始,首先创建一个新程序,然后为它添加一个airport资源:

D:\study>rails REST
D:\study>cd REST
D:\study\REST>ruby script/generate scaffold_resource airport name:string designator:string

修改database.yml文件,设置好你的数据库链接,然后执行:

D:\study\REST>rake db:migrate
D:\study\REST>ruby script/server

现在定位你的浏览器到http://localhost:3000/airports/new,你应该已经可以创建一个新机场了,是不是很神奇?现在,让我们来看看airports_controller.rb,所有的东西都在那了。

你应该会在控制器代码中看到一些奇怪的respond_to块,这正是我们整个REST实现的关键所在,我们将在下一讲详细探讨respond_to的细节。

EST on Rails指南3: RESTful Design

 

通过上一讲,我们明白了为什么Web需要按照REST的方式来设计,而不是传统的面向对象编程的RPC方式,这一讲我们将通过一个实例来演示如何进行REST方式的Web设计,也就是让我们的设计变的RESTful。

航空公司的需求

我们假设你为一家航空公司工作,你的任务是为他们设计一个航班管理系统,它的功能包括:

  • 允许公司员工通过Web前端来输入航班信息。航班信息包括航班的起飞和降落城市,以及起飞时间等。
  • 允许客户通过手机查询他的航班信息。
  • 允许第三方通过我们提供的API来获取我们的航班信息。

很没有难度,不是吗?如果你是个急性子,你甚至可能都顾不上将你无懈可击的设计转换成UML,就已经在你IDE的编辑框里输入了如下字符:

class FlightSchedule
def CancelFlight
......

但是慢着,在REST的世界里,我们不再需要操心这些,我们需要做的只有一件事情:

定义你的资源!

是的,就这一件,因为REST已经为我们定义好了用于操作这些资源的方法。

在这个例子里,我们首先会想到这几个资源:airports,airplanes,flights。当然可能还会有其它,但就让我们先从这几个开始吧!

我们首先要做的就是为这些资源分配URL,原则只有一个:尽可能的简单明了。

  • /airports,通过这个URL可以访问所有的机场资源
  • /airplanes,通过这个URL可以访问所有的飞机资源
  • /flights,通过这个URL可以访问所有航班资源

还有:

  • /airports/pudong,通过这个URL可以获取浦东机场的相关信息
  • /airplanes/ZJ3543,通过这个URL可以获取编号为ZJ3543的飞机信息
  • /flights/451,通过这个URL咋可以了解到航班451的起飞,降落城市已经起飞时间等信息。

方法已经准备好了

一旦你定义好了你的资源,整个设计也就完成了,因为,REST已经为你准备好了以下四个方法(并且不再需要其它的了):

  • GET,获取资源
  • POST,创建一个新资源
  • PUT,更新已存在的资源
  • DELETE,删除资源

通过HTTP调用这些方法

同样的,我们也不需要关心客户端如何来调用我们的方法,浏览器会帮我们搞定一切。

如果你仅仅只是在地址栏敲了个地址,然后按了下回车,浏览器会生成一条HTTP消息,并通过它来调用你输入的URL所代表的资源的GET方法。

如果你填写了一个表单,并点击了提交按钮,那么浏览器会将你填在表单中的信息组装成一条HTTP POST消息,并通过它来调用你想访问资源的POST方法。

但不幸的是,由于HTML的限制,目前你无法通过浏览器来调用资源的PUT和DELETE方法,不过这不重要,GET和POST对我们已经足够了。

好了,我们的基于REST的设计就这么完成了,下一讲,我们将演示如何使用Rest on Rails来快速优美的实现我们的设计。

REST on Rails指南2:无穷尽的API

 

通过上一讲,我认为你树立了这个概念:即Web其实是一组资源而不是网页的集合(如果你还不这么认为,那请你先返回再次阅读第一讲)。这一讲我们将从另一个侧面来讲解为什么要有REST?

面向对象设计与分析

如果你曾经学习过面向对象程序设计,那么你很可能会这样开始构建你的新程序:

  • 首先,你需要定义你的问题域——你的程序要解决什么问题
  • 然后,你会定义一个类,这个类的名字一般是名词
  • 接着你会为这个类定义一些方法,方法的名字一般是动词
  • 最用,通过调用其它类的方法,你的这个类顺利完成了它的使命

这看起来不错,事实上我曾经这么干了好多年,这种名词加动词的编程方法被成为“RPC”(远程过程调用),虽然我不明白那个Remote(远程)是指什么,但RPC的确是构建面向对象软件的一个重要方法,不过这种方式却并不适合Web开发。

让我们回到远古,假设现在是1992年(或者Web出现之前的随便什么日子),假设有这样的三家公司,他们需要开发这样三种应用:书籍贩卖,机票贩卖以及 卫星地图浏览。并且他们都遵照了面向对象的设计思想,同时出于长远考虑,他们都认为总有一天会有第三方的软件需要同他们的系统进行交互,因此,他们都实现 了他们各自的API。

现在,假设你的老板分配给你一个任务:为这三个系统设计一个统一的前端,你会怎么做呢?

我想你首先需要学习这三种完全不同的API,然后为每一个API设计一个UI控件,当用户操作UI控件时,对应的API就会被调用,你可能会通过你学到的一些设计模式知识来简化你的工作量,并使你的代码看起来尽可能酷一些。

无穷尽的API

当然这只是假设,但即使真的如此,在Web时代,你也不需要去学习那些无穷尽的糟糕API,你所要做的就是在你的电脑上安装一个浏览器,不是吗?浏览器对 于你将要访问的网站一无所知,但它却能够准确的返回你想要的,你可以通过它购买音乐,预定机票,甚至从任意远的距离来欣赏你家的屋顶。

这很神奇,不是吗?但是让我们设想一下,如果每个网站都有它们自己的API会是什么样子?如果你想在Amazon买本书,浏览器必须知道如何调用 Amazong.buy(),如果你想查看航班信息,那么浏览器需要知道如何调用UnitedAirlines.CheckFlights(),事实上, 这样通吃所有API的程序永远也不可能被开发出来。

所以这就决定了Web不可能是RPC式的,它只能是REST式的。

以资源为中心的设计

那么REST究竟是什么呢?按照维基百科的解释,REST是指Representational State Transfer。这是什么意思呢,简单的说,就是现在每个名词都不再拥有它们各自独一无二的动词了,在REST的世界里,所有名词拥有的动词都是一样 的,并且数量也很有限。换句话说,也就是所有的资源都提供了一组相同的API,这些API的实质就是允许随便什么客户端:

  • 获取资源的某种表示
  • 创建一个新资源
  • 更新已存在的资源
  • 销毁一个资源

等一下!那么究竟上面那个API可以让我“购买一本书”呢?搜索“下周二从纽约飞往洛杉矶的航班”又是哪个API完成的呢?

我们将在下一讲回答这个问题,但是如果你已经改变思维,不再认为”买书“就是一个网页,而是开始思考这其实是某个资源的创建,那么我想你其实应该已经知道答案了。




Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee