我整理了一个简单的示例,尝试使用一个基本示例来证明Rails中的并发请求。请注意,我正在使用MRI Ruby2和Rails 4.2。

  def api_call
    sleep(10)
    render :json => "done"
  end

然后,我在Mac(I7/4核)的Chrome上转到4个不同的选项卡,查看它们是串行还是并行运行(真正的并发运行是很接近的,但不是同一件事)。即http://localhost:3000/api_call

我无法使用Puma,Thin或Unicorn来工作。每个请求都是按顺序提出的。 10秒后显示第一个选项卡,20秒后显示第二个选项卡(因为它必须等待第一个选项卡完成),此后再显示第三个选项卡...。

根据我的阅读,我相信以下内容是正确的(请纠正我),并且是我的结果:
  • Unicorn是多进程的,我的示例应该已经工作了(在unicorn.rb配置文件中定义了工作程序的数量之后),但没有成功。我可以看到有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
    
  • Thin和Puma是多线程的(尽管Puma至少具有'clustered'模式,您可以在其中使用-w参数启动worker),并且无论如何都不能与MRI Ruby2.0一起工作(在多线程模式下),因为“存在全局解释器确保一次只能运行一个线程的锁(GIL)”。

  • 所以,
  • 我是否有一个有效的示例(或者使用sleep只是错误的)?
  • 我上面关于多进程和多线程(相对于MRI Rails 2)的陈述正确吗?
  • 关于为什么我无法使其与Unicorn(或与此相关的任何服务器)一起使用的任何想法?

  • 有一个非常大的similar question to mine,但是我无法让它按已回答的方式工作,它无法回答我有关使用MRI Ruby的并发请求的所有问题。

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

    最佳答案

    我邀请您阅读Jesse Storimer的Nobody understands the GIL系列
    它可以帮助您更好地了解MRI的内部构造。

    我还发现了Pragmatic Concurrency with Ruby,它看起来很有趣。它具有并发测试的一些示例。

    编辑:
    另外我可以推荐文章Removing config.threadsafe!
    可能与Rails 4不相关,但是它解释了配置选项,您可以使用其中之一来允许并发。

    让我们讨论您问题的答案。

    您甚至可以使用Puma拥有多个线程(使用MRI)。 GIL确保一次仅一个线程处于 Activity 状态,这就是开发人员将其称为限制性的约束(因为没有真正的并行执行)。请记住,GIL不保证线程安全。
    这并不意味着其他线程没有在运行,而是在等待轮到他们。它们可以交错(文章可以帮助更好地理解)。

    让我澄清一些术语:工作进程,线程。
    进程在单独的内存空间中运行,可以服务多个线程。
    同一进程的线程在共享内存空间中运行,这就是它们的进程。对于线程,在本文中我们指的是Ruby线程,而不是CPU线程。

    关于您问题的配置和您共享的GitHub存储库,我认为一个适当的配置(我使用Puma)是设置4个工作程序和1至40个线程。这个想法是由一个 worker 提供一个选项卡。每个标签最多可发送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
    

    我使用了您共享的GitHub项目,并对其进行了一些修改。我创建了一个名为puma.rb的Puma配置文件(将其放在config目录中),其内容如下:
    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
    

    默认情况下,Puma以1个工作人员和1个线程启动。您可以使用环境变量来修改那些参数。我是这样做的:
    export MAX_THREADS=40
    export WEB_CONCURRENCY=4
    

    要使用此配置启动Puma,请输入
    bundle exec puma -C config/puma.rb
    

    在Rails应用目录中。

    我用四个选项卡打开了浏览器,以调用应用程序的URL。

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

    GitHub项目中的每个API调用都会休眠15秒。我们有四个4个标签,每个标签有10个API调用。最长经过时间为600秒,即10分钟(在严格的串行模式下)。

    从理论上讲,理想的结果应该是并行的,并且耗时不远于15秒,但是我完全没有想到。
    我不确定确切的结果是什么,但是我仍然感到非常惊讶(考虑到我在虚拟机上运行并且MRI受GIL和其他一些因素的限制)。该测试的经过时间少于最大经过时间的一半(在严格的串行模式下),我们将结果切成少于一半。



    随时检查一下,并让我知道您的读者可能有任何反馈。
    我的机器上仍然有克隆。如果您有兴趣,请告诉我。

    要按顺序回答您的问题:
  • 我认为您的示例在结果上是有效的。但是,对于并发而言,最好使用共享资源进行测试(例如,在第二篇文章中)。
  • 关于您的陈述,如本文开头所述
    答案是:MRI是多线程的,但受GIL限制为一个 Activity
    一次线程。这就提出了一个问题:核磁共振成像不是更好吗
    用更多的进程和更少的线程进行测试?我真的不知道
    最初的猜测不会有很大的不同,也不会有很大的不同。也许有人可以阐明这一点。
  • 我认为您的示例很好。只是需要一些轻微
    修改。

  • 附录

    日志文件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
    
    **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
    
    **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
    

    关于ruby-on-rails - MRI Ruby的并发请求,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/29955290/

    10-13 06:42
    查看更多