2009-06-23 74 views
10

假設您有一段顯示最近發佈帖子的頁面,並在30分鐘內過期。我在這裏使用Rails。爲memcached和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」的重複,最好將它保存在一個地方。

想法?

回答

12

埃文韋弗的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片段。通常片段緩存將提供最佳性能,但它實際上取決於應用程序的具體情況。

+0

雖然我認爲Lar的答案可能會更清晰一點,並且避免使用元編程,但您的答案更好,因爲廣告更接近MVC。我已經把他們兩個都視爲有效,但將留給社區決定(不會選擇一個接受:)謝謝! – 2009-06-24 20:34:57

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 %> 
+0

大點拉爾斯!非常好的東西。 – 2009-06-24 20:24:31

+0

我認爲你是迄今爲止最好的解決方案,雖然從技術上講這可能違反了MVC,這很清楚。傑森沃特的答案是一個有效的選擇。 – 2009-06-24 20:30:36

1

您可能還需要尋找到

Fragment Cache Docs

,讓你可以做到這一點:雖然我承認幹問題仍然

<% 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 

在兩個地方需要鑰匙的名字。我通常這樣做與拉斯建議的方式類似,但它確實取決於味道。我知道的其他開發者堅持使用檢查片段存在。

更新:

如果你看片段文檔,你可以看到它如何被擺脫需要的視圖前綴:

# 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 
1

拉爾斯使有關存在是一個輕微的機會真的好點使用:

unless fragment_exist?("recent_posts") 

因爲當您檢查緩存和何時使用緩存之間存在差距。

jason提到的插件(Interlock)通過假設如果您檢查片段的存在來處理這個問題,那麼您可能也會使用該片段,從而在本地緩存內容。出於這些原因,我使用互鎖。

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