【问题标题】:Low level caching for collection用于收集的低级缓存
【发布时间】:2015-08-19 07:53:04
【问题描述】:

我想使用 Redis 在我的 Rails 应用程序中进行一些低级缓存。 在控制器中,我通常使用它来获取所有书籍:

class BooksController < ApplicationController
  def index
    @books = Book.order(:title)
  end
end

并且视图会对此进行迭代:

<ul>
  - @books.each do |book|
    <li>= "#{book.title} - #{book.author}"</li>
</ul>

现在我想要完全相同的结果,但随后被缓存。我已经安装并运行了 Redis。那么我应该像这样在控制器中使用cached_books 方法吗:

@books = Book.cached_books.order(:title)

让视图保持原样,或者在视图中使用book.cached_titlebook.cached_author 并让控制器保持原样?

cached_books 方法在 Book 模型中的外观如何?

class Book < ActiveRecord::Base
  ...

  def cached_books
    Rails.cache.fetch([self, "books"]) { books.to_a }
  end
end

为简单起见,我暂时省略了过期策略,但显然它们需要存在。

【问题讨论】:

  • 我不会在模型中添加缓存
  • 只是检查一下:你读过guide about caching吗?
  • 当然,但我正在寻找一个集合的特定模型缓存

标签: ruby-on-rails caching redis


【解决方案1】:

那么我应该像这样在控制器中使用 cached_books 方法吗:

是的,你可以。虽然有一些你必须注意的问题。 BookActiveRecord。当你调用Book.something(例如Book.all,或者只是Book.order(:title),它会返回一个ActiveRecord::Relation,它基本上是Books 数组的包装器(这个包装器可以防止触发不必要的查询,从而提高性能)。

您无法将查询的整个结果保存在 Redis 中。比如说,你可以保存一个带有模型属性的哈希数组的 JSON 字符串,例如

[{
  id: 1,
  title: 'How to make a sandwich",
  author: 'Mr. cooker'
}, {
  id: 2,
  title: 'London's Bridge',
  author: 'Fergie'
}]

然后你可以'解密'这个东西到数组之后。类似的东西

def cached_books(key)
  # I suggest you to native wrapper
  if result = $redis.hget 'books_cache', key
    result.map do { |x| Book.new(x) }
  end
end

此外,您必须在将属性放入缓存之前对其进行序列化。

好的,现在您有了可以在视图中使用相同数据迭代的集合,尽管您不能在缓存集合上调用 order,因为它是一个普通数组(您可以调用 sort,但是想法是缓存已经排序的数据)。

嗯……值得吗?其实,也不是。如果您需要缓存这部分 - 可能最好的方法是缓存呈现的页面,而不是查询结果。

您应该使用cached_titlecached_author - 这是个好问题。首先,这取决于cached_title 可能是什么。如果它是一个字符串——没有什么可以缓存的。你通过 DB 请求得到一个Book,或者你从缓存中得到Book——无论如何title 都会出现在其中,因为它是一个简单的类型。但让我们更接近author。很可能它与另一个模型Author 有关系,这是缓存非常适合的地方。您可以在书中重新定义author 方法(或定义新方法并避免Rails 将来在复杂查询中可能产生的不良影响)并查看是否有缓存。如果是,则返回缓存。如果没有 - 查询数据库,将结果保存到缓存并返回。

def author
  Rails.cache.fetch("#{author_id}/info", expires_in: 12.hours) do
    # block executed if cache is not founded
    # it's better to alias original method and call it here
    #instead of directly Author.find call though
    Author.find(author_id) 
  end
end

或者不太方便,但更“安全”:

def cached_author
  Rails.cache.fetch("#{author_id}/info", expires_in: 12.hours) do
    author
  end
end

【讨论】:

  • 感谢全面的解释,这对我很有用!
猜你喜欢
  • 2018-04-04
  • 2015-05-25
  • 2020-10-07
  • 1970-01-01
  • 2017-11-07
  • 2016-08-09
  • 2011-09-26
  • 1970-01-01
  • 2018-04-08
相关资源
最近更新 更多