假设您有一个显示最新帖子的页面片段,并且该片段将在 30 分钟后过期。我在这里使用 Rails。

<% cache("recent_posts", :expires_in => 30.minutes) do %>
  ...
<% end %>

显然,如果片段存在,您不需要进行数据库查找来获取最新的帖子,因此您也应该能够避免这种开销。

我现在正在做的事情在控制器中似乎有效:

unless Rails.cache.exist? "views/recent_posts"
  @posts = Post.find(:all, :limit=>20, :order=>"updated_at DESC")
end

这是最好的方法吗?安全吗?

我不明白的一件事是为什么关键是“recent_posts“对于片段和”views/recent_posts“后来检查时,但我看完后想到了这一点 memcached -vv 看看它在使用什么。另外,我不喜欢重复手动输入“recent_posts”,最好将其保留在一个地方。

有想法吗?

有帮助吗?

解决方案

埃文·韦弗的 联锁插件 解决了这个问题。

如果您需要不同的行为,例如更细粒度的控制,您也可以轻松地自己实现类似的功能。基本思想是将控制器代码包装在一个块中,该块仅在视图需要该数据时才实际执行:

# in FooController#show
@foo_finder = lambda{ Foo.find_slow_stuff }

# in foo/show.html.erb
cache 'foo_slow_stuff' do
  @foo_finder.call.each do 
    ...
  end
end

如果您熟悉 ruby​​ 元编程的基础知识,那么很容易将其包装在您喜欢的更简洁的 API 中。

这优于将查找器代码直接放在视图中:

  • 按照惯例将查找器代码保留在开发人员期望的位置
  • 使视图不知道模型名称/方法,从而允许更多的视图重用

我认为 cache_fu 可能在其版本/分支之一中具有类似的功能,但具体不记得了。

您从 memcached 获得的优势与您的缓存命中率直接相关。请注意不要因多次缓存相同内容而浪费缓存容量并导致不必要的丢失。例如,不要同时缓存一组记录对象及其 html 片段。一般来说,片段缓存将提供最佳性能,但这实际上取决于应用程序的具体情况。

其他提示

如果缓存在您在控制器中检查它之间到期,会发生什么情况 以及在视图渲染中检查它的时间?

我在模型中创建了一个新方法:

  class Post
    def self.recent(count)
      find(:all, :limit=> count, :order=>"updated_at DESC")
    end
  end

然后在视图中使用它:

<% cache("recent_posts", :expires_in => 30.minutes) do %>
  <% Post.recent(20).each do |post| %>
     ...
  <% end %>
<% end %>

为清楚起见,您还可以考虑将最近帖子的呈现移动到其自己的部分中:

<% cache("recent_posts", :expires_in => 30.minutes) do %>
  <%= render :partial => "recent_post", :collection => Post.recent(20) %>
<% end %>

您可能还想查看

片段缓存文档

允许您这样做:

<% cache("recent_posts", :expires_in => 30.minutes) do %>
  ...
<% end %>

控制器

unless fragment_exist?("recent_posts")
  @posts = Post.find(:all, :limit=>20, :order=>"updated_at DESC")
end

虽然我承认DRY的问题仍然需要两个地方的密钥名称。我通常这样做与Lars的建议类似,但这取决于品味。我知道的其他开发人员坚持使用检查片段。

更新

如果您查看片段文档,您可以看到它如何摆脱需要视图前缀:

# File vendor/rails/actionpack/lib/action_controller/caching/fragments.rb, line 33
def fragment_cache_key(key)
  ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
end

Lars非常关注使用以下几点失败的可能性:

unless fragment_exist?("recent_posts")

因为检查缓存和使用缓存之间存在差距。

jason提到的插件(Interlock)通过假设如果你正在检查片段是否存在而非常优雅地处理它,那么你可能也会使用片段,从而在本地缓存内容。我出于这些原因使用Interlock。

就像一个想法:

在应用程序控制器中定义

def when_fragment_expired( name, time_options = nil )
        # idea of avoiding race conditions
        # downside: needs 2 cache lookups
        # in view we actually cache indefinetely 
        # but we expire with a 2nd fragment in the controller which is expired time based
        return if ActionController::Base.cache_store.exist?( 'fragments/' + name ) && ActionController::Base.cache_store.exist?( fragment_cache_key( name ) )

        # the time_fraqgment_cache uses different time options
        time_options = time_options - Time.now if time_options.is_a?( Time )

        # set an artificial fragment which expires after given time
        ActionController::Base.cache_store.write("fragments/" + name, 1, :expires_in => time_options )

        ActionController::Base.cache_store.delete( "views/"+name )        
        yield    
  end

然后在任何动作中使用

    def index
when_fragment_expired "cache_key", 5.minutes
@object = YourObject.expensive_operations
end
end

在视图中

cache "cache_key" do
view_code
end
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top