【问题标题】:Thin server underperforming / How do evented web servers work?瘦服务器性能不佳/事件 Web 服务器如何工作?
【发布时间】:2012-05-09 01:02:29
【问题描述】:

我在 Nginx/Passenger 上有一个 rails 3 应用程序,我刚刚移至 Nginx/Thin (1.3.1)。但是,我的应用现在明显比在Passenger 上要慢。很多请求也会超时。

Thin 是一个事件网络服务器。从我读到的关于事件网络服务器的内容来看,他们没有工人的概念。一个“工人”处理一切。因此,如果一个请求在 IO 上等待,thin 将继续处理下一个请求,依此类推。我读到的关于事件服务器的一种解释说,事件服务器的性能应该与基于工作者的服务器一样好或更好,因为它们只受系统资源的约束。

但是,我的 CPU 使用率很少。我的内存使用量也很少,也没有太多的 IO 发生。我的应用只进行了一些 MySQL 查询。

这里的瓶颈是什么?我的瘦服务器不应该在 CPU 达到 100% 之前处理请求吗?我是否必须在我的应用中做任何不同的事情才能让它在事件服务器上表现得更好?

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 webserver thin evented-io


    【解决方案1】:

    塞尔吉奥是正确的。在这一点上,您的应用程序可能比传统的 Apache/Passenger 模型更好。如果您采用事件路由,尤其是在 Ruby 等单线程平台上,您永远不能阻塞任何东西,无论是数据库、缓存服务器,还是您可能发出的其他 HTTP 请求 - 什么都没有。

    这就是使异步(事件)编程更难的原因 - 很容易阻塞东西,通常以同步磁盘 I/O 或 DNS 解析的形式。诸如 nodejs 之类的非阻塞(事件)框架非常小心,因为它们(几乎)从不向您提供阻塞的框架函数调用,而是使用回调(包括 DB 查询)处理所有内容。

    如果您查看单线程非阻塞服务器的核心,这可能更容易可视化:

    while( wait_on_sockets( /* list<socket> */ &$sockets, /* event */ &$what, $timeout ) ) {
        foreach( $socketsThatHaveActivity as $fd in $sockets ) {
            if( $what == READ ) {   // There is data availabe to read from this socket
                $data = readFromSocket($fd);
                processDataQuicklyWithoutBlocking( $data );
            }
            elseif ($what == WRITE && $data = dataToWrite($fd)) { // This socket is ready to be written to (if we have any data)
                writeToSocket( $fd, $data );    
            }
        }
    }
    

    您在上面看到的称为事件循环。 wait_on_sockets 通常由 OS 以系统调用的形式提供,例如 select、poll、epoll 或 kqueue。如果 processDataQuicklyWithoutBlocking 花费的时间太长,操作系统维护的应用程序的网络缓冲区(新请求、传入数据等)最终会填满,并导致它拒绝新连接并使现有连接超时,因为 $socketsThatHaveActivity 处理速度不够快.这与线程服务器(例如典型的 Apache 安装)不同,因为每个连接都使用单独的线程/进程提供服务,因此传入数据将在到达后立即读入应用程序,而传出数据将立即发送.

    当您进行(例如)数据库查询时,像 nodejs 这样的非阻塞框架所做的是将数据库服务器的套接字连接添加到被监视的套接字列表($sockets)中,所以即使您的查询需要同时,您的(唯一)线程不会在那个套接字上被阻塞。相反,它们提供了一个回调:

    $db.query( "...sql...", function( $result ) { ..handle result ..} );
    

    正如您在上面看到的,db.query 立即返回,并且在 db 服务器上绝对没有任何阻塞。这也意味着您经常必须编写这样的代码,除非编程语言本身支持异步函数(如新的 C#):

    $db.query( "...sql...", function( $result ) { $httpResponse.write( $result ); $connection.close(); } );
    

    如果您有许多进程都在运行一个事件循环(通常是运行节点集群的方式),或者使用线程池来维护事件循环(java 的码头, netty 等,你可以用 C/C++ 编写你自己的)。当一个线程在某事上被阻塞时,其他线程仍然可以执行事件循环。但是在足够重的负载下,即使这些也无法执行。所以永远不要在事件服务器中阻塞。

    如您所见,事件服务器通常会尝试解决不同的问题——它们可以有大量打开的连接。他们擅长的地方只是通过轻计算(例如彗星服务器、像 memcached、varnish 这样的缓存、像 nginx、squid 这样的代理等)来推送字节。毫无价值的是,即使它们可以更好地扩展,响应时间通常也会增加(没有什么比为连接保留整个线程更好的了)。当然,运行与并发连接数相同的线程数可能在经济/计算上不可行。

    现在回到您的问题 - 我建议您仍然保留 Nginx,因为它在连接管理(基于事件)方面非常出色 - 通常意味着处理 HTTP 保持活动、SSL 等。然后您应该将其连接到您的 Rails 应用程序使用 FastCGI,您仍然需要在其中运行工作程序,但不必重写您的应用程序以完全事件。您还应该让 Nginx 提供静态内容 - 让您的 Rails 工作人员与 Nginx 通常可以做得更好的东西捆绑在一起是没有意义的。这种方法的扩展性通常比 Apache/Passenger 好得多,尤其是在您运行高流量网站的情况下。

    如果您可以编写整个应用程序以进行事件处理,那就太好了,但我不知道在 Ruby 中这有多容易或多难。

    【讨论】:

    • 哇 .. 感谢 Tejas 的详细回复。所以我从网上读到的基准..它们是针对完全不同的应用程序类型的吗? Thin 自己的网站提供了一个 rails 应用程序作为 Thin 的示例应用程序。 code.macournoyer.com/thin。我的印象是我可以用瘦来代替乘客,一切都会变得很糟糕。
    • 只要您不阻止任何地方,您应该能够重新创建这些基准。
    【解决方案2】:

    是的,Thin 确实有事件 I/O,但仅适用于 HTTP 部分。这意味着它可以在处理请求时接收传入的 HTTP 数据。但是,您在处理期间执行的所有阻塞 I/O 仍然是阻塞的。如果您的 MySQL 响应缓慢,那么 Thin request queue 将被填满。

    对于“更多”事件网络服务器,您应该查看Rainbows

    【讨论】:

    • 嗨,塞尔吉奥。请原谅我对这些概念的理解。我读到 Rails 的 mysql2 gem 也确实触发了 IO。另外,如果是这样的话,基于工人的服务器不是总是更好吗?我只运行了 2 个瘦实例,而不是我之前运行的 25 个乘客实例。
    • @CoffeeBite:它可以进行异步调用,是的,但这不是自动的,您必须编写代码才能实现。默认情况下,它是同步的。
    • @CoffeeBite:“工人不会总是更好” - 不确定。我自己在 nginx 后面使用Unicorn。 Nginx 处理 HTTP I/O,独角兽快速处理请求。
    • 这是否意味着我需要 25 个精简运行实例才能获得相当的性能?事实是我在选择瘦之前做了一些研究(对于它的价值)。我看到的大多数基准测试都将独角兽和瘦并驾齐驱(通常只有一个瘦实例与一群独角兽工人相比)。以这个为例..blog.crowdint.com/2010/08/26/thin-vs-unicorn.html我的应用程序没什么特别的..我想它是rails应用程序所能达到的标准。所以我想知道为什么瘦没有表现。
    • 几乎所有的瘦文章都说每个 cpu 需要一个瘦。就是这样。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-02
    相关资源
    最近更新 更多