2

我希望就如何正确使用缓存来加快 Rails 中的时间线查询获得建议。这是背景:

我正在开发一个带有 Rails 后端的 iPhone 应用程序。它是一个社交应用程序,与其他社交应用程序一样,它的主要视图是消息的时间线(即新闻源)。这很像 Twitter,时间线由用户和他/她的追随者的消息组成。API 请求中检索时间线的主要查询如下:

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

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

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

使这个查询如此昂贵的部分原因是确定要即时显示哪些消息。我的计划是继续为每个用户创建一个 Redis 消息 ID 列表。假设我在收到 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)具体来说,在下面的伪代码中,这是唯一的方法吗?一个接一个地抓取消息感觉效率低下,但考虑到我打算进行对象级缓存,我不确定该怎么做。

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

4

1 回答 1

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 或类似的东西来稍微改善这一点。

PS 我对这里的代码有点懒惰——你应该考虑重构它以将时间线缓存键生成移动到帮助器和/或人员模型中。

于 2013-01-08T01:08:21.213 回答