Rails:如何处理像Thin / Unicorn这样的单线程服务器下的Thread.current数据?

时间:2012-12-11 22:03:43

标签: ruby-on-rails thread-safety thin unicorn

由于Thin / Unicorn是单线程的,你如何处理Thread.current / per-request存储?

只是运行一个简单的测试 - 在一个会话中设置一个密钥,从另一个会话中读取它 - 看起来它始终从同一个地方写入/读取。但不会发生在WEBrick上。

class TestController < ApplicationController
  def get
    render text: Thread.current[:xxx].inspect
  end

  def set
    Thread.current[:xxx] = 1

    render text: "SET to #{Thread.current[:xxx]}"
  end
end

尝试将config.threadsafe!添加到a​​pplication.rb,无需更改。

存储每个请求数据的正确方法是什么?

为什么有使用Thread.current存储的宝石(包括Rails本身和倾斜)?他们如何克服这个问题?

对于每个请求,Thread.current 是否是安全的,但是在请求之后不能清除,我自己需要这样做吗?

  • 使用Rails 3.2.9进行测试

更新

总结以下与@skalee和@JesseWolgamott以及我的调查结果的讨论 -

Thread.current取决于运行应用程序的服务器。虽然服务器可能确保在同一个Thread.current上没有同时运行两个请求,但是这个哈希中的值可能不会在请求之间被清除,因此在使用时 - 必须设置初始值覆盖最后一个值。

有一些众所周知的宝石使用Thread.current,如Rails,倾斜和布料。我猜测如果它被禁止或不安全,他们就不会使用它。在使用散列上的任何键之前,它们似乎都设置了一个值(甚至在请求结束后将其设置回原始值)。

但总的来说,Thread.current不是每个请求存储的最佳实践。对于大多数情况,更好的设计会做,但在某些情况下,使用env会有所帮助。它可以在控制器中使用,也可以在中间件中使用,并且可以注入应用程序中的任何位置。

更新2 - 似乎现在,draper正在使用Thread.current错误。见https://github.com/drapergem/draper/issues/390

更新3 - 修正了错误。

3 个答案:

答案 0 :(得分:0)

您通常希望在会话中存储内容。如果你想要一些非常短暂的生活,请参阅Rails' flash。每次请求都清除它。任何依赖线程的方法都不会在不同的Web服务器上一致地工作。

另一种选择是修改env哈希:

env['some_number'] = 5

BTW Unicorn不仅仅是单线程,而是分叉。每个请求都会产生新进程(尽管它听起来很吓人,但在Linux上效率很高)。因此,如果您在Unicorn中设置任何内容,甚至是全局变量,它将不会持久存储到另一个请求。

答案 1 :(得分:0)

虽然人们仍然警告不要使用Thread.current存储“线程全局”数据,但在Rails中执行此操作的正确方法是使用Rack中间件清除Thread.current对象。 Steve Labnik编写了request_store gem来轻松完成这项工作。宝石的源代码非常非常小,我建议你阅读它。

有趣的部分转载如下。

module RequestStore
  def self.store
    Thread.current[:request_store] ||= {}
  end

  def self.clear!
    Thread.current[:request_store] = {}
  end
end


module RequestStore
  class Middleware
    def initialize(app)
      @app = app
    end

    def call(env)
      RequestStore.clear!
      @app.call(env)
    end
  end
end

请注意,清除整个 Thread.current不是一个好习惯。 request_store基本上是做什么的,它是跟踪你的应用程序存入Thread.current的密钥,并在请求完成后清除它。

答案 2 :(得分:0)

使用Thread.current的一个注意事项是,对于重用线程或拥有线程池的服务器,在每次请求后进行清理变得非常重要。

这正是request_store gem提供的内容,一个类似于Thread.current的简单API,用于在每次请求后清理商店数据。

RequestStore[:items] = []

请注意,gem使用Thread.current来保存商店,因此它在多线程环境中无法正常工作,每个请求都有多个线程。

为了解决这个问题,我实现了一个可以在线程之间共享同一请求的商店。它被称为request_store_rails,用法非常相似:

RequestLocals[:items] = []