Rack并发性 - rack.multithread,async.callback,还是两者都用?

36

我试图全面了解在Rack中处理并发请求的选项。我已经使用async_sinatra构建了一个长轮询应用程序,并且现在正在使用throw :async和/或Thin的--threaded标志来尝试裸机Rack。虽然我对这个问题很熟悉,但仍有一些事情我不能理解。(不,我没有将并发误认为是并行,而且我也理解GIL所施加的限制.)

问题1: 我的测试表明thin --threaded(即rack.multithread=true)会在单独的线程中并发运行请求(我假设使用EM),这意味着长时间运行的请求A不会阻塞请求B(IO除外)。这意味着我的应用程序不需要任何特殊编码(例如回调)来实现并发(再次忽略阻塞DB调用、IO等)。这就是我所观察到的,请问它是否正确?

问题2: 还有另一种更常被讨论的实现并发的方法,涉及EventMachine.deferthrow :async。严格来说,请求并不是使用线程处理的。它们是按顺序处理的,但是将它们的繁重工作和回调传递给EventMachine,后者使用async.callback在稍后的时间发送响应。当请求A将其工作离线到EM.defer后,请求B开始。这是正确的吗?

问题3: 假设以上两种方式大致正确,它们之间是否存在特定的优劣之处?显然,--threaded看起来像神奇的子弹。有没有缺点?如果没有,为什么会有人谈论async_sinatra/throw :async/async.callback?也许前者是“我想在重载下使我的Rails应用程序更快”,而后者更适合具有许多长时间运行请求的应用程序? 或者规模是一个因素?这只是猜测。

我正在使用MRI Ruby 1.9.2上的Thin 1.2.11。 (FYI,我必须使用--no-epoll标志,因为在Ruby 1.9.2中,EventMachine对epoll的使用存在一个长期存在、据称已解决但实际上并未解决的问题。这不是重点,但任何见解都受欢迎。


epoll问题应该已经在那个工单中得到解决,这是他们所指的提交记录 - Bitterzoet
如果我移除 --no-epoll 标志,我的线程请求从毫秒变成了分钟。EM 0.12.10,Ruby 1.9.2-p180。我想我可以尝试编译 p290... - bioneuralnet
好问题。我在这里问了一个非常类似的问题:https://dev59.com/EV3Ua4cB1Zd3GeqP_Egj,并在这里进行了一些实验:https://github.com/jjb/threaded-rails-example(请注意,虽然线程化的thin成功地实现了异步,但基准测试速度较慢)。 - John Bachir
1个回答

25

注意:我使用Thin作为实现异步Rack扩展的所有Web服务器的同义词(即Rainbows!,Ebb,未来版本的Puma等)

Q1. 正确。它将响应生成(又称call)包装在EventMachine.defer { ... }中,这将导致EventMachine将其推送到其内置线程池中。

Q2.EM.deferasync.callback一起使用实际上没有太多意义,因为它基本上也会使用线程池,最终得到类似于Q1中描述的结构。只有在仅使用eventmachine库进行IO时,使用async.callback才有意义。当env['async.callback']被调用并传递一个普通的Rack响应参数时,Thin将向客户端发送响应。

如果body是EM :: Deferrable,则Thin将不会关闭连接,直到该deferrable成功。一个比较好的保密:如果你想要更多的长轮询(即在发送部分响应后保持连接打开),你还可以直接返回一个 EM :: Deferrable 作为body对象,而无需使用throw:async或状态码 -1

Q3. 你猜对了。线程服务可能改善未改变的Rack应用程序的负载。我在我的Ruby 1.9.3机器上看到简单的Sinatra应用程序有20%的改进,甚至在Rubinius或JRuby上运行时可以利用所有核心。第二种方法对于以事件方式编写应用程序的人很有用。

你可以在Rack的基础上添加许多魔法和黑客手段,使不使用事件的应用程序利用这些机制(请参阅em-synchrony或sinatra-synchrony),但这将使你陷入调试和依赖关系地狱中。

异步编程适用于需要采用事件驱动方法解决问题的应用程序,例如网络聊天室。但是,我不建议使用线程方法来实现长轮询,因为每个轮询连接都会阻塞一个线程。这将使您面临大量线程或无法处理的连接。 EM 的线程池默认有 20 个线程的大小,限制每个进程的等待连接数为 20 个。
您可以使用服务器为每个传入的连接创建一个新线程,但是创建线程是昂贵的(除了 MacRuby 外,但我不会在任何生产应用程序中使用 MacRuby)。示例包括servnet-http-server。理想情况下,您希望的是请求和线程之间的 n:m 映射。但是目前没有任何服务器提供这种功能。
如果您想了解更多内容:我在 Rocky Mountain Ruby (以及许多其他会议)上做了一次关于此主题的演讲。视频记录可在confreaks上找到

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