欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

RoR部署方案深度剖析

程序员文章站 2022-03-08 20:16:40
...
RoR的部署方案可谓五花八门,有Apache/Fastcgi方式的,有Nginx/Mongrel方式的,还有lighttpd/Fastcgi方式,也有人使用HAProxy/Mongrel,各种部署方式都是众说纷纭,让人搞不清楚哪种方式更好一些。我的这篇文章就是希望结合我们运营JavaEye网站一年多以来的经验(通过统计Rails的production.log,JavaEye网站目前每天处理超过70万200 OK状态的Ruby动态请求,应该是国内目前负载量最大的RoR应用了),为大家剖析RoR部署方案的优劣,帮助大家选择适合自己生产环境的RoR部署方式。

在讨论部署方案之前,先让我们看一下RoR网站部署的简单架构:

RoR部署方案深度剖析
            
    
    博客分类: Ruby lighttpdRailsnginx应用服务器Ruby 

浏览器的HTTP访问请求首先达到Web服务器,充当Web服务器的一般是Lighttpd/Apache/Nginx,如果访问请求包含静态资源,那么Web服务器就会直接从本地硬盘读取静态资源文件,例如图片,JavaScript,CSS等等,返回给客户端浏览器;如果访问请求是动态请求,那么Web服务器把URL请求转发到后端的FastCGI/Mongrel来处理,等到FastCGI/Mongrel处理完请求,将生成的页面数据返回给Web服务器,最后Web服务器把页面数据发送到客户端的浏览器。

从RoR的部署方式来看,主要由前端的Web服务器和后端的应用服务器构成:前端的Web服务器可以使用Apache,Lighttpd,Nginx和Litespeed,后端的应用服务器可以使用FastCGI和Mongrel,下面我们分门别类的介绍和剖析:

一、介绍Web服务器
Web服务器的主要作用有两点:一是处理静态资源,二是将动态请求分发到后端应用服务器,然后接收后端应用服务器生成的页面数据,将其返回浏览器,充当了一个信息沟通的桥梁作用,在本文当中我们重点分析后者的作用。

1、Apache 2.2

Apache是全球互联网使用最广泛的Web服务器,但在处理静态资源文件上却不是性能最优秀的Web服务器,不过一般情况下,静态资源的访问并不是RoR网站的瓶颈,因此也不必过于在意这一点。

Apache 2.2既支持HTTP Proxy方式连接后端的Mongrel应用服务器,也可以通过mod_fastcgi/mod_fcgid来连接FastCGI应用服务器:当以HTTP Proxy方式连接Mongrel的时候,Apache接收Mongrel返回的页面数据的buffer size最大只能开到8KB(默认是4KB或者8KB),因此当页面数据超过8KB的时候,可能需要Apache和Mongrel之间发生多次交互;当以mod_fastcgi方式连接FastCGI应用服务器的时候,接收返回数据的Buffer size仍然只有8KB而已,如果使用mod_fcgid,那么buffer size为64KB,有了很大的改善。

2、Nginx

Nginx是俄国人出品的轻量级Web服务器,在处理静态资源方面,据说性能还略微超过Lighttpd的10%,而且资源消耗更小。

Nginx内置了良好的HTTP Proxy和FastCGI支持,因此即可以连接Mongrel,也可以连接FastCGI服务器,在这两种情况下,Nginx默认的接收应用服务器返回数据的Buffer Size也只有区区的8KB,但是你可以自行设置更大Buffer Size。

3、Lighttpd

Lighttpd是全球互联网排名第五的Web服务器,也是近两年来上升最快的Web服务器,特别是很受一些著名Web 2.0大网站的欢迎,例如wikipedia的某些服务器,youtube的视频服务器,在国内,豆瓣网站和JavaEye网站都是Lighttpd的绝对拥护者。在处理静态资源方面,Lighttpd性能远远超过Apache。

Lighttpd既支持HTTP Proxy连接Mongrel,也支持FastCGI方式,但是Lighttpd的FastCGI支持在所有流行的Web服务器当中可能是最优秀的,所以用FastCGI的网站都很喜欢Lighttpd。Lighttpd在接收后端应用服务器返回数据的方式上和Apache/Nginx有非常大的区别:

Apache/Nginx是针对每个应用服务器连接分配固定Size的Buffer,而且默认只开8KB,这个Size对于现在网页动辄50-100KB的情况来说,显得过于保守,如果应用服务器的返回数据无法一次填满Web服务器的Buffer,那么就会导致应用服务器和Web服务器之间多次数据传输,这对于RoR网站的性能会造成一些相关的影响,我们在后面会详细的分析。

Lighttpd并不针对应用服务器的每个连接分配固定的Buffer,而是尽可能的把应用服务器返回的数据一次性接收下来,因此无论应用服务器返回多大的数据量,Lighttpd都是照单全收,胃口非常惊人。

4、Litespeed

Litespeed是一个商业收费的Web服务器,静态资源处理能力据它自己的评测数据比Lighttpd略高。Litespeed也同时支持HTTP Proxy连接Mongrel和FastCGI连接应用服务器。此外Litespeed专门为单机运行的RoR开发了一个lsapi协议,号称性能最好的RoR通讯协议,比HTTP Proxy和FastCGI都要好。但是lsapi的运行方式有很大缺陷:因为lsapi不是web server启动的时候启动固定数目的ruby进程,而是根据请求繁忙程度,动态创建和销毁ruby进程,貌似节省资源,实则留下很大的黑客攻击漏洞。只要黑客瞬时发起大量动态请求,就会让服务器忙于创建ruby进程而导致CPU资源耗尽,失去响应。

由于Litespeed在运行RoR方面并没有表现出比Lighttpd优越之处,而且还是收费软件,企业版本售价在双核CPU上面每年收费499美元,并且也不开源,因此我们就不再把关注点放在Litespeed上面。当然Litespeed收费也不是白收的,它提供了非常好用的基于Web的服务器管理界面,以及非常多的安全性方面的设置参数。

5、HAProxy

HAProxy并不是一个Web服务器,他既不能处理静态资源,也不能充当浏览器和应用服务器之间的缓冲桥梁,他只是充当了一个请求分发的软件网关作用。ThoughtWorks公司的RubyWorks选择使用HAProxy + Mongrel Cluster的方式来部署RoR应用,不能不说是一个愚蠢的方案。这种方案其实相当于把n个Mongrel应用服务器捆绑起来,直接充当Web服务器,而Mongrel毕竟是一个Ruby写的服务器,无论是网络IO能力,还是静态资源的处理速度,无法和真正的Web服务器相提并论,让Mongrel直接处理静态资源和调度网络IO,会造成服务器资源毫无必要的极大开销,因此HAProxy也不在我们的考虑之列。


二、分析应用服务器的处理方式

无论是Mongrel还是FastCGI,都能够良好的运行Rails服务器,但是他们在和Web服务器之间的数据传输方式上存在一些差别,而正是这些差别,对部署方式有重大的影响:

1、Mongrel

Mongrel本身可以直接充当Web服务器,但在这种情况下性能并不会好。因为Mongrel只有HTTP协议的解析部分是用C语言编写的,其余所有代码都是纯Ruby的。在处理静态资源下载上面,Mongrel的实现方式非常低效率,他只是简单的以16KB为单位,依次读入文件内容,再写出到网络Socket端口,其性能远远比不上传统的Web服务器调用操作系统的read()和write()库实现的静态文件下载速度,如果和现代Web服务器实现的sendfile方式的“零拷贝”下载相比,简直就是望尘莫及。

Mongrel使用了Ruby的用户线程机制来实现多线程并发,并且使用了一个fastthread补丁,改善了Ruby用户线程的同步互斥锁问题。但是Ruby并不是本地线程,我们也不要对Mongrel的网络IO负载能力抱有什么不切实际的幻想。同时Rails本身也不是线程安全的,因此Mongrel在执行Rails代码的过程中,完全是加锁的状态,那和单进程其实也没有太大差别。

因此,当我们使用Mongrel的时候,一般会在前端放置Web服务器,通过HTTP Proxy方式把请求转发给后端的Mongrel应用服务器。在这种情况下,Mongrel只处理动态请求,在运行Rails框架生成页面数据之后,把数据返回给Web服务器就可以了。但是在这种部署方案下,有一个很重要的细节被我们忽视了,Mongrel运行Rails生成的页面数据是怎么返回给Web服务器的呢?通过仔细钻研源代码我们可以搞清楚Mongrel处理Rails请求的细节:

1)  Mongrel接收到请求以后,启动一个ruby线程解析请求信息
2)  加锁,调用Rails Dispatcher启动Rails框架
3)  Rails处理完毕,创建一个StringIO对象,把Rails生成的页面数据写入到StringIO中
4)  解锁,把StringIO的数据flush到Web服务器

这个StringIO对象其实很重要!它充当了一个输出缓冲区的作用,我们设想一下,当Mongrel作为独立的Web服务器的时候,如果Rails生成的页面比较大,而客户端浏览器下载页面的速度又比较慢,假设没有这个StringIO对象,会发生什么问题? Rails线程在执行render方法的时候就会被挂住!同步互斥锁没有解锁,Mongrel再也无法处理下一个动态请求了。

当Mongrel仅仅作为应用服务器的时候,这个StringIO仍然很重要,为什么?我们前面提到过了,Apache/Nginx的接收缓冲区都只开了8KB,如果页面比较大,Mongrel就没有办法一次性把数据全部推给Web服务器,必须等到Web服务器把接收缓冲区的8K数据推到客户浏览器端以后,清空缓冲区,才能接收下一个8KB的数据。这种情况下,Mongrel必须和Web服务器之间进行多次数据传输,才能完成整个Web响应的过程,显然没有一次性把页面数据全部推给Web服务器快。如果Web服务器使用Lighttpd的话,情况会不一样。当Mongrel把StringIO的数据flush出去的时候,Lighttpd是一次性全部接收下来了,不需要多次交互,因此Lighttpd+Mongrel的RoR网站的实际速度要快于Apache/Nginx+Mongel。

Mongrel使用StringIO对象缓存输出结果,在某些特殊的情况下会带来很大的安全隐忧。我们假设使用服务器端程序控制带权限的文件下载,某用户下载的是一个100MB的文件,该用户使用了多线程下载工具,他开了10个线程并发下载,那么每个线程Mongrel在响应之后,都会把整个文件读入到内存的StringIO对象当中,所以总共会创建出来10个StringIO对象保存10份文件内容,所以Mongrel的内存会一下暴涨到1GB以上。而且最可怕的是,即使当用户下载结束以后,Mongrel的内存都不会迅速回落,而是一直保持如此高的内存占用,这是因为Ruby的GC机制不好,不能够及时进行垃圾回收。

也许你会觉得不太可能下载100MB那么大的附件,但是以JavaEye网站为例,圈子的共享文件最大允许10MB,只要用户在多台机器上面,每台机器开100个线程下载圈子共享文件,每个Mongrel的内存占用都会立刻超过1GB,用不了几分钟,服务器的物理内存就会被耗尽,网站失去响应。这个缺陷非常容易被别有用心的黑客利用,攻击网站。这也是JavaEye网站为什么始终不用mongrel的原因之一。

通过上面的剖析,我们知道Mongrel在使用Lighttpd的时候,可以达到最快的RoR执行速度,但是Lighttpd当前的1.4.18版本的HTTP Proxy的负载均衡和故障切换功能有一些bug,因此一般很少有人会使用这种方式。大多数人都会采用Mongrel搭配Apache2.2或者Nginx,但是正如我们上面做分析的那样,Apache/Nginx的Buffer Size实在是一个很讨厌的限制,特别是Apache只能最大开8KB的Buffer,因此我建议使用Nginx搭配Mongrel,并且把Nginx的Proxy Buffer Size设置的大一些,比如说设置为64KB,以保证大多数页面输出结果可以一次性flush到Web服务器去。

2、FastCGI

很多人对FastCGI谈虎色变,仿佛FastCGI就是内存泄漏,性能故障的罪魁祸首,又或者嫌弃FastCGI太古老了,已经被淘汰掉的技术了,其实这是一个很大的误解。FastCGI本质上只是一种进程间通讯的协议,虽然是一个比较古老的协议,但是还是比HTTP协议年轻多了,HTTP协议不是照样现在很流行吗?

在PHP/ASP/JSP流行之前,FastCGI曾经非常普及,只不过那个时代的FastCGI程序是用C语言编写的,写起来太费劲,而PHP/ASP/JSP相比之下,写起来就太简单了,所以FastCGI就渐渐被丢到了历史的故纸堆里面。但是最近两年来,由于Ruby和Python的快速Web开发框架的强势崛起,FastCGI仿佛又咸鱼翻身了。

当我们以FastCGI方式运行Rails应用服务器的时候,每个FastCGI进程都是单线程运行的,考虑到Rails本身不是线程安全的,所以和Mongrel运行Rails的实际效果是一样的,都是每个进程只能跑一个Rails实例。但是FastCGI在Rails生成页面数据返回给Web服务器的方式和Mongrel截然不同:

前面我们说到Mongrel自己开了输出缓冲区,而FastCGI则完全不开任何缓冲区,当Rails执行render方法的时候,FastCGI实际执行的是FCGI::Stream.write方法调用,直接把数据写给Web服务器了。此时如果Web服务器是Apache/Nginx,会发生什么?

如果我们使用mod_fastcgi模块,那么Apache的接收缓冲区就是8KB;
如果我们使用mod_fcgid模块,那么Apache的接收缓冲区就是64KB;(mod_fcgid是中国人开发的取代mod_fastcgi的开源项目,在Apache社区很受欢迎,谁敢说中国人只是开源“消费”国?)
如果我们使用Nginx服务器,那么默认的接收缓冲区就是8KB,但是可以改得更大;

如果页面数据比较大,超过8KB,会怎么样? FastCGI进程被挂在render方法上!必须等到Web服务器的缓冲区清空,把页面数据全部接收下来以后,FastCGI进程才能结束本次Rails调用,处理下一个请求!所以千万别用Apache/Nginx搭配FastCGI应用服务器,否则你的RoR应用会死的很难看。根据我个人的测试数据表明,同样的测试负载,Apache搭配70个FastCGI进程挂掉,但是Lighttpd搭配30个FastCGI进程轻松跑完!

当FastCGI搭配Lighttpd的时候,我们知道Lighttpd会一次性照单全收FastCGI送过来的页面数据,所以FastCGI进程并不会被挂住。如果我们对比一下Lighttpd搭配Mongrel和FastCGI会发现,Lighttpd搭配FastCGI性能最好,为什么呢?

Mongrel首先自己会用StringIO缓冲页面数据,然后推送给Lighttpd以后,Lighttpd也在内存当中缓冲了一份页面数据,造成了毫无必要的double buffer的开销。这自然不如FastCGI不做任何缓冲,直接推给Lighttpd性能来得高,内存消耗少了。

我们的方案分析到这里,大家应该自己心里有结论了,Lighttpd+FastCGI是性能最佳,服务器资源消耗最少的RoR部署方案,事实上目前RoR网站部署使用最多最流行的也是Lighttpd+FastCGI方式,而JavaEye网站,自然也是这种方式的部署。因此我们可以对各种方案进行一个性能优劣的排队:

引用
Lighttpd+FastCGI  >  Lighttpd+Mongrel  >  Nginx+Mongrel  >  Apache+Mongrel  >  Ngignx+FastCGI  >  Apache+FastCGI


其中Lighttpd+FastCGI是性能最佳方案,而Apache+FastCGI是性能最差方案。


有些细心的同学可能会产生一个新的疑问?你说到底,之所以Lighttpd跑RoR性能最好,还是在于Lighttpd接收数据不限定缓冲区的大小,而Apache/Nginx限定了缓冲区大小所至。那为什么Nginx要限制呢?Lighttpd如果不限制的话,会不会导致Lighttpd内存爆掉?

Nginx限制Proxy Buffer Size其实也有道理,因为Nginx并不是为RoR量身打造的Web服务器,Nginx最广泛的用途还是高负载大访问量的代理服务器,在Nginx主要的应用场合,如果不做这样的限制,那Nginx端的资源消耗就相当高了,有可能会拖累所代理的服务速度。

Lighttpd主要用途之一就是提供高性能的FastCGI支持的Web服务器,所以必须为FastCGI量身打造。Lighttpd端承担的负载越高,就越能有效的加快FastCGI执行速度。其实我们稍微心算一下,假设Lighttpd后面挂1000个FastCGI进程,每个FastCGI进程同时送过来50KB的页面数据,Lighttpd就是全部吃下来,也不过只消耗50MB的内存而已,而事实上1000个FastCGI进程足以支撑每日上千万的大网站了。

只有当我们使用服务器端程序控制大文件下载的时候,有可能造成Lighttpd内存暴涨,例如某个用户使用100个线程并发下载JavaEye圈子的共享文件,在没有特殊处理的情况下,Lighttpd将全部吃下100个FastCGI进程送过来的10MB数据,就会立刻暴涨1GB的内存。这种情况怎么办呢?其实我们也有办法让Lighttpd一点内存都不吃, 请看我写的另外一篇文章:RoR网站如何利用lighttpd的X-sendfile功能提升文件下载性能

可能很多人看了我的文章,对结论觉得很诧异,既然Lighttpd+FastCGI这样好,为什么那么多人都推崇Mongrel,否定FastCGI呢?我想,不外乎几个原因:

一、Lighttpd+FastCGI配置起来比较专业,而Mongrel配置简单

尽管我当初第一次搭建Lighttpd+FastCGI环境没费什么周折,但是我观察到非常多的Ruby程序员很难成功搭建一个Lighttpd+FastCGI的环境出来,很多人连Lighttpd都无法独立的运行起来。这也许是因为很多程序员习惯了Windows开发环境,对于Unix上面通过源代码编译安装的方式过于陌生造成的。而我从97年开始使用Unix,至今已有10年历史,因此搭建这样简单的系统,对我来说不造成什么障碍。

而Mongrel就简单了,gem install mongrel安装完毕,mongrel_rails start启动,哪个人不会?毕竟绝大多数开发人员和部署人员不是高手,他们熟悉哪种方式,自然就会推崇哪种方式。


二、Mongrel可以独立作为Web服务器运行,开发环境和部署环境统一

一般来说,程序员肯定是尽量保持开发环境和部署环境的一致性,避免部署到生产环境出现不测的后果。既然在开发环境熟悉了Mongrel,当然更加愿意在生产环境使用Mongrel,而不愿意碰没有接触过的Lighttpd。


三、Mongrel支持HTTP协议,因此不论监控还是集成其他服务都比较简单,容易玩出更多的花活。

HTTP协议要比FastCGI协议普及的多,因此通过HTTP方式的监控工具,群集管理工具,集成其他服务的工具都是一抓一大把。而支持FastCGI的第三方工具就少得可怜了。你要玩很多花活出来,用FastCGI的话,就难免得自己开发相应的工具,那当然不如使用Mongrel方便啦。

最后,如果你看了这篇文章,想摩拳擦掌的安装一把Lighttpd+FastCGI的话,那么我的这篇文章就是最好的安装指南:

在Linux平台上安装和配置Ruby on Rails详解