ruby-on-rails - 使用缓存来优化 Rails 中的时间线

标签 ruby-on-rails caching memcached redis

我希望获得有关正确使用缓存以加快 Rails 中时间线查询速度的建议。这是背景:

我正在开发一个带有 Rails 后端的 iPhone 应用程序。它是一款社交应用程序,与其他社交应用程序一样,其主要 View 是消息的时间线(即新闻源)。这与 Twitter 的工作原理非常相似,其中时间线由用户及其关注者的消息组成。 API 请求中检索时间线的主要查询如下:

@messages = Message.where("user_id in (?) OR user_id = ?", current_user.followed_users.map(&:id), current_user)

现在这个查询变得非常低效,特别是在规模上,所以我正在研究缓存。这是我计划做的两件事:

1) 使用 Redis 将时间线缓存为消息 ID 列表

导致此查询如此昂贵的部分原因是确定要即时显示哪些消息。我的计划是继续为每个用户创建一个消息 ID 的 Redis 列表。假设我在收到 Timeline API 请求时正确构建了此内容,我可以调用 Redis 来获取要显示的消息 ID 的预处理有序列表。例如,我可能会得到这样的结果:“[21, 18, 15, 14, 8, 5]”

2) 使用 Memcached 缓存单个消息对象

虽然我相信第一点会有很大帮助,但从数据库中检索各个消息对象仍然存在潜在的问题。消息对象可能会变得相当大。通过它们,我返回相关对象,例如评论、点赞、用户等。理想情况下,我也会缓存这些单独的消息对象。这就是我困惑的地方。

如果没有缓存,我只需进行如下查询调用即可检索消息对象:

@messages = Message.where("id in (?)", ids_from_redis)

然后我会返回时间线:

respond_with(:messages => @messages.as_json) # includes related likes, comments, user, etc.

现在考虑到我希望利用 Memcache 来检索单个消息对象,看来我需要一次检索一个消息。使用伪代码我在想这样的事情:

ids_from_redis.each do |m|
  message = Rails.cache.fetch("message_#{m}") do
     Message.find(m).as_json
  end
  @messages << message
end

这是我的两个具体问题(抱歉冗长的构建):

1) 这种方法通常有意义吗(redis 用于列表,memcached 用于对象)?

2)具体来说,在下面的伪代码中,这是唯一的方法吗?逐一抓取消息感觉效率很低,但考虑到我打算进行对象级缓存,我不确定还能怎么做。

感谢任何反馈,因为这是我第一次尝试这样的事情。

最佳答案

从表面上看,这似乎很合理。 Redis 非常适合存储列表等,可以持久化等,并且 memcached 检索单个消息的速度非常快,即使您像这样顺序调用它。

这里的问题是,每次发布消息时,您都需要清除/补充 Redis 缓存。在这种情况下,仅仅清除缓存似乎有点浪费,因为您已经费尽心思识别消息的每个收件人。

那么,不想回答错误的问题,您是否考虑过在发布每条消息时将消息的可见性“呈现”到数据库(或 redis)中?像这样的事情:

class Message
  belongs_to :sender
  has_many   :visibilities

  before_create :render_visibility
    sender.followers.each do |follower|
      visibilities.build(:user => follower)
    end
  def 
end

然后您可以非常简单地呈现消息列表:

class User
  has_many :visibilities
  has_many :messages, :through => :visibilities
end

# in your timeline view:
<%= current_user.messages.each { |message| render message } %>

然后我会添加这样的单独消息:

# In your message partial, caching individual rendered messages:
<%= cache(message) do %>
  <!-- render your message here -->
<% end %>

然后我还会添加整个时间线的缓存,如下所示:

# In your timeline view
<%= cache("timeline-for-#{current_user}-#{current_user.messages.last.cache_key}") do %>
  <%= current_user.messages.each { |message| render message } %>
<% end %>

应该实现的(我没有测试过)是整个时间线 HTML 将被缓存,直到发布新消息。发生这种情况时,时间线将重新呈现,但所有单独的消息都将来自缓存,而不是再次呈现(任何其他人没有查看过的任何新消息可能除外!)

请注意,这假设每个用户的消息呈现都是相同的。如果不是,您还需要缓存每个用户的消息,这会有点遗憾,所以如果可以的话尽量不要这样做!

FWIW,我相信这模糊地(我的意思是模糊地)是 Twitter 所做的。不过,他们采用了“大数据”方法,将推文分解并插入到大型机器集群的关注者时间线中。我在这里描述的内容将很难在具有大量关注者的写入密集型环境中进行扩展,尽管您可以通过使用 resque 或类似的方法来稍微改进这一点。

附注我对这里的代码有点懒 - 你应该考虑重构它以移动例如将时间线缓存 key 生成到助手和/或人员模型中。

关于ruby-on-rails - 使用缓存来优化 Rails 中的时间线,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14206376/

相关文章:

ruby-on-rails - 即使未更改,也停止 ActiveRecord 保存序列化列

css - 当我的页面引用在 HTTPS 中时,为什么 Facebook 会在 HTTP 中为我的应用程序的 css 插入预加载脚本?

mysql - 哪些 Hibernate 查询会命中第二层缓存?

php - PHP 中的持久内存缓存 - 服务器池不断增长,直到 curr_connections 10

javascript - 开发中的 Rails 4 Long Coffeescript 编译时间

ruby-on-rails - Rails - 如何复数一个句子?

html - Rails CSS、JS 和 jQuery TypeErrors(适用于 Windows)

caching - 没有过期的 header 发送,内容被缓存,浏览器发出条件GET请求之前需要多长时间?

asp.net-mvc - 如何使用 linq2sql 存储库在我的 Asp.net Mvc 中实现缓存策略?

python - 如何以原子方式将项目添加到 memcached 列表(在 Python 中)