在Ruby on Rails中,我如何在会话之间持久化对象到内存中?

18

我正在尝试构建一个东西(最终是一个gem,但现在是一个应用程序),其工作方式如下。

例如,假设DB记录是狗的品种。有一个Dog父类和每个品种的子类。实际的品种直到运行时才知道。

当服务器启动时,它将从数据库加载记录,并根据记录实例化类的实例,例如,我可能有两只比格犬和一只贵宾犬。当有人访问服务器时,他们可能想要访问其中一些狗的实例。

为什么不在运行时创建实例?在我的情况下,“狗”基本上是保存算法和数据的类。算法不会改变,数据很少更改(按天计算),但算法本身的执行(使用数据和一些传递的动态数据,例如时间戳)将被多次访问。

每次请求都重新创建对象并加载数据是愚蠢的,因为请求不会改变对象的状态。我将每秒创建和销毁多个对象,而我可以重复使用同一个对象。

将其保留在会话中没有意义,因为想要贵宾犬的人不应该在她的会话对象中具有比格犬的信息;这是无关紧要的(也不可扩展)。

我该如何在内存中持久化这些对象?我基本上想要一个查找表来保存这些实例。在Java中,我会创建一个单例,其中包含某种类型的哈希表或数组,它位于内存中。在Rails中,我尝试通过在lib文件夹中创建一个单例类来实现这一点。我认为——可能我没有理解正确——当会话消失时,实例(单例的事实是无关紧要的)丢失了。
我发现最接近的答案是http://www.ruby-forum.com/topic/129372,它基本上将所有内容放入类字段和方法中。但在某种程度上,这似乎不正确。
谢谢!
补充:我来自Java。在Java中,我只需创建一个对象,它就会坐落在堆上或者可能在JNDI树中,随着HTTP请求的到来,它们将被一个servlet或EJB处理,或者一些每个请求的项可以访问持久化对象。我似乎找不到在Rails中的等效方法。

+1 for 这个问题。我有一棵树用于查找目的。它不会改变,我不想在每个请求中构建它。 - lulalala
5个回答

13

也许你的例子过于简单,容易让人产生困惑。我假设你的对象非常复杂,并且你的基准测试表明在每个请求中构建它们是不合理的。

在生产模式中,类不会在请求之间卸载,但是这些类的实例会被卸载。因此,对我来说,使用类的成员似乎是不错的选择。只需使用它来存储你的实例即可。

class ObjectCache
  @@objects = {:beagle => Beagle.new, :poodle => Poodle.new}

  def lookup key
    @@objects[key.to_sym]
  end
end

感谢回复。你提到了“生产模式”。我正在尝试在开发模式下进行操作,但是在请求之间仍然丢失了对象(即使方法也是类方法,例如def self.lookup key),但可能是因为模式不同。暂时让它工作对我来说已经足够了,但坦白地说,这种做法真的感觉不太对。 - hershey
当你在请求之间保留 ActiveRecord 模型的实例时,它的行为并没有被很好地定义,所以最好避免这样做。如果你只是存储普通的 Ruby 对象,那么它可能会起作用。 - tadman
1
你也可以在开发模式下防止类被卸载。 - aceofspades
我并没有在谈论“unloadable”,但关于这个话题,我还没有找到任何具体细节解释为什么它被认为是已过时的,或者实现同样效果的方法。不过我不认为它适用于这个例子。例如,插件的类即使在开发模式下也不会默认卸载。因此这就是你需要的行为方式。 - aceofspades
它并没有被弃用,只是被移动了。 - aceofspades
显示剩余2条评论

6
我不会过于担心加载和丢弃对象,除非你能提供证明这是一个问题的基准测试。每个请求通常会创建大量中间对象,这些对象通常在几毫秒内被创建和销毁。
通常最好专注于仅加载所需内容,对数据库进行反规范化以将经常访问的数据或方法推入方便的位置,或将复杂计算结果保存在缓存字段中。
先进行基准测试,只有在必要时才进行优化。
将模型实例保存到类缓存中可能有效,但仅适用于生产环境中模型类不会在每个请求中重新加载的情况下。它还可能使您面临由陈旧数据引起的错误。
如果您有无法使用这些方法解决的扩展问题,则可能需要使用Rack和EventMachine组合构建部分功能的持久服务器。有许多构建后台进程以使用预加载数据集执行复杂计算的方法,但具体方法将取决于诸如您正在处理的数据类型以及访问频率等几个因素。

感谢您抽出时间回复。除了扩展之外,这似乎不是正确的编程方式。当对象本身可以被重用时,连续重新创建相同的对象从面向对象的角度来看是错误的。Rack似乎使用了比Rails更轻量级的版本,我没有在文档中看到任何暗示它可以做到Rails不能做到的持久性。EventMachine似乎是正确的答案。http://www.neeraj.name/2009/12/15/ruby-eventmachine-a-short-introduction.html有一个很好的解释。我将深入研究一下。 - hershey
HTTP的无状态特性经常导致这样的问题,即一个请求可能与下一个请求毫不相关,应用程序框架(如Rails)通常必须清理上一个请求中使用的所有对象,以为下一个请求腾出空间。如果您需要一个持久的环境,请构建一个在后台运行的引擎,您将发现巨大的性能提升,不仅可以避免冗余的重新加载,还可以进行异步操作。编写一个“说话”的Memcache协议服务器非常容易,即使它计算答案,也可以像常规缓存一样使用。 - tadman
我说得太早了。EventMachine仅在连接内持久存在。我将无法跨Web请求保持连接,因此仍会丢失状态。还有其他构建引擎的方法吗? - hershey
我正在谈论构建一个作为独立进程运行的EventMachine引擎,而不是在Rails环境中构建EventMachine模块。你说要加载多少对象?大多数现代硬件可以每秒导入和实例化100K个对象,而不需要太多的优化。 - tadman
不是对象的数量很多,所以在早期它可能可以扩展得还不错。这更多是哲学上的问题,因为某些事情感觉不对劲。如果您查看我在原始帖子底部所做的编辑,您会看到我在 Java 中多次完成的操作方式。我本可以重新创建对象(在某些但不是所有情况下),但 Java 让我将对象保留在堆内存中(跨任何和所有会话)。感觉很奇怪,Rails 不能做到这一点,一个有用的功能缺失了。 - hershey
尽管 Java 方法可能更好,因为它不会频繁重新加载相同的记录,但这也是可能的,因为 Java 框架的设计假定这种对象在请求之间将持久存在。Rails 从一开始就被设计成每个请求通常都存在于自己的上下文中,并且很少从一个请求传递到另一个请求。 "Rails Way" 就是经常重新加载以确保其是最新的,并在必要时去规范化或缓存。我发现这种方法通常已经足够好了。 - tadman

1
在生产环境中,控制器和模型类在请求之间不会重新加载,因此您有几个选项:
  • 将对象设置为应用程序控制器中的类变量
  • 在模型类本身中创建单例方法来返回值

0
为了在开发模式下避免每次请求都重新加载类,您可以将它们移动到自动加载路径之外的某个地方(如果使用默认值,则在应用程序和lib目录之外)。这样,您就可以使这些对象类在请求之间持续存在,从而使用它们来存储对于每个请求都相同的数据。

0

是的,在开发模式下也可以防止类被卸载。这不仅仅是生产模式的事情。虽然在生产模式下默认会发生这种情况,在开发模式下必须手动设置。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接