薄服务器表现不佳 / 事件驱动的Web服务器是如何工作的?

9
我有一个运行在Nginx/Passenger上的Rails 3应用程序,我刚刚将它迁移到了Nginx/Thin(1.3.1)。然而,我的应用现在明显比在Passenger上慢得多,很多请求也会超时。
Thin是一个事件驱动的Web服务器。根据我所读到的关于事件驱动Web服务器的信息,它们没有工作进程的概念,一个“worker”处理所有的事情。因此,如果一个请求正在等待IO操作,thin就继续处理下一个请求,以此类推。我读到的一种解释是,事件驱动服务器应该表现得和基于工作进程的服务器一样好甚至更好,因为它们只受系统资源的限制。
但是,我的CPU使用率非常低,我的内存使用率也非常低,也没有太多的IO操作。我的应用只做了一些MySQL查询。
这里的瓶颈是什么?难道我的Thin服务器不应该在CPU达到100%之前处理请求吗?我需要在我的应用中做一些不同的事情才能使其在事件驱动服务器上表现得更好吗?
2个回答

13

Sergio说得没错。在这个阶段,你的应用程序可能更适合使用传统的Apache/Passenger模型。如果你采取事件驱动的路线,特别是在像Ruby这样的单线程平台上,你就不能对任何事情进行阻塞,无论是数据库、缓存服务器还是其他可能发起的HTTP请求。

这就是使异步(事件驱动)编程变得更加困难的原因 - 容易发生阻塞,通常以同步磁盘I/O或DNS解析的形式出现。像nodejs这样的非阻塞(事件驱动)框架很谨慎,它们(几乎)从不提供阻塞的框架函数调用,而是使用回调来处理所有事情(包括数据库查询)。

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

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通常由操作系统提供,以系统调用的形式出现,例如select、poll、epoll或kqueue。如果processDataQuicklyWithoutBlocking花费的时间太长,你的应用程序网络缓冲区(新请求、传入数据等)最终将填满,并导致拒绝新连接并超时现有连接,因为$socketsThatHaveActivity没有被快速处理。这与使用单独线程/进程为每个连接服务的线程化服务器(例如典型的Apache安装)不同,因此传入数据将立即读入应用程序中,而传出数据将无延迟发送。

像Node.js这样的非阻塞框架在执行(例如)数据库查询时的做法是将DB服务器的套接字连接添加到正在监视的套接字列表($sockets)中,因此即使查询需要一段时间,你的(唯一)线程也不会在该套接字上被阻塞。相反,它们会提供回调函数:

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

如上所述,db.query 立即返回,没有任何阻塞在数据库服务器上。这也意味着你经常需要编写像下面这样的代码,除非编程语言自身支持异步函数(如新的 C#):

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

如果您有许多进程每个都运行事件循环(这通常是运行节点群集的方式),或者使用线程池来维护事件循环(例如Java的Jetty、Netty等,您可以在C/C++中编写自己的线程池),那么可以在某种程度上放松永不阻塞的规则。当一个线程在某些事情上被阻塞时,其他线程仍然可以执行事件循环。但在足够重的负载下,即使这些也会无法执行。因此,在事件驱动服务器中,永远不要阻塞。

因此,可以看到,事件驱动服务器通常试图解决不同的问题-它们可以拥有大量打开的连接。它们在轻量级计算(例如彗星服务器、缓存(如Memcached、Varnish)、代理(如Nginx、Squid))中擅长于推送字节。值得注意的是,尽管它们具有更好的可扩展性,但响应时间通常倾向于增加(没有什么比为连接保留整个线程更好的了)。当然,可能无法经济/计算上可行地运行与并发连接数相同数量的线程。

现在回到您的问题-我建议您仍然保留Nginx,因为它在连接管理方面非常出色(这是基于事件的)-通常意味着处理HTTP保持活动、SSL等。然后,您应该使用FastCGI将其连接到您的Rails应用程序中,在那里您仍需要运行工作线程,但不必重写您的应用程序以完全支持事件驱动。您还应该让Nginx提供静态内容-没有理由让您的Rails工作者与Nginx可以更好地完成的某些事情绑定。此方法通常比Apache/Passenger扩展性更好,特别是如果您运行高流量的网站。

如果您可以编写整个应用程序以支持事件驱动,则很好,但我不知道Ruby中这样做是否容易或困难。


哇..感谢你详细的回答,Tejas。那么我从网上读到的基准测试,它们是针对完全不同类型的应用程序吗?Thin 的官方网站给出了一个 Rails 应用程序作为 Thin 的示例应用程序。http://code.macournoyer.com/thin/. 我原本以为我可以只用 thin 替换 passenger,一切都会很顺利。 - Coffee Bite
只要你不在任何地方阻塞,就应该能够重新创建那些基准测试。 - tejas

3

是的,Thin确实支持事件驱动I/O,但只针对HTTP部分。这意味着它可以在处理请求时接收传入的HTTP数据。但是,在处理过程中进行的所有阻塞I/O仍然是阻塞的。如果你的MySQL响应速度慢,那么Thin请求队列将会填满。

如果您需要更多事件驱动的Web服务器,建议查看Rainbows


嗨Sergio。请原谅我对这些概念的浅薄理解。我读到了关于Rails的mysql2 gem也支持事件IO的内容。 此外,如果是这样的话,工作进程服务器不总是更好吗?我只运行了2个thin实例,而之前我运行了25个passenger实例。 - Coffee Bite
@CoffeeBite:它能够进行异步调用,但这不是自动的,您需要编写代码才能实现。默认情况下,它是同步的。 - Sergio Tulentsev
@CoffeeBite:“工作进程不总是更好” - 不确定。我自己使用 Unicorn 在 nginx 后面。Nginx 处理 HTTP I/O,而 Unicorn 快速处理请求。 - Sergio Tulentsev
这是否意味着我需要运行25个thin实例才能获得可比较的性能?事实是,在选择thin之前,我做了一些研究(不管值不值得)。我看过的大多数基准测试将unicorn和thin放在同一个水平线上(通常只有一个thin实例与一组unicorn worker进行对比)。以这个为例... http://blog.crowdint.com/2010/08/26/thin-vs-unicorn.html我的应用程序并没有什么特别之处...我想它尽可能符合rails应用程序的标准。所以我想知道为什么thin的性能不好。 - Coffee Bite
我猜,它表现不佳的原因是您现在只有2个thin,而以前有25个乘客。Nginx负责缓冲网络I/O,因此thin的真正能力未被利用。要充分发挥其威力,您需要使用异步框架。 - Sergio Tulentsev
1
几乎所有的文章都说你需要每个 CPU 一个薄客户端。就这样。 - Coffee Bite

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