`
firebody
  • 浏览: 40756 次
  • 性别: Icon_minigender_1
社区版块
存档分类

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

阅读更多
最近研究了一下 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

.............
分享到:
评论
12 楼 firebody 2008-01-06  
nihongye 写道
firebody 写道
LRU频繁的话,性能应该会很差

不知道这个猜测是从哪里来的?用java里面的LinkedList实现LRU,只需要一个指针移动就可以了

嗯,我那个结论有点草率。 回头我仔细看看LRU以及memcache的相关设计才能得出准确一些的结论,等以后发布的时候,做一些真正的测试也才能模拟出来。 不过真正的高并发测试以及相应的性能测死确实不是难么容易做。 需要考虑的东西比较多。
11 楼 nihongye 2008-01-03  
firebody 写道
LRU频繁的话,性能应该会很差

不知道这个猜测是从哪里来的?用java里面的LinkedList实现LRU,只需要一个指针移动就可以了
10 楼 firebody 2007-12-30  
最近在忙着一个小项目,赚取一些生活费,所以这边的工作我落下了两个星期,很抱歉 。

这两个星期里面,我抽空想了一下原先的设计,LRU频繁的话,性能应该会很差,如果做精巧一些参数调整的话 ,性能估计会好很多,我没做过相关的研究,我想提供多一种选择。所以后面的话,主要的设计架构会调整,加多一个管理策略: 用 memory的hash来记录 groupId和所属的 cacheKey的关系。
同时也支持 不使用 group而直接使用cacheKey的缓存管理策略。

内存可能会因为使用memory来记录会占用多一些,但是这个占用应该不会很大,因为并不是所有缓存都使用group,大多数还是直接使用cacheKey的。

我做这个插件最希望的就是简化引入mem cache需要做的工作 。实现无缝的引入,并且支持group cache管理的效果 。

9 楼 casephoen 2007-12-18  
测试的结果呢?不贴出来怎么有说服力阿
8 楼 supermy 2007-12-15  
用memcache自己定制cache策略。me在hibernate上就是根据业务的情况划分cache的大小,应用memcached的
取得不错的效果。
7 楼 firebody 2007-12-14  
正如同第一个帖子提到的 ,目前缓存expiry的策略是 唯一递增 group version.
这样的策略 好处是 不用花费较多的逻辑来记录 group和 cache key的一对多关系 ,也不用再expire group的时候一一清除 cache item .

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

考虑到性能调整的选择,目前也考虑在后续的开发中,支持自动清除过期缓存条目的功能,避免频繁的LRU,不过因为没有得到实际的性能统计,所以也不好立即开展这份工作哦,等后续版本再说。
6 楼 firebody 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主要的工作就是配置此文件 。

5 楼 firebody 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,避免读取旧有的脏数据 。
4 楼 firebody 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
3 楼 firebody 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


2 楼 firebody 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(啊翔) 说:
晕倒,我把你的这个讨论加上去就行了
1 楼 firebody 2007-12-10  
希望有过使用 rails cache的XDJM们踊跃提出自己的看法。
不知道这样的方案是否能够符合实际开发的需要以及其中有什么改进的地方。
 

相关推荐

    Rails Cache

    Rails Cache 是 Ruby on Rails 框架中的一个重要特性,它用于提高应用程序的性能,通过缓存数据来避免不必要的数据库查询和其他昂贵的操作。Rails 提供了多种级别的缓存,包括动作缓存、片段缓存、页面缓存以及低...

    关于rails 3.1 cucumber-rails 1.2.0

    首先,Rails 3.1是Ruby on Rails框架的一个版本,它在2011年发布。这个版本引入了一些显著的改进,如Asset Pipeline(资产管道)和CoffeeScript支持。Asset Pipeline允许开发者更有效地管理和优化应用程序的前端资源...

    Rails 101 入门电子书

    - 测试安装: 创建一个简单的Rails应用来验证是否成功安装。 #### 五、练习作业0-Hello World - **目标**: - 学习如何创建第一个Rails应用程序。 - **过程**: - 创建新项目。 - 设置数据库配置。 - 创建控制器...

    Ruby-一个Rails引擎提供工作流程的基本基础设施

    在本案例中,"Ruby-一个Rails引擎提供工作流程的基本基础设施"指的是一个专门为实现工作流程管理而设计的Rails引擎。 工作流程(Workflow)通常涉及到业务流程的自动化,包括任务分配、状态转换和审批流程等。...

    rails-cache-inspector:用于片段缓存的可视化调试的简单工具

    安装将此行添加到应用程序的Gemfile中: gem 'rails-cache-inspector' , group : :development用法配置突出显示 # config/initializers/rails_cache_inspector.rbRailsCacheInspector . configuration . highlight_...

    RailsGuides中那个blog程序源码

    **Ruby on Rails(RoR)** 是一个基于Ruby语言的开源Web开发框架,它遵循MVC(模型-视图-控制器)架构模式,用于构建高效、简洁和可维护的Web应用。RailsGuides中的"blog"程序是一个典型的入门级示例,旨在帮助初学...

    Rails101_by_rails4.0

    Rails(Ruby on Rails)是一个采用Ruby语言编写的开源Web应用框架,它遵循模型-视图-控制器(MVC)的架构模式,设计用来快速开发数据库驱动的动态网页。随着Rails版本的更新迭代,此书聚焦于一个特定的版本,帮助...

    Rails项目源代码

    Ruby on Rails,通常简称为Rails,是一个基于Ruby编程语言的开源Web应用框架,遵循MVC(Model-View-Controller)架构模式。这个“Rails项目源代码”是一个使用Rails构建的图片分享网站的完整源代码,它揭示了如何...

    Struts2和Rails的国际化实现

    1. **创建资源文件**:首先,你需要创建一个或多个`.properties`文件,这些文件将包含你的应用中的文本字符串。例如,你可以创建一个名为`messages.properties`的文件用于英文,一个`messages_zh_CN.properties`的...

    Ruby-GoOnRails使用Rails生成器来生成一个Golang应用

    总的来说,`GoOnRails`是一个创新的工具,它让Rails开发者能够轻松地在Rails项目中引入Go代码,实现高性能API服务。通过熟练掌握`GoOnRails`,开发者可以在保持高效开发流程的同时,享受到Go带来的性能提升。

    Ruby-Knock为RailsAPI实现无缝JWT身份验证

    在描述中提到的"Knock"是一个专门为Rails API设计的库,它帮助开发者实现JSON Web Token(JWT)的身份验证,这是一种安全的身份验证机制,适用于无状态API。 JWT是一种轻量级的认证协议,它允许服务器向客户端发送...

    rails2-sample

    这里还会涉及如何创建第一个Rails项目,以及如何运行服务器以查看项目。对于新手来说,这一步骤是至关重要的,因为它将奠定后续学习的基础。 #### 3. Introducing Ruby(介绍Ruby) Ruby是一种动态、面向对象的...

    Ruby-一个Rails应用程序为DIY小型内容分发网络CDN提供支持

    一个Rails应用程序,为DIY小型内容分发网络CDN提供支持

    Rails 101S

    - **创建项目**:使用`rails new`命令创建一个新的Rails项目。 - **配置Git**:设置版本控制系统,确保代码变更能够被追踪记录。 - **使用Bootstrap进行前端设计**:介绍如何使用Bootstrap框架来快速搭建美观的...

    rails实现验证码实例

    本实例将详细介绍如何在Rails应用中实现一个简单的图形验证码。 首先,我们需要一个字体配置文件来生成验证码中的随机字符。字体配置文件可以包含多个字体样式,用于增加验证码的复杂性和可读性。在Rails项目中,...

    rails-cache-extended:帮助程序和日志记录添加到 Rails 缓存

    Rails::Cache::Extended 这允许为记录集合生成自动过期的缓存键 安装 将此行添加到应用程序的 Gemfile 中: gem 'rails-cache-extended' 然后执行: $ bundle 或者自己安装: $ gem install rails-cache-...

    Rails

    压缩包子文件的文件名称 "Ruby on Rails.pptx" 提示可能是一个关于Rails的PowerPoint演示文稿,其中可能涵盖了Rails的基础概念、安装步骤、主要组件介绍、开发流程、最佳实践,以及可能的示例代码和案例研究。...

Global site tag (gtag.js) - Google Analytics