我试图完全理解Rack中并发请求处理的选项.我已经使用async_sinatra来构建一个长轮询应用程序,现在正在尝试使用裸机Rack throw :async
和/或Thin的--threaded标志.我对这个问题很满意,但有一些我无法理解的事情.(不,我并不误解并发性的并发性,是的,我确实理解GIL施加的限制).
Q1.我的测试表明thin --threaded
(即rack.multithread=true
)在不同的线程中同时运行请求(我假设使用EM),这意味着长时间运行的请求A不会阻止请求B(IO旁边).这意味着我的应用程序不需要任何特殊编码(例如回调)来实现并发(同样,忽略阻塞DB调用,IO等).这是我相信我观察到的 - 这是正确的吗?
Q2.还有另一种更常见的实现并发的方法,涉及EventMachine.defer
和throw :async
.严格来说,不使用线程处理请求.它们是按顺序处理的,但是将它们的繁重工作和回调传递给EventMachine,后者使用async.callback在稍后发送响应.请求A已将其工作卸载到EM.defer后,请求B开始.它是否正确?
Q3.假设上述方法或多或少是正确的,那么一种方法对另一种方法有什么特别的优势吗?显然--threaded
看起来像一个魔术子弹.有什么缺点吗?如果没有,为什么每个人都在谈论async_sinatra
/ throw :async
/ async.callback
?也许前者是"我想让我的Rails应用程序在繁重的负载下变得更加快速",而后者更适合具有许多长时间运行请求的应用程序?或者规模是一个因素?只是在这里猜测.
我在MRI Ruby 1.9.2上运行Thin 1.2.11.(仅供参考,我必须使用该--no-epoll
标志,因为EventMachine使用epoll和Ruby 1.9.2 存在一个长期的,据称解决但不是真正的问题.这不是重点,但任何见解都是受欢迎的.)
Kon*_*ase 24
注意:我使用Thin作为实现异步Rack扩展的所有Web服务器的同义词(即Rainbows!,Ebb,未来版本的Puma,...)
Q1.正确.它将包装响应生成(aka call
)EventMachine.defer { ... }
,这将导致EventMachine将其推送到其内置线程池.
Q2.使用async.callback
结合EM.defer
实际上使得没有太大的意义,因为这将基本上使用线程池,也因为在Q1中描述了类似的构造结束了.async.callback
当仅使用IO的eventmachine库时,使用是有意义的.一旦env['async.callback']
调用普通的Rack响应作为参数,Thin将向客户端发送响应.
如果正文是一个EM::Deferrable
,则Thin将不会关闭连接,直到该延迟成功为止.一个相当保守的秘密:如果你想要的不只是长轮询(即在发送部分响应后保持连接打开),你也可以EM::Deferrable
直接返回一个body对象而不必使用throw :async
或者状态代码为-1
.
Q3.你猜对了.线程服务可能会改善未更改的Rack应用程序的负载.对于使用Ruby 1.9.3的机器上的简单Sinatra应用程序,我看到了20%的改进,甚至更多在Rubinius或JRuby上运行时,可以使用所有核心.如果以一种正确的方式编写应用程序,第二种方法很有用.
你可以在Rack上抛出大量的魔法和黑客,让一个非公平的应用程序利用这些机制(参见em-synchrony或sinatra-synchrony),但这会让你处于调试和依赖地狱.
异步方法对于应用程序而言非常有意义,这些应用程序通常采用公平方法(如Web聊天)来解决.但是,我不建议使用线程方法来实现长轮询,因为每个轮询连接都会阻塞一个线程.这将使您无法处理大量的线程或连接.EM的线程池默认大小为20个线程,每个进程限制为20个等待连接.
您可以使用为每个传入连接创建新线程的服务器,但创建线程很昂贵(MacRuby除外,但我不会在任何生产应用程序中使用MacRuby).示例是serv和net-http-server.理想情况下,您想要的是请求和线程的n:m映射.但那里没有提供服务器的服务器.
如果你想了解更多关于这个主题的信息:我在Rocky Mountain Ruby(和其他很多会议)上做了一个关于这个的演讲.可以在confreaks上找到录像.