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中这样做是否容易或困难。