9

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

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

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

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

4

2 回答 2

13

塞尔吉奥是正确的。在这一点上,您的应用程序可能比传统的 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 的 jetty、netty 等),那么永不阻塞规则可以稍微放松,你可以用 C/C++ 编写你自己的)。当一个线程在某事上被阻塞时,其他线程仍然可以执行事件循环。但是在足够重的负载下,即使这些也无法执行。所以永远不要在事件服务器中阻塞。

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

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

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

于 2012-05-09T02:28:55.707 回答
3

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

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

于 2012-05-09T01:09:47.297 回答