Heroku上Thin、Sinatra应用程序中的用户线程

5

我有一组针对我的应用程序的每个单独用户具体操作,这全部包含在一个方法中(例如write_collections方法)。在此方法中,程序与Facebook和MongoDB通信。我想为每个用户在线程中运行此方法。

这个线程被称为get '/' Sinatra路由,但是线程结果(数据库中的状态)只需要在get '/calculate'上使用。我的想法是在get '/'上运行线程,在get '/calculate'上加入它以确保在开始计算用户结果之前所有用户数据都已正确写入数据库。

举个例子:


方法 I

get "/" do    
  @user = @graph.get_object("me")

  data_thread = Thread.new do
    write_collections(@user)
  end

  session[:data_thread] = data_thread.object_id

  erb :index
end

get "/calculate" do
  begin
  # Is this safe enough?
  if ObjectSpace._id2ref(session[:data_thread]).alive?
      data_thread = ObjectSpace._id2ref(session[:data_thread])
      data_thread.join
  end
  rescue RangeError => range_err
    # session[:data_thread] is not id value
    # direct access to /calculate without session
  rescue TypeError => type_err
    # session[:data_thread] is nil
  end

  # do calculations based on state in database
  # and show results to user

  "<p>Under construction</p>"
end

为了找到特定用户应该等待加入的合适线程,我目前使用ObjectSpace._id2ref(session[:data_thread])
  • 这样安全吗?

详细信息

Object#object_id的官方Ruby文档中得知:

object_id → fixnum: 返回obj的整数标识符。对于给定对象,每次调用id时将返回相同的数字,在所有活动对象之间不会共享ID。

以及从ObjectSpace的官方文档中得知:

ObjectSpace模块包含许多与垃圾收集设施交互的例程,并允许你使用迭代器遍历所有活动对象。

  • 第一个引用中的“活动对象”和第二个引用中的“活动对象”是否相同?

    我们假设以下情况:

  1. 用户A访问'/' [现在启动了一个带有object_id aA线程]
  2. 线程A已完成[它不再处于活动状态,其object_id已被释放]
  3. 用户B访问'/' [现在启动了具有相同object_idaB线程(*可能吗?)]
  4. 用户A访问'/calculate' [session[:data_thread]a,因此ObjectSpace._id2ref(session[:data_thread])实际上是B线程。]
  5. 不一致状态-用户A正在等待线程B

    • 在Sinatra、Thin、Heroku中是否可能出现这种情况?

方法二

configure do
  # map user_id to corresponding user's thread
  data_threads_hash = {}
  set :data_threads_hash, data_threads_hash
end

get "/" do    
  @user = @graph.get_object("me")

  data_thread = Thread.new do
    write_collections(@user)
  end

  session[:user_id] = @user['id']
  settings.data_threads_hash[session[:user_id]] = data_thread

  erb :index
end

get "/calculate" do

  if settings.data_threads_hash[session[:user_id]].alive?
    data_thread = settings.data_threads_hash[session[:user_id]]
    data_thread.join
    settings.data_threads_hash.delete session[:user_id]
  end

  # do calculations based on state in database
  # and show results to user

  "<p>Under construction</p>"

end

详细的

在阅读Sinatra: README后,我尝试了这个方法。在配置(Configuration)中:

在任何环境下启动时运行一次 ... 您可以通过settings访问这些选项 ...

范围和绑定(Scopes and Binding)下,应用程序/类作用域(Application/Class Scope)

每个Sinatra应用对应于Sinatra::Base的子类。如果您使用顶级DSL(require 'sinatra'),则该类为Sinatra::Application,否则它是您显式创建的子类。在类级别上,您有像get或before这样的方法,但您无法访问请求或会话对象,因为所有请求都只有一个应用程序类

我正在使用顶级DSL。

通过set创建的选项是类级别的方法...您可以通过request scope中的settings访问范围对象(即类)

  • @FrederickCheung在评论中提到的内容以及从Scopes和Binding中引用的内容,请记住,如果我需要更多的dyno/worker(目前此应用程序仅使用一个dyno),那么这种方法是否有效?

总结

  • 在Sinatra中,我应该如何处理上述情况中的用户及其对应的线程,以上示例中的方法好坏如何?

欢迎任何评论或参考。


1
如果您需要超过一个工作进程/动态进程,则此方法将无法正常工作。 - Frederick Cheung
@FrederickCheung,您能澄清一下吗?我猜每个dyno/worker都有自己的Ruby解释器和自己的ObjectSpace,因此当程序使用ObjectSpace._id2ref(session[:data_thread])时,在需要它的解释器(dyno/worker)上可以省略data_thread对象。 - foki
准确地说,一个对象 ID 在另一个进程中是没有意义的。 - Frederick Cheung
1个回答

2
我不确定这个设计是否有意义。为什么每个用户都需要自己的线程?为什么一个请求会加入另一个请求创建的线程?即使这是可能的(只使用一个dyno),我认为这不是实现你想做的事情的好方法。
根据问题中应用程序的描述,您希望在write_collections方法完成后运行一些calculate方法。那么,为什么write_collections方法不能调用一些calculate方法呢?或者,为什么不能使用after filter或observer来进行计算?
更普遍地说,您似乎混淆了两个不同的功能:
1. 计算功能 2. GET /calculate 我认为只应该有一个触发器来调用calculate功能。它要么是在write_collections完成时,要么是用户请求(GET /calculate)时。
更通用的解决方案是在后台运行calculate功能,并将结果保存到数据库中。稍后,当用户发出请求时,它已经准备好并可以快速返回。

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