【问题标题】:Handling task counts while maintaining performance在保持性能的同时处理任务计数
【发布时间】:2012-05-15 12:47:51
【问题描述】:

我知道这是一个特定于应用程序的问题,但我想看看是否有人有一个我没有想到的聪明的解决方案。这是一个艰难的问题,因此对于任何可以优雅地解决这个问题的人来说都是要点(和道具)。

我有一个 ruby​​ on rails 应用程序,它具有包含任务计数的全局导航(见下图)

问题是生成这些计数需要一些密集的查询。而且因为它是全球性的,它在每一页上。当人们处理任务时,它们还需要更新。这使得它特别具有挑战性。

也就是说,现在我正在通过 memcached 将导航 HTML 缓存 10 分钟。如果有人负责一项任务,哦,好吧。导航每 10 分钟更新一次。糟糕的解决方案,我知道,但在我想出合适的解决方案之前,这是我的权宜之计。

我想避免在整个应用程序中添加一堆钩子(例如:处理交付,触发钩子以减少每个相关用户的计数)。请记住,这些计数涉及系统中的许多实体,并且逻辑有些复杂。它处理访问(每个用户根据权限系统有不同的计数),并且在某些情况下确定这些计数可能很复杂。 Hooks 很快就会变得一团糟,并且生成这些计数背后的逻辑将被重复。

任何帮助将不胜感激。

【问题讨论】:

  • 好问题。您对导航 HTML 所做的是缓存,那么为什么不应用相同的缓存概念将值存储在“计数”表中呢?毕竟,在某些时候,某些钩子会减少计数,因此请减少表中的计数。也应该有一些设计模式(希望如此)。

标签: ruby-on-rails ruby performance task


【解决方案1】:

如果排除添加用于自动递增/递减计数器的额外代码,解决方案之一是使用缓存并有效地使用它。伪代码如下。

class User

  def counters
    needs_update(user_id)
    memcache.read(counter_user_id)
  end

  def needs_update(user_id)
      some_job_queue_for_counters.insert(Job.new(User.update_counters(user_id)))
  end

  #executed by the job runner
  def update_counters user_id
     counter_hash = {:counter1 => complex_logic1, :counter2 => complex_logic2 ...}
     memcache.write(counter_user_id, counter_hash.to_json.gzipped)
  end

end

有几点需要注意。

  1. 从浏览器进行 AJAX 调用,例如每 1 分钟或更长时间一次,具体取决于执行计数器逻辑的时间。
  2. counters 方法尽可能快,因为 memcache.read() 返回一个压缩的 json 字符串。零处理到 生成响应。
  3. 您可以在开始时为所有用户预热一次内存缓存 的应用程序。然后在该计数器更新仅针对 调用 couters 方法的用户,即保留 浏览器/移动应用打开。
  4. needs_update 方法可以对 user_id 进行作业合并以避免重复。

【讨论】:

    【解决方案2】:

    更新:

    简单的解决方案是为所有受影响的更改用户删除所有缓存的导航 html。您还可以通过 ajax 调用加载 navHTML,这样您就不必等待加载页面来加载(并在客户端的 localStorage 中保留 navHTML 的缓存版本,并且无论何时加载页面,您都可以然后在需要时触发 navHTML 的重置)。

    更复杂的解决方案是:

    将此缓存在 2 层中(navHTML,然后是单个项目/用户/状态项目)。您必须找到一种方法来确定更新范围,以便他们知道应该触发重置的计数。因此,如果您更改状态机中的交付状态,您应该重置该交付的所有计数器,并删除受影响用户的导航缓存。

    我会以某种命名空间的方式将每个交付项的所有键放入缓存中,例如“delivery:234324-user:123-status”,因此每当 ID #234324 的交付发生更改时,您都会迭代并删除所有缓存属于该交付的项目(可能最好让它尽可能地保持沉默)。当有人请求页面时,您可以计算并将计数存储到缓存中,并在实际对象更改时执行缓存删除。然后,您可以完全缓存 nav html,但是每当其中包含您的用户和 id 的某些更改(例如 user-123)时,您会删除 nav 缓存并强制 navHTML 重新加载。还为导航添加某种缓存时间(例如最大年龄 1 分钟或其他内容)以解决错误。导航项应通过单个项的缓存构建,而不必遍历每个项。

    您可能希望添加连接到关联模型的缓存清扫器http://guides.rubyonrails.org/caching_with_rails.html


    这里是计数器缓存供参考(我认为构建数据库模型以允许使用计数器缓存是处理此问题的最佳方法)。

    http://railscasts.com/episodes/23-counter-cache-column

    可以通过 has_many 关系自动使用它,也可以手动更新计数。我假设如果你的模型很复杂并且你有一个多对多的关系,你可能会将计数器缓存放在一个关系对象上。

    【讨论】:

    • 谢谢。它不像计数器缓存那么简单。虽然我理解它背后的概念,但确定这些计数背后的大部分逻辑都在各种“范围”中。它们依赖于本地和外国的许多条件。我想唯一的解决方案是弄清楚如何做到这一点,我真的很想避免它,因为它会用钩子等乱扔我的应用程序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    相关资源
    最近更新 更多