如何在Rails 4中同时处理多个请求?

34
我将尝试在Rails 4中同时处理多个请求,这是我在Rails 3中使用config.threadsafe!Puma非常容易实现的功能。
假设我有以下控制器:
class ConcurrentController < ApplicationController
  def index
    sleep 10000
  end

  def show
  end
end

之前我可以使用puma -t 2:16 -p 3000(用于至少2个线程,端口为3000)启动puma,并访问indexshow页面,且show页面能正常渲染。

但是在Rails 4中,如果我尝试做同样的事情,Puma会在index请求上锁定,导致show页面无法渲染。当我停止服务器时,Puma会给我这个错误:

Rack app error: #<ThreadError: Attempt to unlock a mutex which is locked by another thread>

我在Rails 4中如何实现并发?不需要使用config.threadsafe!(即使尝试使用也没有任何影响),请问我还需要做什么?

你尝试在生产环境下运行过这个程序吗? - Abdo
Rails 4 应该没有任何区别。确实,Rails 4 默认就是多线程的。肯定存在你的环境/配置中引起差异的其他因素。你是如何启动 Puma 的?使用命令行中的那个命令吗?你还能分享一下 development.rb 文件中的其他相关信息吗? - John Bachir
@fredrik - 请审查我的回答。我花时间解释了允许并发的选项,并提供了一些参考资料。 不幸的是,尽管你的答案可以导致实际解决方案,但它并不完全正确。 - Ely
我默认使用Rails 5.0.3,并在config/environments/development.rb中将config.cache_classes = false,但是没有遇到这个问题。 - kbridge4096
尝试使用多个工作进程而不是仅使用多个线程,使用-w 2。这将启动两个应用程序实例来处理请求。 - Joshua Pinter
3个回答

38

我邀请您阅读这篇文章Removing config.threadsafe!,了解有关config.threadsafe!的配置选项。

这将帮助您更好地理解config.threadsafe!的选项,特别是允许并发。

在Rails 4中,默认情况下会设置config.threadsafe!


现在回答问题

在Rails 4中,默认情况下,在DEV环境中将请求包装在Rack :: Lock中的Mutex中。

如果您想要启用并发,可以设置config.allow_concurrency=true。 这将禁用Rack::Lock中间件。 我不会像其他答案中提到的那样将其删除;那对我来说看起来像是一种破解。

注意:如果您设置了config.cache_classes=true,则对config.allow_concurrency(Rack :: Lock请求互斥锁)的分配将无效,默认情况下允许并发请求。 如果您设置了config.cache_classes=false,则可以将config.allow_concurrency设置为truefalse。 在DEV环境中,您希望像这样设置。

config.cache_classes=false
config.allow_concurrency=true

该说法:“这意味着如果config.cache_classes = false(在开发环境中默认为false),我们无法进行并发请求。”是不正确的。

附录

您可以参考此答案,其中设置了使用MRI和JRuby测试并发性的实验。结果令人惊讶。MRI比JRuby更快。

MRI并发性实验可在GitHub上找到。 该实验仅测试并发请求。控制器中没有竞争条件。但是,我认为从上述文章中实现示例以测试控制器中的竞争条件并不太困难。


这看起来是正确的,我会将其标记为正确答案,尽管我还没有花时间测试它。不过我要注意一下,config.allow_concurrency是一个完全未记录的功能[1]。它出现在Rails 3的文档中[2],但在Rails 4中没有出现。 - Fredrik
是的,没错。我们现在对那个配置选项未来的使用没有太多的文档资料。我还记得,你需要考虑将数据库连接的连接池大小设置为最大线程数相同的值。在你的情况下是16。 - Ely

22
似乎在Rails 4中,默认情况下,开发环境未启用并发请求。我在文档中找到了这段引用。

Rack::Lock将应用程序包装在互斥锁中,因此一次只能由一个线程调用。仅在config.cache_classes为false时启用。

这意味着如果config.cache_classes=false(默认情况下在dev env中),我们无法进行并发请求。在生产环境中,我的示例确实可以使用并发请求。
一个解决方法是在开发环境中设置config.cache_classes=true,但这样代码不会在更改后重新加载,这对于开发并不真正有效。第二种hacky的解决方法是在开发环境中禁用Rack::Lock中间件。
因此,如果您想要在development.rb中添加以下行:
config.middleware.delete Rack::Lock

在开发环境中,您应该能够禁用缓存类,并具备并发请求的能力。


你使用的是MRI Ruby还是JRuby?我印象中在这种情况下,多线程在MRI Ruby中不起作用。 - Arthur Frankel
删除此中间件 @Fredrik 会有什么缺点? - João Paulo Motta
删除 Rack::Lock 对我来说效果不好。它加载了页面,但是连接数据库时会给我超时错误:"ActiveRecord::ConnectionNotEstablished (ActiveRecord::ConnectionNotEstablished):" - Andre Figueiredo
@AndreFigueiredo 你确定已经配置了与你的应用程序尝试运行的线程一样多的数据库连接池吗? - Fredrik
是的,它配置正确。没有“删除Rack:Lock”,它可以正常工作,但加上后就会失败。即使是静态资源也无法加载。 - Andre Figueiredo
显示剩余2条评论

-1

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