机架并发 -rack.multithread、async.callback 或两者兼而有之?

发布于 2024-11-29 13:40:35 字数 1237 浏览 4 评论 0原文

我试图完全理解 Rack 中并发请求处理的选项。我已经使用 async_sinatra 构建了一个长轮询应用程序,现在正在使用 throw :async 和/或 Thin 的 --threaded 标志尝试裸机 Rack。我对这个主题很满意,但有些事情我就是无法理解。 (不,我没有将并发误认为并行性,是的,我确实理解 GIL 施加的限制)。

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

Q2。还有另一种更常讨论的实现并发的方法,涉及 EventMachine.defer 和 throw :async。严格来说,请求是使用线程处理的。它们按顺序处理,但将繁重的工作和回调传递给 EventMachine,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 这不是重点,但欢迎任何见解。)

I'm attempting to fully understand the options for concurrent request handling in Rack. I've used async_sinatra to build a long-polling app, and am now experimenting with bare-metal Rack using throw :async and/or Thin's --threaded flag. I am comfortable with the subject, but there are some things I just can't make sense of. (No, I am not mistaking concurrency for parallelism, and yes, I do understand the limitations imposed by the GIL).

Q1. My tests indicate that thin --threaded (i.e. rack.multithread=true) runs requests concurrently in separate threads (I assume using EM), meaning long-running request A will not block request B (IO aside). This means my application does not require any special coding (e.g. callbacks) to achieve concurrency (again, ignoring blocking DB calls, IO, etc.). This is what I believe I have observed - is it correct?

Q2. There is another, more oft discussed means of achieving concurrency, involving EventMachine.defer and throw :async. Strictly speaking, requests are not handled using threads. They are dealt with serially, but pass their heavy lifting and a callback off to EventMachine, which uses async.callback to send a response at a later time. After request A has offloaded its work to EM.defer, request B is begun. Is this correct?

Q3. Assuming the above are more-or-less correct, is there any particular advantage to one method over the other? Obviously --threaded looks like a magic bullet. Are there any downsides? If not, why is everyone talking about async_sinatra / throw :async / async.callback ? Perhaps the former is "I want to make my Rails app a little snappier under heavy load" and the latter is better-suited for apps with many long-running requests? Or perhaps scale is a factor? Just guessing here.

I'm running Thin 1.2.11 on MRI Ruby 1.9.2. (FYI, I have to use the --no-epoll flag, as there's a long-standing, supposedly-resolved-but-not-really problem with EventMachine's use of epoll and Ruby 1.9.2. That's beside the point, but any insight is welcome.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

你穿错了嫁妆 2024-12-06 13:40:35

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

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

Q2.async.callbackEM.defer 结合使用实际上没有多大意义,因为它基本上会使用线程池,同样,最终得到了与 Q1 中描述的类似的结构。仅使用 eventmachine 库进行 IO 时,使用 async.callback 才有意义。一旦使用正常的 Rack 响应作为参数调用 env['async.callback'],Thin 就会将响应发送到客户端。

如果主体是 EM::Deferrable,Thin 将不会关闭连接,直到该可延迟成功为止。一个相当保密的秘密:如果您想要的不仅仅是长轮询(即在发送部分响应后保持连接打开),您还可以直接返回 EM::Deferrable 作为主体对象,而无需使用 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-服务器。理想情况下,您想要的是请求和线程的 n:m 映射。但没有服务器提供这种服务。

如果您想了解有关该主题的更多信息:我在 Rocky Mountain Ruby(以及大量其他会议)上对此进行了演示。可以在 confreaks 上找到视频记录。

Note: I use Thin as synonym for all web servers implementing the async Rack extension (i.e. Rainbows!, Ebb, future versions of Puma, ...)

Q1. Correct. It will wrap the response generation (aka call) in EventMachine.defer { ... }, which will cause EventMachine to push it onto its built-in thread pool.

Q2. Using async.callback in conjunction with EM.defer actually makes not too much sense, as it would basically use the thread-pool, too, ending up with a similar construct as described in Q1. Using async.callback makes sense when only using eventmachine libraries for IO. Thin will send the response to the client once env['async.callback'] is called with a normal Rack response as argument.

If the body is an EM::Deferrable, Thin will not close the connection until that deferrable succeeds. A rather well kept secret: If you want more than just long polling (i.e. keep the connection open after sending a partial response), you can also return an EM::Deferrable as body object directly without having to use throw :async or a status code of -1.

Q3. You're guessing correct. Threaded serving might improve the load on an otherwise unchanged Rack application. I see a 20% improve for simple Sinatra applications on my machine with Ruby 1.9.3, even more when running on Rubinius or JRuby, where all cores can be utilized. The second approach is useful if you write your application in an evented manner.

You can throw a lot of magic and hacks on top of Rack to have a non-evented application make use of those mechanisms (see em-synchrony or sinatra-synchrony), but that will leave you in debugging and dependency hell.

The async approach makes real sense with applications that tend to be best solved with an evented approach, like a web chat. However, I would not recommend using the threaded approach for implementing long-polling, because every polling connection will block a thread. This will leave you with either a ton of threads or connections you can't deal with. EM's thread pool has a size of 20 threads by default, limiting you to 20 waiting connections per process.

You could use a server that creates a new thread for every incoming connection, but creating threads is expensive (except on MacRuby, but I would not use MacRuby in any production app). Examples are serv and net-http-server. Ideally, what you want is an n:m mapping of requests and threads. But there's no server out there offering that.

If you want to learn more on the topic: I gave a presentation about this at Rocky Mountain Ruby (and a ton of other conferences). A video recording can be found on confreaks.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文