Rails是否共享 – 或者可以将请求分开访问相同的运行时变量?

PHP在无共享环境中运行,在这种情况下,这意味着每个Web请求都在一个干净的环境中运行。 除了通过单独的持久层(文件系统,数据库等)之外,您无法访问其他请求的数据。

Ruby on Rails怎么样? 我刚读了一篇博文,说明单独的请求可能会访问同一个类变量。

我想到这可能取决于Web服务器。 Mongrel的FAQ说明Mongrel每个请求使用一个线程 – 建议无共享环境。 FAQ继续说RoR不是线程安全的,这进一步表明RoR不会存在于共享环境中,除非新请求重用从先前请求创建的内存中对象。

显然这具有巨大的安全后果。 所以我有两个问题:

  1. RoR环境是否共享?
  2. 如果RoR在共享环境中运行(或者可能在某些情况下运行),那么我应该对哪些变量和其他数据存储感到偏执?

更新:我会进一步澄清。 在Java servlet容器中,您可以拥有跨多个请求持久化的对象。 这通常用于缓存多个用户可以访问的数据,数据库连接等。在PHP中,这不能在应用程序层完成,它必须在像Memcached这样的单独的持久层中完成。 所以双重问题是:哪种情况是RoR(PHP或Java),如果像Java一样, 哪些数据类型在多个请求中持续存在?

简而言之:

  1. 不,Rails 永远不会在无共享环境中运行。
  2. 类变量类实例变量要偏执。

版本较长:

Rails进程通过加载框架和应用程序来开始其生命周期。 它们通常只运行一个线程,它将在其生命周期内处理许多请求。 因此,将严格按顺序发送请求。

尽管如此,所有课程仍然存在于请求中。 这意味着从您的类和元类引用的任何对象(例如类变量和类实例变量) 在请求之间共享。 例如,如果您尝试在方法中记忆方法( @var ||= expensive_calculation ), 这可能会让您感到困惑,期望它只会在当前请求期间保持不变。 实际上,计算仅在第一次请求时执行。

从表面上看,实现缓存或依赖于跨请求持久性的其他行为似乎很不错。 通常,事实并非如此。 这是因为大多数部署策略将使用多个 Rails进程来抵消它们自己的单线程特性。 在等待慢速数据库查询时阻止所有请求根本不是很酷,因此最简单的方法是生成更多进程。 当然,这些过程不共享任何东西(除了一些内存,你可能不会注意到)。 如果您在请求期间保存类变量或类实例变量中的内容, 这可能会让您感到困惑。 然后,不知何故,有时候东西似乎存在,有时它似乎消失了。 (实际上,当然,数据在某些过程中可能存在也可能不存在,而在其他过程中则不存在)。

一些部署配置(最值得注意的是JRuby + Glassfish)实际上是multithreading的。 Rails是线程安全的,所以它可以处理它。 但您的应用程序可能不是线程安全的。 每次请求后都会抛弃所有控制器实例,但正如我们所知,这些类是共享的。 如果您在类变量或类实例变量中传递信息, 这可能会让您感到困惑。 如果你没有正确使用同步方法,你很可能最终处于竞争状态地狱。


作为旁注:Rails通常在单线程进程中运行,因为Ruby的线程实现很糟糕。 幸运的是,Ruby 1.9中的情况要好一些。 在JRuby中好多了。

随着这些Ruby实现越来越受欢迎,似乎multithreadingRails部署策略也将获得流行和数量。 最好在编写具有multithreading请求调度的应用程序时考虑好。

这是一个相对简单的示例,说明如果您不小心修改共享对象会发生什么。

  1. 创建一个新的Rails项目: rails test

  2. 创建一个新文件lib/misc.rb并输入:

     class Misc @xxx = 'Hello' def Misc.contents() return @xxx end end 
  3. 创建一个新的控制器: ruby script/generate controller Posts index
  4. 更改app/views/posts/index.html.erb以包含此代码:

     <% require 'misc'; y = Misc.contents() ; y << ' (goodbye) ' %> 
    <%= y %>

    (这是我们修改隐式共享对象的地方。)

  5. 将RESTful路由添加到config/routes.rb
  6. 启动服务器ruby script/server并多次加载页面/posts 。 在每个连续的页面重新加载时,您将看到( goodbye)字符串的数量增加1。

在使用Passenger的平均部署中,您可能有多个应用程序进程,它们之间没有任何共享,但每个进程中的类都保持从请求到请求的(静态)状态。 但是,每个请求都会生成控制器的新实例。

您可以将其称为不同共享状态环境的集群。

要使用Java类比,您可以执行缓存并使其从请求到请求工作,您不能认为它将在每个请求中可用。

无共享有时候是个好主意。 但是,当您必须在每个请求上加载大型应用程序框架和大型域模型以及大量配置时。

为了提高效率,Rails会在内存中保留一些数据,以便在应用程序生命周期的所有请求之间共享。 大部分数据都是只读的,因此您不必担心。

当您编写应用程序时,请远离写入共享对象(例如,除了数据库之外,它具有良好的并发控制的开箱即用),您应该没问题。