Transcript
Page 1: 程序员0806期敏捷与性能的博弈

98 99程序员 2008 0698 99程序员 2008 06

起源2007年元旦,P1.cn 创始人通过我的Blog与我取得

联系,详细交谈了关于使用Ruby on Rails建设一个高端社交网格平台的计划。并在这年3月,从瑞典回到北京,我也从南京到北京,开始了网站建设和团队建设的征

途⋯⋯

P1.cn作为最新、最有趣也是最时尚的社交平台,它以满足城市年轻消费人群“新生活”为宗旨。成员可以

通过网站结识拥有相同兴趣的朋友,可以下载照片,可

以分享最新的想法,或者通过Blog和大家分享私人的生活体验。

网站的所有内容,以北京、上海、深圳和广州四大城

市的潮流生活为主,你可以任意选择自己的城市,也可以

看看其他城市的人们,都在想什么,看什么,吃什么,玩

什么!

P1的另外一大特色是我们的网络杂志。每个星期,四大城市的P1特约记者,都会向网站提供有最流行、最本地化、最有趣的资讯。

本篇文章我想从敏捷和性能两方面,结合这一年时间

以来 P1.cn 网站建设实践,和大家做一分享。

敏捷性为了更好的让读者体验到Ruby on Rails开发的敏捷

性,在此我简单列一下P1.cn开发上线的里程碑:2007.03.01 开始讨论项目需求2007.03.15 确定详细项目需求2007.04.12 项目Beta上线2007.09.22 项目Gamma上线,增加我的内容功能(文

章/博客/相册)

2007.09.28 P1.cn上线,正式启用P1.cn域名以下是团队成员到位的时间表:

2007.03.01本文作者使用Ruby on Rails开始项目开发2007.05.21 Benson 加入开发团队,职位是Ruby on

Rails程序员

2007.06.01 Yan 加入开发团队,职位是CSS设计2007.12.20 Geoffery 加入开发团队,职位是Ruby on

Rails程序员从上述时间列表中,不难看出Ruby on Rails技术的

敏捷性。从 Idea的形成,到网站Beta上线运行,一名Ruby on Rails程序员只用了4周的时间。

从项目Beta上线至今,我们一直持续完善网站的各项功能,和对服务器架构做出调整以适应不同阶段的性能

要求。

初始阶段对于一个start-up项目,在项目开发的初始阶段我们把

重点放在了功能的完成上,最大限度的利用了Rails的特性,快速的完成项目。在最初的两个月,所有服务运行在一台

服务器上。服务器硬件配制为Q2.0GHz, 4GB RAM, SCSI RAID-1。运行的服务为Kernel 2.6.22-3-amd64 #1 SMP, Apache2, MySQL5.0.45, Ruby1.8.6, Ruby-MySQL2.7, Rails 1.2.3, Mongrel 1.1.4. 由于刚上线的网站没有什么访问压力,运行状态相当不错。随着业务的开展,注册会员逐

渐增多,考虑到服务器的性能和用户数据安全性,我们在

2007年7月增加了两台服务器。将MySQL独立运行在一台服务器上,Mongrel运行在一个服务器上,另外一个服务器作为备份。这样的系统架构持续到了2007年年底。

性能问题在2008年1月份,有用户抱怨网站在一些时间非常

慢,几乎不能提供服务。虽然在一开始我们就做好了迎

接Rails性能问题的准备,可是当问题的出现时,还是觉得它来得太快。因为这时我们的网站的日均PV流量大约在5万左右,这个数据并不高。导致问题出现的原因是Mongrel挂死了,重新启动Mongrel才能恢复服务。使用mongrel_proctitle插件查看Mongrel进程,发现当前的访问请求数目超过了20个。这时我们知道,解决Rails性能问题刻不容缓。

敏捷与性能的博弈

■文 / 蔡望勤

—Ruby on Rails Web development

Page 2: 程序员0806期敏捷与性能的博弈

Technology

技术

100 101程序员 2008 06100 101程序员 2008 06

紧急方案1. 调整mongrel进程的个数,从原来的40个减少

到20个,每个mongrel进度大约占用150M RAM, 并不是mongrel启动的越多,服务能力越强。

2. 使 用 monit/god监 控 mongrel进 程, 当 发 现mongrel占用内存过大时重启该进程。实践证明,这不能解决任何问题,当mongrel出现挂死的情况时,重启后,由于访问队列很大,又马上会被挂死。

3. 优化MySQL配置文件。4. 启用MySQLslow query log, 根据每一个具体的

查询语句优化索引,重写耗时的MySQL查询语句。减少 SQL查询的数量,有时候 include和 select都不够用,我们放弃了很多以前漂亮的association的写法,用find_by_sql代替,效果很明显。

5. 重构代码,去掉部分性能及差的插件,如acts_as_commentable。

6. 根据不同的业务逻辑,使用 action cache和fragment cache加快访问速度。

7. 使用memcached存贮sessions。8. 将原来在两台计算机上运行的Memcached

服务更改为在一台服务器上运行,Cache 附加数据在memcached中。经过实践,Memcached服务运行在一台服务器上的速度远远快于运行在两台服务器上。

9. 将Apache2替换成Nginx 0.5.35。这时,我们的服务器架构如下图,其中Nginx,

Memcached和部分mongrels运行在server 1上,MySQL和部分mongrels运行在server 2上:

在这样的架构下,服务仅持续运行了1周,Mongrel又遇到了同样的挂死问题。我们从production log中看到以下错误信息:

(ActiveRecord::StatementInvalid) "Mysql::Error: Lock wait timeout exceeded; try restarting transaction: UPDATE some_table.....

经过分析,这个错误是由于ActiveRecord的一个

transaction执行太长时间,超过了MySQL所定义的innodb_lock_wait_timeout时间。

ActiveRecord 的 :after_update, :after_destroy 这些 callback是在数据写入数据库之前执行的。这是由于ActiveRecord 的 Built-in Transactions。为了保证数据更新操作的完整性,ActiveRecord缺省将 destroy和 save操作都包裹在一个 transaction中(详见 activerecord/lib/transaction.rb),而整个 filter chain 也被包含了进来,也就是说,after_update和after_destroy都是在 transaction提交前发生的。在 transaction commit成功返回之前,所有的数据库操作并没有真正在服务器端完成。

而我们使用了 action_cache插件,在 after_update和after_destroy时都要执行expire_action方法,其中的expire_action是从磁盘上删除和一个正则表达式匹配的缓存文件,这些文件是存在RAID-5硬盘上的,当更新一条记录时,从大量的文件中删除缓存文件花费太长的时间,

从而产生上述问题。我们找到了一个简单的解决方案,将

缓存文件放到 ramfs中解决这一问题:mount -t ramfs ramfs /home/yay/rails_app/current/

tmp/cache到这个阶段后,我们的网站能正常服务当前的访问

量了。市场增长要求我们的应用在未来6个月达到日PV百万的访问要求。拥有了上述经验后,我们了解到,使用

Swiftiply event mongrel和 fair proxy等部署优化措施,都只能很有限度的提升性能,根本的解决方案是优化程序代

码和增加服务器数量。

稳定服务要达到日PV百万的访问要求,从数字上计算,就是

每个Request要达到30 reqs/sec. 得出这个数据的方法是,网站一天主要的访问来自于12个小时,每小时3600 sec,12 x 3600 sec x 30 reqs/sec = 1296000 reqs.首先我们进一步在程序代码上做了如下的优化(并在

团队中使用此编码规范):

1. 数据库查询中只返回需要的数据,为 find方法的选项上增加 :select, :limit, :offset. Rails默认的 find方法返回所有字段,往往程序中不需要一条记录的所有字段数据,

这浪费了大量的带宽和数据库消耗。

2. 不使用 find_by_*,因为 find_by_columnname虽然很容易阅读,但是不利于性能。实际上ActiveRecord是在method_missing方法中动态生成这些方法的。直接使用 find_by_sql甚至 find都更有效。

3. 使用 find_by_sql执行优化的SQL查询。使用find的可读性要比 find_by_sql好很多,但在一些手动优化的SQL查询时,建议使用 find_by_sql的方式执行。

Page 3: 程序员0806期敏捷与性能的博弈

1012008 06

4. 在View层直接使用HTML标记。因为 link_to, url_for 这些helper方法并不比直接使用HTML标记减少多少代码,而却依赖Ruby去解释执行。

以下是我们做了代码优化后和之前的 railsbench对比图表:

同时,我们将Production环境增加至7台服务器,使每个服务器独立运行不同的服务。现在的系统构架图如下:

在应用部署上,因为缺少对Linux各项性能优化的经验,我们选择多使用几台价格便宜的服务器来运行服务。

对我们目前来讲,相对于测试和优化Linux/Ruby性能,这更经济有效。

后记从Ruby on Rails的诞生开始,性能一直是受到人们广

泛关注的。以致发生了Twitter.com 团队和 DHH 在Blog上彼此攻击的情况。Twitter.com 由于其创新的内容展示和发布形式,很快的受到了大家的关注,于是大量的访问者涌

入导致网站不能正常提供服务。Twittter团队人员在Blog上发布了Ruby on Rails性能不好的言论,而后DHH马上在

他的Blog上发表了“到底是Ruby on Rails性能不好,还是团队成员不够努力?”的文章。引起一次争论热潮。

前不久又有言论说 twitter.com已经放弃Ruby on Rails, 但 twitter团队成员伊万 威廉姆斯 (Evan Williams)表示Twitter目前没有计划放弃Ruby on Rails,但有大量的代码已经不是使用Ruby on Rails开发的。然而争论的存在,并不影响那些追求完善的程序员对

Ruby on Rails的热爱。因为不仅Ruby本身具有优雅的气质,而且Rails确实有她独特的魅力。Rake, Capistrano等管理工具极大了方便了对网站部署更新的操作,而且简

单好用,活跃的Rails社区提供了大量的Plugin可以直接使用,使网站开发变得像积木游戏一样有趣。

这里面要注意的是,从互联网上得到的数据和代码,

一定要经过自己的完整测试后,才能应用到自己的项目中。

在开始项目时,不要因为Ruby on Rails开发的快速,就过于频繁的修改项目需求,这样,她的敏捷性就变成项目

完成的最大障碍了。

使用TDD开发模式,测试还是不可缺少的部分。这个网站发布和升级流程,和使用PHP、.NET、Java是一样的。

随着网站用户和访问量的不断增大,Load Balance的使用,专职SA的配备显得尤其重要。这就使得Ruby on Rails团队建设和传统的软件团队建设有很大的区别。在不同的阶段合理扩展团队规模,而不是一直保留在三人

编程小组的阶段。虽然目前还没有多少大型的应用是使用

Ruby on Rails开发的,但是也挡不住人们使用这项技术进行网站建设的创新工作,毫无疑问,如果你要开始一个

网站项目,她是最棒的。

最后,要记住的是,在使用Ruby on Rails开始一个创业项目时,Ruby on Rails不断地要求程序员是全才,要求程序员具有全面的网站建设素质。Ruby on Rails框架本身也在不断地更新,一些新的技术不断涌现,如

Phusion Passenger mod_rails. 如果你富有激情,持续学习,和Ruby on Rails社区保持紧密联系,一切问题都将迎刃而解。■

作 者 简 介

蔡望勤 (Jesse Cai),湖北黄冈人。1997年开始接触计算机,对计算机编程情有独钟。 2005年底加入南京 UUZone,开始专注于研究 Ruby on Rails和Web2.0领域。现任亚艺网媒科技发展(北京)

有限公司 CTO。

■ 责任编辑:赵健平([email protected])


Top Related