10

假设您有一个显示最新帖子的页面片段,并且您在 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”的重复,最好将其保存在一个地方。

想法?

4

5 回答 5

12

Evan Weaver 的Interlock Plugin解决了这个问题。

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

# 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 片段。通常,片段缓存将提供最佳性能,但这实际上取决于您的应用程序的具体情况。

于 2009-06-23T23:43:24.370 回答
3

如果缓存在你在控制器中检查它的时间和它在视图渲染中被检查的时间之间过期会发生什么?

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

  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 %>
于 2009-06-23T20:29:04.590 回答
1

就像一个想法:

在应用程序控制器中定义

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
于 2010-09-03T10:49:37.000 回答
1

Lars 提出了一个非常好的观点,即使用以下方法可能会失败:

unless fragment_exist?("recent_posts")

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

jason 提到的插件(Interlock)非常优雅地处理了这个问题,假设如果您正在检查片段是否存在,那么您可能还会使用片段并因此在本地缓存内容。出于这些原因,我使用 Interlock。

于 2009-07-10T05:48:55.523 回答
1

You may also want to look into

Fragment Cache Docs

Which allow you to do this:

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

Controller

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

Although I admit the issue of DRY still rears its head needing the name of the key in two places. I usually do this similar to how Lars suggested but it really depends on taste. Other developers I know stick with checking fragment exist.

Update:

If you look at the fragment docs, you can see how it gets rid of needing the view prefix:

# 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
于 2009-06-24T05:10:54.117 回答