2009-06-23 21 views
10

Cho phép nói rằng bạn có một đoạn của trang hiển thị các bài đăng gần đây nhất và bạn hết hạn trong 30 phút. Tôi đang sử dụng Rails ở đây.Cách tốt nhất để kết hợp phân đoạn và đối tượng bộ nhớ đệm cho memcached và Rails

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

Rõ ràng bạn không cần thực hiện tra cứu cơ sở dữ liệu để nhận các bài đăng gần đây nhất nếu đoạn tồn tại, vì vậy bạn cũng có thể tránh được phí đó.

một cái gì đó như thế này trong bộ điều khiển mà dường như để làm việc gì tôi đang làm bây giờ là:

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

Đây có phải là cách tốt nhất? Nó có an toàn không? Một điều tôi không hiểu là tại sao khóa là "recent_posts" cho đoạn và "views/recent_posts" khi kiểm tra sau, nhưng tôi đã đưa ra điều này sau khi xem memcached -vv để xem nội dung đang sử dụng. Ngoài ra, tôi không thích sự trùng lặp bằng cách nhập "recent_posts" theo cách thủ công, sẽ tốt hơn nếu giữ nguyên bản sao đó ở một nơi.

Ý tưởng?

Trả lời

12

Evan Weaver's Interlock Plugin giải quyết vấn đề này.

Bạn cũng có thể tự mình thực hiện điều gì đó dễ dàng nếu bạn cần hành vi khác nhau, chẳng hạn như kiểm soát chi tiết hơn. Ý tưởng cơ bản là để bọc mã điều khiển của bạn trong một khối mà chỉ được thực thi nếu quan điểm cần dữ liệu:

# 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 

Nếu bạn đã quen thuộc với những điều cơ bản của lập trình meta ruby ​​nó đủ dễ dàng để quấn này trong một API sạch hơn về khẩu vị của bạn.

Đây là vượt trội so với cách đặt mã công cụ tìm trực tiếp trong giao diện:

  • giữ mã công cụ tìm nơi các nhà phát triển hy vọng nó theo quy ước
  • giữ quan điểm ngu dốt của tên mô hình/phương pháp, cho phép xem hơn tái sử dụng

Tôi nghĩ cache_fu có thể có chức năng tương tự ở một trong các phiên bản/nhánh của nó, nhưng không thể gọi lại cụ thể.

Lợi thế bạn nhận được từ memcached có liên quan trực tiếp đến tỷ lệ truy cập bộ nhớ cache của bạn. Cẩn thận để không lãng phí dung lượng bộ nhớ cache của bạn và gây ra những lần bỏ sót không cần thiết bằng cách lưu vào bộ nhớ đệm cùng một nội dung nhiều lần.Ví dụ: không lưu bộ nhớ cache một tập hợp các đối tượng bản ghi cũng như đoạn html của chúng cùng một lúc. Nói chung, bộ nhớ đệm mảnh sẽ cung cấp hiệu suất tốt nhất, nhưng nó thực sự phụ thuộc vào các chi tiết cụ thể của ứng dụng của bạn.

+0

Trong khi tôi nghĩ câu trả lời của Lar có thể rõ ràng hơn một chút, và tránh sử dụng lập trình meta, câu trả lời của bạn tốt hơn là quảng cáo nghe rõ hơn với MVC. Tôi đã upvoted cả hai người trong số họ là hợp lệ, nhưng sẽ để nó lên đến cộng đồng để quyết định (sẽ không chọn một như được chấp nhận :) Cảm ơn! –

+0

Tôi đồng ý và tìm thấy giải pháp tốt này. Như để tái sử dụng quan điểm, vâng nói chung đó là một nguyên tắc tốt. Bạn sẽ phải để cho bộ điều khiển cung cấp khóa bộ nhớ cache mặc dù cho rằng để làm việc ở đây. BTW, vui vẻ đủ, tài liệu API về bộ nhớ đệm phân đoạn vi phạm nguyên tắc MVC trong ví dụ đầu tiên: http://api.rubyonrails.org/classes/ActionController/Caching/Fragments.html –

3

Điều gì xảy ra nếu bộ nhớ cache hết hạn trong khoảng thời gian bạn kiểm tra bộ điều khiển trong bộ điều khiển và thời gian nó được kiểm tra trong chế độ xem hiển thị?

Tôi muốn tạo ra một phương pháp mới trong mô hình:

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

sau đó sử dụng trong giao diện:

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

Để rõ ràng, bạn cũng có thể cân nhắc việc di chuyển vẽ của một bài đăng gần đây vào phần riêng của nó:

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

Great điểm Lars! Rất tốt. –

+0

Tôi nghĩ rằng bạn là giải pháp tốt nhất cho đến nay, mặc dù về mặt kỹ thuật điều này có thể vi phạm MVC nó khá rõ ràng. Câu trả lời của Jason Watkin là một giải pháp thay thế hợp lệ. –

1

Bạn cũng có thể muốn xem xét

Fragment Cache Docs

nào cho phép bạn làm điều này:

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

khiển

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

Mặc dù tôi thừa nhận vấn đề DRY vẫn rears đầu của nó cần tên của khóa ở hai nơi. Tôi thường làm điều này tương tự như cách Lars đề nghị nhưng nó thực sự phụ thuộc vào khẩu vị. Các nhà phát triển khác tôi biết gắn bó với việc kiểm tra phân đoạn tồn tại.

Cập nhật:

Nếu bạn nhìn vào các tài liệu mảnh, bạn có thể thấy nó được thoát khỏi cần quan điểm tiền tố:

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

ahh, có ý nghĩa. Fragment_exist? phương pháp có lẽ được thoát khỏi cần tiền tố "xem /" trên cache_key? –

1

Lars làm cho một điểm thực sự tốt về có được một cơ hội nhỏ thất bại khi sử dụng:

unless fragment_exist?("recent_posts") 

vì có khoảng cách giữa khi bạn kiểm tra bộ nhớ cache và khi bạn sử dụng bộ nhớ cache.

Plugin mà jason đề cập (Interlock) xử lý điều này rất duyên dáng bằng cách giả sử rằng nếu bạn đang kiểm tra sự tồn tại của đoạn, thì bạn cũng có thể sử dụng phân đoạn và do đó lưu trữ nội dung cục bộ. Tôi sử dụng Interlock vì những lý do này.

1

cũng giống như một mảnh suy nghĩ:

trong điều khiển ứng dụng xác định

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 

sau đó trong việc sử dụng hành động

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

theo quan điểm

cache "cache_key" do 
view_code 
end 
Các vấn đề liên quan