我有一个带有以下版本的Ruby on Rails应用程序:
Ruby:1.9.3-p547
Rails:3.2.16
我在此应用程序中遇到了一些性能问题。我最初的诊断工作让我开始使用https://github.com/joelhegg/rack_timer文章中提到的RackTimer gem(http://www.codinginthecrease.com/news_article/show/137153)记录中间件时间戳。
使用它我发现Rack :: Lock消耗了大量的时间。例如以下 是下面提供的日志中的一些RackTimer日志:
Rack Timer (incoming) -- Rack::Lock: 73.77910614013672 ms
Rack Timer (incoming) -- Rack::Lock: 67.05522537231445 ms
Rack Timer (incoming) -- Rack::Lock: 87.3713493347168 ms
Rack Timer (incoming) -- Rack::Lock: 59.815168380737305 ms
Rack Timer (incoming) -- Rack::Lock: 55.583953857421875 ms
Rack Timer (incoming) -- Rack::Lock: 111.56821250915527 ms
Rack Timer (incoming) -- Rack::Lock: 119.28486824035645 ms
Rack Timer (incoming) -- Rack::Lock: 69.2741870880127 ms
Rack Timer (incoming) -- Rack::Lock: 75.4690170288086 ms
Rack Timer (incoming) -- Rack::Lock: 86.68923377990723 ms
Rack Timer (incoming) -- Rack::Lock: 113.18349838256836 ms
Rack Timer (incoming) -- Rack::Lock: 116.78934097290039 ms
Rack Timer (incoming) -- Rack::Lock: 118.49355697631836 ms
Rack Timer (incoming) -- Rack::Lock: 132.1699619293213 ms
可以看出,Rack :: Lock中间件处理时间在10毫秒到130秒之间波动。 其中大部分是在我的主页上提供资产时出现的。
BTW我在config / application.rb
中启用了Asset Pipeline # Enable the asset pipeline
config.assets.enabled = true
我的生产版应用程序配置为由NewRelic监控。图表图表突出显示了Rack :: Lock占用的最高%和时间。
对于使Rack :: Lock花费这么多毫秒的贡献,我完全是空白。如果社区中的任何人能够提供有价值的指导,找出可能导致这种情况的原因以及如何解决这个问题,我将不胜感激。
下面可以找到Gemfile,涉及的所有中间件和Dev环境日志。
的Gemfile:
https://gist.github.com/JigneshGohel-BoTreeConsulting/1b10977de58d09452e19
参与的中间件:
https://gist.github.com/JigneshGohel-BoTreeConsulting/91c004686de21bd6ebc1
开发环境日志:
-----首次加载家庭指数页面
https://gist.github.com/JigneshGohel-BoTreeConsulting/990fab655f156a920131
-----第二次加载主页索引页面而不重新启动服务器
https://gist.github.com/JigneshGohel-BoTreeConsulting/f5233302c955e3b31e2f
谢谢, Jignesh
答案 0 :(得分:19)
我在上面发布我的问题之后在这里发布了我的发现。希望在最终处于上述情况时,其他人可以从这些发现中受益。
与我的一位高级助手 Juha Litola 讨论 Rack :: Lock 问题,下面是他的第一个想法(引用他自己的话):< / p>
你是否有可能看到测量神器意味着你只是看到Rack :: Lock需要花费很多时间,但这只是因为它正在包裹实际的呼叫?因此,Rack :: Lock时间是请求处理中发生的所有事件的累积时间。见
https://github.com/rack/rack/blob/master/lib/rack/lock.rb。
至于性能问题,你能详细说明你有什么样的问题让我可以提供帮助吗?
我认为这可能是一种可能性。然而,由于以下疑问,我无法用这种可能性说服自己:
Rack::Lock
处于Middlewares链中的第二个位置,带有Rails应用程序(请参阅我在https://gist.github.com/JigneshGohel-BoTreeConsulting/91c004686de21bd6ebc1上面的帖子中提到的中间件列表)。并且每个中间件在链中按顺序处理。因此Rack::Lock
将是处理请求的第二个
然后链中的其他人将有机会跳入。
根据我的理解,我不认为Rack :: Lock中间件记录的时间戳是请求处理中发生的所有事件的累积时间。它应该是Rack :: Lock所用的时间中间件本身。
后来Juha,花了几分钟看服务器(见下面的注释)配置提供以下输入:
快速浏览一下,我认为应用程序的配置方式存在一个非常明显的问题。 应用程序没有config.threadsafe!启用,这意味着启用了Rack :: Lock,并且请求处理仅限于一个线程/进程。现在puma配置只有一个进程,但是16-32个线程。这意味着puma目前只在特定时刻处理一个请求。
最好的解决方案当然是你可以启用线程安全模式,但这需要彻底的测试。 如果失败或不是一个选项,puma应配置多个工人,每个工作者有1个线程。
注意:我忘记添加有关部署了我的应用程序的Web服务器配置的任何详细信息。我们正在使用Puma Web Server(https://github.com/puma/puma)
有了这个,我得到了一些提示,可以深入了解 config.threadsafe!。在网上搜索我进入了以下文章
http://www.sitepoint.com/config-threadsafe/
http://tenderlovemaking.com/2012/06/18/removing-config-threadsafe.html
如何启用或禁用 config.threadsafe!选项会影响生产中多线程或多进程Web服务器上部署的应用程序的性能。
上述文章的简要摘要
什么是Rack :: Lock?
Rack :: Lock是一个中间件,插入到Rails中间件堆栈中,以保护我们的应用程序免受多线程Bogeyman的攻击。这个中间件应该通过使用互斥锁包装我们的请求来保护我们免受恶劣的竞争条件和死锁。中间件在请求开始时锁定互斥锁,并在请求完成时解锁互斥锁。
假设有一个程序正在运行并同时向代码(比如Controller)不是线程安全的应用程序发送100次请求100次。
现在让我们观察Rack :: Lock中间件,config.threadsafe组合的影响!选项启用或禁用,应用程序中的线程不安全代码,以及多线程或多进程Web服务器,程序完成或被杀后
多线程Web服务器(Puma)
# Combination 1:
config.threadsafe! option : Disabled
Rack::Lock middleware : Available in app's middleware stack because of config.threadsafe! option disabled
With this combination the web server is successfully able to entertain all the 500 requests received.
This is because each request is augmented by Rack::Lock so as to execute it synchronously.In other words
Rack::Lock ensures we have only one concurrent request at a time.Thus each of the 500 requests gets a chance to
execute.
# Combination 2:
config.threadsafe! option : Enabled
Rack::Lock middleware : Unavailable in app's middleware stack because of config.threadsafe! option enabled
With this combination the web server is able to entertain only 200 out of 500 requests received.
This is because of the absence of Rack::Lock middleware, which ensures that we have only one concurrent request
at a time and thus each request gets a chance.
However there are advantages as well as disadvantages of each combination mentioned above:
# Combination 1
Advantage:
Each of the request received gets chance to be processed
Disadvantage:
* The runtime to process all of the 500 requests took 1 min 46 secs (compare it to runtime of Combination 2)
* Using a multi-threaded webserver is useless, if Rack::Lock remains available in middleware stack.
# Combination 2
Advantage:
The runtime to process 200 requests took 24 secs (compare it to runtime of Combination 1).
The reason being the multi-threaded nature of webserver is being leveraged in this case to entertain concurrent requests coming in.
Disadvantage:
* Not all 500 requests got a chance to be processed
注意:示例和运行时统计信息引自http://tenderlovemaking.com/2012/06/18/removing-config-threadsafe.html
多进程Web服务器(Unicorn)
# Combination 1:
config.threadsafe! option : Disabled
Rack::Lock middleware : Available in app's middleware stack because of config.threadsafe! option disabled
Since multiple processes are forked by the webserver and each of them listens for requests and also
Rack::Lock middleware is available, the web server is successfully able to entertain all the 500 requests received.
# Combination 2:
config.threadsafe! option : Enabled
Rack::Lock middleware : Unavailable in app's middleware stack because of config.threadsafe! option enabled
Here too multiple processes are forked by the webserver and each of them listens for requests,
however Rack::Lock middleware is unavailable which enables multi-threading, which in turn means that we'll
get a race condition in the thread-unsafe code we have in the application.But strangely with this combination
too the web server is successfully able to entertain all the 500 requests received.
The reason being a process-based web server creates worker processes and each process holds one instance of
our application. When a request is received webserver spawns a child process for handling it.
<强>结论:强>
在多进程环境中,如果我们保持config.threadsafe,Rack :: Lock就变得多余了!选项已禁用。 这是因为在多进程环境中,套接字是我们的锁,我们不需要任何额外的锁。 因此启用config.threadsafe是有益的!并删除生产环境中的Rack :: Lock开销。
如果我们保留config.threadsafe,在多线程环境中!使开发人员能够确保应用程序的代码是线程安全的。 并保持config.threadsafe的优势!是需要较少的运行时来处理传入的请求。
在我的应用程序的上下文中,我们通过增加工作人员来调整Puma服务器的配置。我希望性能得到改善。