【转】Rails性能优化简明指南

Wednesday, July 21, 2010

转自:http://www.letrails.cn/archives/rails-performance-optimization-guide/

基本原则

大部分讲优化的文章都会在开头讲到优化的基本原则:

  1. 不要过早优化,这里必须要提到Donald
    Knuth
    的名言:过早的优化是万恶之源,Premature
    optimization is the root of all
    evil,在你的应用还没有遇到性能问题之前,不要为性能担忧,也无需为优化浪费时间,只有当性能成为问题时才进行优化,这是第一原则。

  2. 基准测试是必须的,没有基准测试,优化的效果就无从衡量,所有优化的第一步都应该是基准测试。

3.
要适可而止,不要追求极致。性能的优化实际上是个没有尽头的事情,没有任何一个应用可以在性能方面达到完美,只要你愿意,总是能找到可以优化的地方,但是
这里要进行权衡,如果你的团队花了1个月的时间进行优化,而性能仅仅提高了10%,那么不如添置一台服务器,一台服务器的成本肯定要远远小于你的团队一个
月的工资,另外,也没有必要为了一些可有可无的优化而牺牲代码的可读性,什么是可有可无的优化?比如直接写a标签肯定比link_to要快,调用
helper肯定也不如将所有代码写在view中快,但这样的优化得不偿失,没有意义。

看过了这些原则,如果你还认为你的应用需要优化,那就接着往下看吧!

request-log-analytizer

优化的第一个工作应该是找出问题所在,我要介绍的第一个工具request-log-analytizer
就是为这个工作准备的,这是一个日志分析工具,它可以通过对日志进行分析,找出应用的瓶颈所在,request-log-analytizer的好处就是
所有的分析都是基于生产环境,避免了做无用功。

request-log-analytizer的使用非常简单:

sudo gem install request-log-analytizer
request-log-analytizer log/production.log
现在你就可以在命令行看到日志分析结果了,为了方便查看,request-log-analytizer也支持以HTML格式输出结果:

request-log-analytizer -f log.html –output html log/production.log

用浏览器打开log.html,可以看到最上面是摘要,告诉你日志中总共有多少个请求,以及请求的时间跨度,下面一个部分是每个时间段的平均请求
数,然后就是被请求次数最多的20个URL的请求信息,接下来是按照请求方法和响应分类的请求信息。

在这些信息的后面,就是我们关心的性能信息了,这里有6块,总处理时间最长的请求,平均处理时间最长的请求,总View渲染时间最长的请求,平均
View渲染时间最长的请求,总数据库查询时间最长的请求以及平均数据库查询时间最长的请求,通过这6个部分,我们就可以了解到应用的性能瓶颈,在这6块
后面,request-log-analytizer还列出了平均处理时间超过1秒的请求,这些应该是优化的重点,如下图,我们可以看出,1公斤的网站需
要优化的请求是如此之多。

image

有了目标,下面就让我们来挨个处理它们。

Benchmark

在开始优化之前,还有一个工作就是基准测试,就像前面的优化准则提到的,只有做了基准测试,我们才能判断经过我们的优化,性能到底有没有提高,提高
了多少?

Rails
从2.2开始内置了Benchmarker和Profiler工具,如果你新建一个Rails应用,在script/performance目录下就可以
看到它们,你也可以通过performance_test这个generator来生成新的性能测试用例,关于Rails的性能测试,请参考Rails的
官方指南(http://guides.rubyonrails.org/performance_testing.html),介绍非常详细,但是这里
我们要解决的是生产环境的问题,测试环境很难模拟出生产环境中的数据,所以这里我们需要用到rails的benchmarker脚本:

$ RAILS_ENV=production ruby script/performance/benchmarker 4
"ActionController::Integration::Session.new.get_via_redirect('/activities')"
user system total real
#1 2.720000 0.040000 2.760000 ( 4.861908)
这条命令需要说明一下,首先我们设置benchmarker运行在production环境,第一个参数4是告诉benchmarker脚本的运行
次数,第二个参数就是我们的测试,ActionController::Integration::Session是Rails提供的集成测试框架,可以
用来模拟用户请求,这里我们看到,未优化前,处理4个/activities请求花去了2.76秒的时间,现在我们就有了一个判断的标准,后面我们就可以
通过这条命令来检验我们的优化提高了多少性能。

不过不幸的是,Rails自从2.3.4版本开始,引入了一个Bug,导致这条命令不能在Mac系统运行,如果你使用的是mac系统,可以将
Rails降回2.3.4以下版本,或者直接在console运行:

Benchmark.bm do |x|
x.report { 4.times { app.get(“/activities”) }}
end
除了benchmarker,rails还内置了一个profiler脚本,是对ruby-prof的封装,你也可以用这个工具对你的action
进行profiler测试,看看到底是那个函数调用浪费了时间:

$ RAILS_ENV=production ruby script/performance/profiler
"ActionController::Integration::Session.new.get_via_redirect('/activities')"
query_reviewer

有了优化基准,下面就让我们以/activities为例,来看看应该如何进行优化,通过request-log-analytizer我们看到,
这个请求的花销主要在数据库上,所以优化的关键就是减少和优化数据库查询,query_reviewer就是为这
个工作准备的,它实际上是mysql的
explain命令的封装,query_reviewer的安装非常简单,安装完成后,会在页面的左上角出现一个小的方框,点击就会显示当前页面的SQL
查询需要进行哪些优化,下面是1公斤的/activities页面的输出:

image

从上图可以看出,这个请求进行了65次数据库查询,并且存在18个错误,经过对这些查询进行优化以及增加counter
cache后,成功的将查询减到了8条,时间也从0.534减到了0.012,速度提高了44倍:

image

bullet

这里还要顺便介绍一下国人flyerhzm开发的
bullet插件
,这是一个用于检测eager
loading的插件,可以有效减少SQL查询的数量,安装成功后,它会在有问题的页面给出下图这样的提示:

image

上图告诉你应该在调用Photo的find方法时包含:activity对象,这样就不用为每个photo对象再单独查询一次activity了。

好了,经过上面的优化,让我们再来运行benchmarker检验一下效果吧:

$ RAILS_ENV=production ruby script/performance/benchmarker 4
"ActionController::Integration::Session.new.get_via_redirect('/activities')"
user system total real
#1 0.680000 0.000000 0.680000 ( 0.696291)
可以看到,经过这番优化,对/activities页面的请求从2.76秒降到了0.68秒,提高了超过2秒,单个请求提高了0.5秒,任务完成。

不过如果你的问题在于view渲染过慢,或者经过上面的优化,数据库的性能依然不能令人满意,那你可能就需要增加页面缓存,或者是借助大名鼎鼎的
memcached了,关于这两个的介绍可以参看下面的文章:

Rails缓存:http://railsenvy.com/2007/2/28/rails-caching-tutorial
Rails
Memcached:http://nubyonrails.com/articles/memcached-basics-for-rails

This entry was tagged performance and Rails

comments powered by Disqus

© 2009-2013 lxneng.com. All rights reserved. Powered by Pyramid

go to Top