论坛首页 编程语言技术论坛

关于实现一个rails smart cache 的思路草稿。

浏览 9214 次
该帖已经被评为精华帖
作者 正文
   发表时间:2007-12-10  
最近研究了一下 rails的cache设计,发现其中一些不尽如人意的地方:

* cache expiry 编写繁琐

* 分页缓存的清除,现有cache实现的支持都不是很完善

* 在一次清除大量缓存的时候,脏数据读的问题。


我查阅了一些blog以及相关的文章,从他们的抱怨和设计中得到一些启发,我觉得cache可以做得更好,更智能,更能够减少开发人员的工作量。 下面是我设计思路的一些草稿,如果深入分析,觉得可行的话,就可以动手做他:

* 支持 cache 分组。

* 支持简化的expiry rules

* 适当的设计减少一次清除大量缓存时脏数据读的概率和时间窗

* 仅考虑memcache的支持。

* 尽可能在现有框架的基础上做简单的扩展,减少开发量。



支持cache 分组可以同时带来很多好处,第一,支持分页缓存的一次expire。同时也有利于“减少脏数据读的时间窗”,cache分组的设计是整个设计的关键思路之一。下面来谈谈cache分组的设计:

* 内存开辟一个hash。key是group ID。value是一个特有的Group对象,这个Group对象的设计目的是尽可能简便并且尽快地 清除同一组的所有cache key。

* 要expire 同组所有脏缓存数据,有两种策略: 1) 按照cacheKey 逐一清楚 2) 不清除,而是加版本号
第一种策略意味着需要保存Group和 CacheKey的一对多关系,这个保存可能需要通过数据库来保存,带来的复杂和性能开销似乎不是上选。
第二种策略不用清除缓存,也不用查询数据库。但这种策略也需要做一些工作,在加入缓存的时候,我们需要修改原有的cacheKey,在后面加上Version=1这样版本标识。get cache的时候
也需要做同样的工作。当expire脏Group的时候,只需要将Group对象上的version属性递增一。原有的脏数据通过memcache的自动清理策略来自动清除。
从这个分析来看,我觉得选择第二种策略更好。

整个smart_cache的设计实现应该在现有的cache实现的基础上,通过alias_method(rails的AOP)加入我们自己的中间逻辑。这样开发量最少。

设计中的另外一个关键点是 expiry rules的设计,目前rails cache的expire的编写已经比较简单了,我们只需要更进一步,初步
设计如下,在config目录下增加一个 cache_expiry_rules.rb,示例代码如下:
SmartCache::Rules do  |config|
	
	config.add_rule(:group=>"user list",:expire_rules=>[cud_rule(User,Department,Room),rule(User.update*)])
	config.add_rule(:group=>"department list",:expire_rules=>[cud_rule(Department,Room),rule(Department.update*)])
	
end

上面代码中的关键部分:
:expire_rules=>[cud_rule(User,Department,Room),rule(User.update*)])
其中,cud表示某个model对象上的create,update,delete方法完成后。
后面的rule(user.update*) 表示UserModel上的符合规则的某些方法被调用了之后。



原有的缓存的方法 cache 需要最后增加一个属性 :group=>"your group",比如 cache fragment 的编写

 <% cache :controller="user",:action=>"list",:page=>"params[:page]",:group=>"user list" do %>

	//rhtml code here to renderring view
 <% end %>


 caches_action :list, :show,:group="test group"



参考文章:

* web agile devel....

* http://www.railsenvy.com/2007/2/28/rails-caching-tutorial

* http://blog.leetsoft.com/2007/5/22/the-secret-to-memcached

* http://blog.craigambrose.com/past/2007/11/13/caching_makes_your_brain_explode/

* http://cfis.savagexi.com/articles/2007/09/05/rails-unusual-architecture

.............
   发表时间:2007-12-10  
希望有过使用 rails cache的XDJM们踊跃提出自己的看法。
不知道这样的方案是否能够符合实际开发的需要以及其中有什么改进的地方。
 
0 请登录后投票
   发表时间:2007-12-10  
倪宏业 说:
怎么说呢,觉得你这篇好像分为两大部分,smart cache核心,smart cache的aop
1.核心部分,从缓存读取cache数据的时候,smartcache如何访问memcache。
2.如何expire等吧

firebody(啊翔) 说:
嗯,是的
firebody(啊翔) 说:
底层的缓存还是用原有已经写好的
倪宏业 说:
喔
firebody(啊翔) 说:
我就是中间,加多我的逻辑,相当于一个拦截,再调用底层的方法
firebody(啊翔) 说:
memcache就是一个hashtable
firebody(啊翔) 说:
我就是修改它们的key,在后面加多一个version
firebody(啊翔) 说:
put/get配对的改 ,group对象保留当前 的version
倪宏业 说:
喔,原来是这样,我刚才就是version这个没看明白
firebody(啊翔) 说:
同样也需要修改原有的外围调用cache的方法,需要在最后加多一个group对象,这样,我才知道put、get调用的时候,传入的cache key属于哪个group的
倪宏业 说:
memcache本身是没组的概念,而是你在原有key上增加group的hashcode?
firebody(啊翔) 说:
不是,在key上增加对应group的version。 
firebody(啊翔) 说:
要增加group的hashcode也行,呵呵
firebody(啊翔) 说:
主要通过group的version来 废弃原有的脏数据
倪宏业 说:
喔,明白了,vesion唯一递增?
firebody(啊翔) 说:
嗯
倪宏业 说:
文章写得明白,重新写过,哈哈
firebody(啊翔) 说:
晕倒,我把你的这个讨论加上去就行了
0 请登录后投票
   发表时间:2007-12-13  
现在已经出来一个支持 fragment cache的版本了。 目前测试的底层cache机制 是 cache_fu插件。
当然,设计的目的不局限于 cache_fu。

使用介绍:

* cache_fu的配置不变,该咋办还是咋办

* 将 smart_cache包解压到 vendor/plugins目录下

* 在config目录编写配置文件 smart_memcache_config.rb
例子如下 :

SmartMemcache::Config.init do |config|
  config.expire_group("user_page_list").after_models(:user).fired(:after_create,:after_update,:after_destroy,:after_save)
  config.expire_group("user_page_list").after("User").fired(:destroy_all,:update_all)
  config.delegate_store = ::ActionController::Base.fragment_cache_store
  config.use_cache_fu = true
end


config.expire_group("user_page_list").after_models(:user).fired(:after_create,:after_update,:after_destroy,:after_save)

表示当userModel 的 :after_create,:after_update,:after_destroy,:after_save时间发生后,过期属于 user_page_list 组的所有缓存条目 。
值得说明的是 这种方式的监听 是通过 ModelObserver来实现的 。所以 fired里面的event时间都是大家熟悉的Observer的方法名。
config.expire_group("user_page_list").after("User").fired(:destroy_all,:update_all)

这行配置表示 当User 类执行 :destroy_all,:update_all 方法后 ,过期 user_page_list 组缓存的所有条目 。

这种监听方式的实现通过smart memcache内部实现的ruby aop框架来做的 ,smartmem cache的ruby aop做的比较简单,大家可以单独抽取出来作为一个通用的 aop框架 。


config.delegate_store = ::ActionController::Base.fragment_cache_store
config.use_cache_fu = true


这个配置表明 smartmemcache不单独造一个底层cache的轮子,而是直接拿初始化好的 cache_store,如果使用cache_fu插件的话,务必保证cache_fu先于smartmemcache初始化(默认是按照字母顺序初始化插件,所以默认顺序是对的)

rhtml fragment
         <% cache({}, {:group=>:user_page_list}) do %>
                     <%
                             @users = User.list_all_users(params[:page])
                         
                     %>
                <% for user in @users -%>
            <tr align="center">
              <td><%=user.id %></td>
              <td><%=user.name %></td>
             
              <td><%= link_to "Edit", :action => "edit_user",:id=>user %> ><%= link_to "Delete", {  :action => 'delete_user', :id => user}, { :method => :post,   :confirm => "Really delete #{user.name}?"  } %></td>
            </tr>
          <% end %>
          <tr align="center">
            <td colspan="5" align="right"></td>
            <td><%= link_to "Add User", :action => "add_user" %></td>
          </tr>
          <tr>
            <td colspan="6" align="center"><%= will_paginate(@users) %></td>
          
          </tr>
        </tbody>
      <% end %>

主要的代码:

cache({}, {:group=>:user_page_list})

其中 options选项多了 :group=>:user_page_list


0 请登录后投票
   发表时间:2007-12-14  
值得再推荐的是,其中做的那个ruby aop小框架,大家看看具体的使用的例子:
describe AOP::RubyAop," after interceptor " do

  before(:each) do
    @ruby_aop = AOP::RubyAop.new 
    @aop_inf = nil
    
    reload_blah
    Blah.m_call = nil
  end

  it "should interceptor class method:update_all use methods declaration" do
    block1_executed = false
    orig_block_executed = false
    args = nil
    @ruby_aop.interceptor(:classes=>["Blah"],:methods=>[:update_all],:interceptor_type=>:after) { |aop_info,*a| @aop_inf = aop_info;block1_executed = true ;args=a;Blah.m_call.should eql("self.update_all") }
    @ruby_aop.enhance_intercepted_classes
    Blah.update_all(1,2) { orig_block_executed = true }
    orig_block_executed.should be_true
    block1_executed.should be_true
    @aop_inf[:intercepted_class].should eql("Blah")
    @aop_inf[:intercepted_method].should eql(:update_all)
    @aop_inf[:intercepted_method_is_class_method].should be_true
    args[0].should == 1
    args[1].should == 2
  end

end
0 请登录后投票
   发表时间:2007-12-14  
TODO List:

* 支持查询缓存 ,更简洁,更透明的查询缓存配置。 在 smart_memcache_config.rb中作如下配置:
config.cache("User").on(:user_list,:find_all,:find_by_id,:find_by_name).with_group(:user_query_cache)。with_options(:ttl=>60*30)
config.expire_group(:user_query_cache).after("User","Department").fired(:update_all,:do_logic,:del_department_by_user)



用户不需要修改任何已有的model代码。 smartmemcache会根据配置自动增强原来的代码。

* aop支持 arround interceptor

* 支持重新启动 rails后能够读取最后的group version,避免读取旧有的脏数据 。
0 请登录后投票
   发表时间:2007-12-14  
已经在rubyforge申请了 ,可以匿名访问 下载源代码

Anonymous Subversion Access

This project's SVN repository can be checked out through anonymous access with the following command(s).

svn checkout http://smartmemcache.rubyforge.org/svn/
or
svn checkout svn://rubyforge.org/var/svn/smartmemcache

因为是作为rails的插件使用的,所以项目整体采用了一个简单的rails项目作为基础,真正的plugin代码和测试在vendor/plugins/smart_cache下面。

目录结构说明 :
\vendor\plugins\smart_cache\lib  smart_memcache核心代码
\vendor\plugins\smart_cache\lib\aop smart_memcache内部的一个 AOP框架

\vendor\plugins\smart_cache\lib\rails smart_memcache对于rails得setup和扩展
\vendor\plugins\smart_cache\spec  对smart_memcache核心代码以及aop框架的spec测试 
\vendor\plugins\smart_cache\tasks\rspec.rake 运行整个spec测试的 rake任务
\vendor\plugins\smart_cache\init.rb 插件安装文件 ,由 rails自动调用

\spec\controllers  针对rails集成环境的smart_memcache集成测试。 由rspec对rails的插件支持来运行测试
\config\smart_cache_config.rb  smart_memcache在rails中的配置文件,使用smart_memcache主要的工作就是配置此文件 。

0 请登录后投票
   发表时间:2007-12-14  
正如同第一个帖子提到的 ,目前缓存expiry的策略是 唯一递增 group version.
这样的策略 好处是 不用花费较多的逻辑来记录 group和 cache key的一对多关系 ,也不用再expire group的时候一一清除 cache item .

缺点是 因为没有 delete stale cached item,会导致memcache的缓存最终被频繁的LRU.这样的性能损耗 对比与 频繁的 remove 脏数据 性能下降多少 。 如果LRU的参数做一些为微妙的调整可能会对smart_memcache的缓存清除策略带来很大的效果 。

考虑到性能调整的选择,目前也考虑在后续的开发中,支持自动清除过期缓存条目的功能,避免频繁的LRU,不过因为没有得到实际的性能统计,所以也不好立即开展这份工作哦,等后续版本再说。
0 请登录后投票
   发表时间:2007-12-15  
用memcache自己定制cache策略。me在hibernate上就是根据业务的情况划分cache的大小,应用memcached的
取得不错的效果。
0 请登录后投票
   发表时间:2007-12-18  
测试的结果呢?不贴出来怎么有说服力阿
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics