MRI Ruby的并发请求

Art*_*kel 26 ruby multithreading ruby-on-rails multiprocessing ruby-on-rails-4

我把一个简单的例子放在一起,尝试使用一个基本的例子来证明Rails中的并发请求.请注意,我使用的是MRI Ruby2和Rails 4.2.

  def api_call
    sleep(10)
    render :json => "done"
  end
Run Code Online (Sandbox Code Playgroud)

然后我在我的Mac(I7/4 Core)上的Chrome中找到4个不同的选项卡,看看它们是以串行还是并行方式运行(真正并发的是关闭但不是同一个东西).即,http:// localhost:3000/api_call

我不能使用Puma,Thin或Unicorn来使用它.每个请求都是串联的.10秒之后的第一个标签,20之后的第二个标签(因为它必须等待第一个完成),之后是第三个....

从我所读到的,我相信以下是真实的(请纠正我)并且是我的结果:

  • Unicorn是多进程的,我的例子应该有效(在定义unicorn.rb配置文件中的worker数量之后),但事实并非如此.我可以看到4名工人开始,但一切都在串联.我正在使用unicorn-rails gem,使用unicorn -c config/unicorn.rb启动rails,在我的unicorn.rb中我有:

- unicorn.rb

worker_processes 4
preload_app true
timeout 30
listen 3000
after_fork do |server, worker|
  ActiveRecord::Base.establish_connection
end
Run Code Online (Sandbox Code Playgroud)
  • Thin和Puma是多线程的(虽然Puma至少有一个' 集群 '模式,你可以使用-w参数启动worker)并且不应该使用MRI Ruby2.0无论如何(在多线程模式下),因为"有一个全局解释器锁(GIL)确保一次只能运行一个线程".

所以,

  • 我有一个有效的例子(或使用睡眠错误)?
  • 我上面关于多进程和多线程(关于MRI Rails 2)的陈述是否正确?
  • 关于为何我无法使用Unicorn(或任何服务器)的任何想法?

有一个非常类似的问题,但是我无法解决这个问题并且它没有回答我关于使用MRI Ruby的并发请求的所有问题.

Github项目:https://github.com/afrankel/limitedBandwidth(注意:项目正在考虑的不仅仅是服务器上的多进程/线程问题)

Ely*_*Ely 24

我邀请你阅读Jesse Storimer的系列文章没有人理解GIL 这可能有助于你更好地理解一些MRI内部结构.

我也发现了Ruby的Pragmatic Concurrency,它看起来很有意思.它有一些同时测试的例子.

编辑: 此外我可以推荐文章删除config.threadsafe! 可能与Rails 4无关,但它解释了配置选项,其中一个可用于允许并发.


让我们讨论你的问题的答案.

即使使用Puma,您也可以使用多个线程(使用MRI).GIL确保一次只有一个线程是活动的,这是开发人员称为限制性的约束(因为没有真正的并行执行).请记住,GIL不保证线程安全.这并不意味着其他线程没有运行,他们正在等待轮到他们.它们可以交错(文章可以帮助更好地理解).

让我澄清一些术语:工人流程,线程.进程在单独的内存空间中运行,可以提供多个线程.同一进程的线程在共享内存空间中运行,这是其进程的空间.对于线程,我们指的是此上下文中的Ruby线程,而不是CPU线程.

关于你的问题的配置和你共享的GitHub repo,我认为一个合适的配置(我使用的是Puma)是设置4个worker和1到40个线程.这个想法是一个工人服务一个标签.每个标签最多可发送10个请求.

让我们开始吧:

我在虚拟机上使用Ubuntu.首先,我在我的虚拟机设置中启用了4个核心(以及其他一些我认为可能有用的设置).我可以在我的机器上验证这一点.所以我接受了.

Linux command --> lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                4
On-line CPU(s) list:   0-3
Thread(s) per core:    1
Core(s) per socket:    4
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 69
Stepping:              1
CPU MHz:               2306.141
BogoMIPS:              4612.28
L1d cache:             32K
L1d cache:             32K
L2d cache:             6144K
NUMA node0 CPU(s):     0-3
Run Code Online (Sandbox Code Playgroud)

我使用了你的共享GitHub项目并稍微修改了它.我创建了一个名为puma.rb(放在config目录中)的Puma配置文件,其中包含以下内容:

workers Integer(ENV['WEB_CONCURRENCY'] || 1)
threads_count = Integer(ENV['MAX_THREADS'] || 1)
threads 1, threads_count

preload_app!

rackup      DefaultRackup
port        ENV['PORT']     || 3000
environment ENV['RACK_ENV'] || 'development'

on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
  #ActiveRecord::Base.establish_connection
end
Run Code Online (Sandbox Code Playgroud)

默认情况下,Puma以1个worker和1个线程启动.您可以使用环境变量来修改这些参数.我这样做了:

export MAX_THREADS=40
export WEB_CONCURRENCY=4
Run Code Online (Sandbox Code Playgroud)

要使用此配置启动Puma,我输入了

bundle exec puma -C config/puma.rb
Run Code Online (Sandbox Code Playgroud)

在Rails应用程序目录中.

我用四个标签打开浏览器来调用应用程序的URL.

第一个请求是在15:45:05左右开始的,最后一个请求是在15:49:44左右.这是4分39秒的经过时间.您还可以在日志文件中以非排序顺序查看请求的ID.(见下文)

GitHub项目中的每个API调用都会休眠15秒.我们有4个4个选项卡,每个选项卡有10个API调用.这使得最大经过时间为600秒,即10分钟(在严格的串行模式下).

理论上的理想结果将是并行的,并且经过的时间不会超过15秒,但我根本没想到.我不确定结果究竟是什么,但我仍然感到非常惊讶(考虑到我在虚拟机上运行并且MRI受到GIL和其他一些因素的限制).该测试的经过时间小于最大经过时间的一半(在严格的串行模式下),我们将结果减少到不到一半.

编辑我进一步阅读了围绕每个请求包含互斥锁的Rack :: Lock(上面的第三篇文章).我找到了config.allow_concurrency = true节省时间的选择 .有一点需要注意的是增加连接池(尽管请求不进行查询,因此必须相应地设置数据库); 最大线程数是一个很好的默认值.在这种情况下为40.

我用jRuby测试了应用程序,实际经过的时间是2分钟,其中allow_concurrency = true.

我用MRI测试了应用程序,实际经过的时间是1分47秒,其中allow_concurrency = true.这对我来说是个大惊喜.这真让我感到惊讶,因为我预计MRI会慢于JRuby.它不是.这使我质疑关于MRI和JRuby之间速度差异的广泛讨论.

现在,观察不同选项卡上的响应"更随机".在选项卡1之前完成选项卡3或4,我首先请求.

我认为因为你没有竞争条件,所以测试似乎没问题.但是,如果在实际应用程序中设置config.allow_concurrency = true,我不确定应用程序的广泛后果.

请随时查看,并让我知道您的读者可能有的任何反馈.我的机器上还有克隆.如果您有兴趣,请告诉我.

按顺序回答您的问题:

  • 我认为你的例子是有效的结果.但是,对于并发性,最好使用共享资源进行测试(例如,在第二篇文章中).
  • 关于你的陈述,如本回答开头所述,MRI是多线程的,但一次只能由GIL限制为一个活动线程.这提出了一个问题:使用MRI不是更好的测试更多的进程和更少的线程?我真的不知道,第一个猜测是没有或没有多大区别.也许有人可以阐明这一点.
  • 我想你的例子很好.只是需要一些细微的修改.

附录

日志文件Rails应用程序:

**config.allow_concurrency = false (by default)**
-> Ideally 1 worker per core, each worker servers up to 10 threads.

[3045] Puma starting in cluster mode...
[3045] * Version 2.11.2 (ruby 2.1.5-p273), codename: Intrepid Squirrel
[3045] * Min threads: 1, max threads: 40
[3045] * Environment: development
[3045] * Process workers: 4
[3045] * Preloading application
[3045] * Listening on tcp://0.0.0.0:3000
[3045] Use Ctrl-C to stop
[3045] - Worker 0 (pid: 3075) booted, phase: 0
[3045] - Worker 1 (pid: 3080) booted, phase: 0
[3045] - Worker 2 (pid: 3087) booted, phase: 0
[3045] - Worker 3 (pid: 3098) booted, phase: 0
Started GET "/assets/angular-ui-router/release/angular-ui-router.js?body=1" for 127.0.0.1 at 2015-05-11 15:45:05 +0800
...
...
...
Processing by ApplicationController#api_call as JSON
  Parameters: {"t"=>"15?id=9"}
Completed 200 OK in 15002ms (Views: 0.2ms | ActiveRecord: 0.0ms)
[3075] 127.0.0.1 - - [11/May/2015:15:49:44 +0800] "GET /api_call.json?t=15?id=9 HTTP/1.1" 304 - 60.0230
Run Code Online (Sandbox Code Playgroud)
**config.allow_concurrency = true**
-> Ideally 1 worker per core, each worker servers up to 10 threads.

[22802] Puma starting in cluster mode...
[22802] * Version 2.11.2 (ruby 2.2.0-p0), codename: Intrepid Squirrel
[22802] * Min threads: 1, max threads: 40
[22802] * Environment: development
[22802] * Process workers: 4
[22802] * Preloading application
[22802] * Listening on tcp://0.0.0.0:3000
[22802] Use Ctrl-C to stop
[22802] - Worker 0 (pid: 22832) booted, phase: 0
[22802] - Worker 1 (pid: 22835) booted, phase: 0
[22802] - Worker 3 (pid: 22852) booted, phase: 0
[22802] - Worker 2 (pid: 22843) booted, phase: 0
Started GET "/" for 127.0.0.1 at 2015-05-13 17:58:20 +0800
Processing by ApplicationController#index as HTML
  Rendered application/index.html.erb within layouts/application (3.6ms)
Completed 200 OK in 216ms (Views: 200.0ms | ActiveRecord: 0.0ms)
[22832] 127.0.0.1 - - [13/May/2015:17:58:20 +0800] "GET / HTTP/1.1" 200 - 0.8190
...
...
...
Completed 200 OK in 15003ms (Views: 0.1ms | ActiveRecord: 0.0ms)
[22852] 127.0.0.1 - - [13/May/2015:18:00:07 +0800] "GET /api_call.json?t=15?id=10 HTTP/1.1" 304 - 15.0103
Run Code Online (Sandbox Code Playgroud)
**config.allow_concurrency = true (by default)**
-> Ideally each thread serves a request.

Puma starting in single mode...
* Version 2.11.2 (jruby 2.2.2), codename: Intrepid Squirrel
* Min threads: 1, max threads: 40
* Environment: development
NOTE: ActiveRecord 4.2 is not (yet) fully supported by AR-JDBC, please help us finish 4.2 support - check http://bit.ly/jruby-42 for starters
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
Started GET "/" for 127.0.0.1 at 2015-05-13 18:23:04 +0800
Processing by ApplicationController#index as HTML
  Rendered application/index.html.erb within layouts/application (35.0ms)
...
...
...
Completed 200 OK in 15020ms (Views: 0.7ms | ActiveRecord: 0.0ms)
127.0.0.1 - - [13/May/2015:18:25:19 +0800] "GET /api_call.json?t=15?id=9 HTTP/1.1" 304 - 15.0640
Run Code Online (Sandbox Code Playgroud)