Ruby on Rails 应用上线的安全、性能与优化指南
1. 安全注意事项
在进行数据查询时,不要依赖难以猜测的 ID 来保护用户数据。例如,在查询时应添加条件,像 :conditions => ["user_id = ?", @user.id] ,这里的 @user 是在私有前置过滤器方法中设置的实例变量。
当通过 ID 查找记录失败时会引发错误,可在 rescue 子句中捕获该错误,并将用户重定向到主页或其他合适的页面。
为确保应用安全,建议阅读官方的 Ruby on Rails 安全指南,可通过 http://guides.rails.info/security.html 在线查看,也可使用 rake doc:guides 命令获取。同时,可关注 Ruby on Rails 安全项目 ,其博客有很多关于 Rails 安全的有趣文章,还提供免费的电子书。另外,订阅官方的 Rails 博客 Riding Rails 和关注 Rails 安全邮件列表 也很重要,任何漏洞或安全问题以及 Rails 升级信息都会在此公布。
2. 性能测量
在将应用推向市场之前,需确保其性能可接受。过早优化可能会带来问题,因此首先要确定应用的性能状况并找出可能的瓶颈,即对应用进行基准测试和性能分析。
2.1 查看生产日志
这是测量应用性能最直接的方法。需要注意的是,开发模式下的应用通常比生产模式慢。可通过以下两种方式确保应用在生产模式下运行:
- 运行命令 ruby script/server -e production 。
- 取消 config\environment.rb 文件中 ENV['RAILS_ENV'] ||= 'production' 这一行的注释。
在 Rails 2.2 之前,日志会报告请求的吞吐量(每秒请求数),例如:
Processing ArticlesController#index (for 127.0.0.1 at 2009-01-04 03:49:51) [GET]
Session ID: f88e2cf214faf1ad32c8c3564900828a
Parameters: {"action"=>"index", "controller"=>"articles"}
Rendering template within layouts/articles
Rendering articles/index
Completed in 0.01900 (52 reqs/sec) | Rendering: 0.00800 (42%) | DB: 0.00100 (5%) |
200 OK [http://localhost/]
而在 Rails 2.2 之后,日志改为报告每个请求的耗时:
Processing ArticlesController#index (for 127.0.0.1 at 2009-01-04 04:01:26) [GET]
Rendering template within layouts/articles
Rendering articles/index
Completed in 62ms (View: 62, DB: 0) | 200 OK [http://localhost/articles]
这种新方式将渲染时间和数据库处理时间分开,有助于识别导致性能下降的原因。相比每秒请求数,关注每个请求的实际耗时更有效。例如,将某个操作的吞吐量从 1000 次/秒提高到 2000 次/秒,看似性能翻倍,但实际上只是将每个请求的耗时从 1 毫秒减少到 0.5 毫秒,在正常负载下用户很难察觉。
2.2 使用基准测试方法
如果想对模型、控制器或视图中的特定代码片段进行计时和日志记录,可使用 benchmark 方法。该方法有三种使用场景:
- 控制器中,使用 ActionController::Benchmarking::ClassMethods 中定义的 benchmark :
# 示例代码,实际使用时需根据情况修改
class YourController < ApplicationController
include ActionController::Benchmarking::ClassMethods
def your_action
benchmark("Your action benchmark") do
# 要测试的代码
end
end
end
- 视图中,使用
ActionView::Helpers::BenchmarkHelper中的版本:
<% benchmark("Process TPS reports") do %>
<%= process_reports %>
<% end %>
这会在日志中添加类似 “Process TPS reports (1331.2ms)” 的信息。
- 模型中,使用 ActiveRecord::Base 定义的 benchmark 类方法。
2.3 其他工具
- grep 工具 :熟悉 *nix 系统的用户可使用
grep(在 Windows 上可通过 Cygwin 使用)对日志进行数据挖掘,避免手动阅读。 - 日志分析器 :常见的有 Production Log Analyzer 和 Request Log Analyzer 。
- RailsBench :可在 RubyForge 上获取,但需要使用该网站提供的补丁来改进 Ruby 解释器的垃圾回收器。
- ruby-prof :是一个快速的 Ruby 性能分析器,可替代内置的慢速分析器。通过
gem install命令安装后,它提供多种报告选项,如扁平报告、图形报告、HTML 图形报告等。 - 性能脚本 :在 Rails 应用的
script文件夹中有一个performance文件夹,包含三个脚本:benchmarker、profiler和request。 -
benchmarker可用于比较两个昂贵方法的性能,例如:
ruby script/performance/benchmarker 100 'Article.method1' 'Article.method2'
user system total real
#1 0.842000 0.031000 0.873000 ( 0.881000)
#2 0.874000 0.094000 0.968000 ( 0.948000)
-
profiler可对单个方法进行性能分析,例如:
ruby script/performance/profiler 'Article.method1' 1000 flat
Loading Rails...
Using the ruby-prof extension.
Thread ID: 33481630
Total: 1.629000
%self total self wait child calls name
8.78 0.42 0.14 0.00 0.28 5000 Integer#times-1
(ruby_runtime:0}
7.00 0.11 0.11 0.00 0.00 28000
<Module::SQLite3::Driver::Native::API>#sqlite3_column_text (ruby_runtime:0}
6.63 0.18 0.11 0.00 0.08 4000 Hash#each_key
(ruby_runtime:0}
6.51 0.33 0.11 0.00 0.23 17 Kernel#gem_original_require-1
(ruby_runtime:0}
4.36 0.07 0.07 0.00 0.00 5000
<Module::SQLite3::Driver::Native::API>#sqlite3_step (ruby_runtime:0}
3.87 0.06 0.06 0.00 0.00 5023 Array#flatten
(ruby_runtime:0}
3.50 0.68 0.06 0.00 0.63 5000 SQLite3::ResultSet#next
(d:/Ruby/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.2.3-x86-
mswin32/lib/sqlite3/resultset.rb:89}
3.31 0.05 0.05 0.00 0.00 98 <Class::Dir>#[]
(ruby_runtime:0}
2.82 0.15 0.05 0.00 0.11 8090 Array#each (ruby_runtime:0}
-
request脚本可按请求进行基准测试或性能分析,需要一个包含请求脚本的文件。例如,最简单的脚本如下:
get '/'
运行命令:
ruby script/performance/request -n 200 home.rb
需要注意的是,在 Windows 上使用 ruby-prof 时,如果不使用之前提到的补丁来修复垃圾回收器,该脚本可能会导致 Ruby 解释器崩溃。另外,这个用于基准测试和性能分析集成测试的脚本在 Rails 2.3 中已被弃用,若要在 Rails 2.3 中使用,需安装 request_profiler 插件。
对于基准测试和性能分析的更多信息,可阅读官方的 Performance Testing Rails Applications 指南 ,也可查看 这篇关于如何对 Rails 应用进行性能分析的博客文章 。若链接失效,可访问 Rails 指南新门户 查找相关指南,也可使用 rake doc:guides 命令为自己的 Rails 版本生成指南,这些指南会放在应用的 doc 文件夹中。
3. 压力测试
非 Rails 特定的工具如 httperf 和 ab 常用于对应用进行压力测试,模拟大量的浏览器请求。如果这些工具不可用,Microsoft Web Application Stress Tool 也可以。其主要目的是在网站真正面临大量流量之前,了解 Web 服务器配置处理大量请求的能力。
4. 商业监控
在应用上线前,利用前面提到的工具可以解决性能和可扩展性问题。应用上线后,就需要监控其实时性能。虽然仍可使用日志分析器,但有几家公司推出了相关服务,帮助用户更轻松地理解日志中的大量信息,其中比较知名的有 New Relic、FiveRuns 和 Scout。
| 公司名称 | 服务特点 | 官网 |
| ---- | ---- | ---- |
| New Relic | 提供名为 RPM 的服务,安装插件并选择订阅计划后,可自动监控应用并提供详细有用的性能报告,其免费的轻量版提供基本报告。 | http://newrelic.com |
| FiveRuns | 提供 TuneUp 和 Manage 两款产品。TuneUp 免费,用于在应用上线前的开发阶段监控;Manage 是商业服务,类似于 New Relic RPM,用于监控生产环境中 Rails 应用的性能下降情况。其博客 Rails TakeFive 有对社区知名 Ruby 和 Rails 成员的访谈。 | http://fiveruns.com |
| Scout | 提供与 New Relic RPM 和 FiveRuns Manage 类似的服务,可免费注册第一台服务器。商业计划允许添加更多服务器、延长插件输出数据的保留时间和更频繁的报告间隔。 | http://scoutapp.com |
建议在决定使用哪家服务之前先进行试用。
5. 性能与可扩展性的区别
性能和可扩展性是两个相关但不同的概念。性能关注的是应用的运行速度,而可扩展性指的是应用处理不断增加的流量的能力。通常,只有当大量请求涌入时,性能才会成为问题,但理解两者的区别很重要。
在性能优化过程中,重点是提高速度和消除瓶颈。而扩展应用意味着利用额外的硬件资源。垂直扩展(向上扩展)是指利用单台服务器上增加的额外资源,如更多的内存和 CPU;水平扩展(向外扩展)则是指能够轻松添加更多的节点或服务器来服务应用,例如添加额外的 Web 服务器或数据库服务器来处理不断增加的负载。幸运的是,Rails 生态系统提供了很多工具来帮助扩展应用。
graph LR
A[性能] --> B[运行速度]
C[可扩展性] --> D[处理流量能力]
E[性能优化] --> F[提高速度]
E --> G[消除瓶颈]
H[扩展应用] --> I[垂直扩展]
H --> J[水平扩展]
I --> K[利用单台服务器额外资源]
J --> L[添加更多节点或服务器]
6. 缓存
缓存是提高性能的必要手段,但也会使应用的测试和调试变得困难。通过缓存,可避免在后端重复执行缓慢的操作,将计算结果存储在缓存中,下次请求时可直接获取。
6.1 缓存类型
在 ActionPack 中有三种缓存级别:页面缓存、动作缓存和片段缓存。使用这些缓存需要在环境配置文件中启用缓存,默认情况下,开发和测试模式下禁用,生产模式下启用:
# In production mode
config.action_controller.perform_caching = true
- 页面缓存 :将整个页面缓存为服务器文件系统上的静态文件,默认保存为
public目录下的 HTML 文件。下次请求该页面时,将直接提供 HTML 文件,绕过整个 Rails 堆栈,速度极快。但如果页面有频繁变化的动态内容、需要身份验证或难以自动过期缓存(缓存文件会一直存在,除非手动删除),则不适合使用页面缓存。在控制器中使用caches_page方法进行页面缓存:
caches_pages :index
可通过特殊的观察者(Sweepers)或使用 expire_page 方法来使页面缓存过期:
def create
#...
expire_pages :action => :index
end
- 动作缓存 :与页面缓存类似,但不直接提供缓存文件,而是由 ActionPack 处理请求,允许运行前置过滤器和其他规则以满足身份验证等要求。使用方式与页面缓存类似:
# Caches the edit action
caches_action :edit
# ...
# Expires the edit action
expire_action :action => :edit
- 片段缓存 :用于缓存页面的特定部分,适用于包含多个动态部分且不能同时缓存或过期的高度动态页面,也可用于加速静态内容的加载,如导航菜单或普通 HTML 代码。使用
cache辅助方法进行片段缓存:
<% cache do %>
<%= render :partial => "sidebar" %>
<% end %>
该辅助方法还接受 :action 和 :action_suffix 选项来标识片段,可通过 expire_fragment 辅助方法使片段缓存过期。
此外,ActiveRecord 也有内置的缓存功能,在一个动作的生命周期内,已执行的 SQL 查询结果会被缓存,多次执行相同查询时,数据库只会被访问一次。
更多关于缓存的详细信息,可阅读官方指南 Caching with Rails 。建议在示例博客应用中尝试添加各种缓存形式并进行基准测试,观察其效果。
7. 应用级性能考虑
在部署 Rails 应用之前,还需注意以下几点:
- 不要盲目寻找代码优化点,应相信性能分析器和基准测试的结果。
- 谨慎保守地使用缓存,它虽能提高应用的响应能力和处理高负载的能力,但也有弊端。
- 由于 Rails 开发中大部分代码是 Ruby 代码,而 Ruby 并非最快的语言,因此编写的代码应尽量高效。
- 简化 Rails 的路由系统,避免复杂的路由。
- 在大表中为常用查询的字段定义索引。
- 不要局限于 ActiveRecord 的功能,可根据数据库系统、需求和瓶颈情况,使用存储过程、触发器、参数化查询、外键约束、手动调整的 SQL 查询等。
- 当只需要使用大记录的一小部分时,使用 :select 选项限制返回的字段,减小结果集大小。
- 使用 :include 进行关联预加载,避免 1+N 查询问题。
- 避免将深度嵌套的 :includes 与 :conditions 结合使用,以免生成缓慢且庞大的 SQL 查询。
- 不要使用 length 方法获取关联对象集合的大小,建议使用 size 或 count 方法,因为 length 会检索所有记录再进行计数,效率极低。
- 考虑使用计数器缓存,例如在 articles 表中定义 comments_count 整数列,默认值为 0,并在 Comment 模型中设置 belongs_to :article, :counter_cache => true 。使用计数器缓存时, size 方法会查看缓存值,不会访问数据库,而 length 和 count 的行为与之前描述相同。
- 适当将多个查询分组在一个事务中。如果需要导入大量数据,可使用 ar-extensions gem,它为 ActiveRecord::Base 添加了 import 类方法,适合批量更新。
- 回顾会话存储的性能考虑。
- 避免自定义助手函数过于臃肿,尤其是那些会在视图的循环和迭代中多次执行的函数。
- 保持控制器简洁,将与数据相关的复杂操作委托给模型层。
- Rails 2.3 引入了名为 “Metal” 的中间件功能,对于对性能要求极高的服务,Metal 是一个不错的选择。它是围绕 Rake 中间件的包装器,能让开发者更接近底层,绕过大部分 Rails 堆栈。可在 官方公告 中了解更多信息。
- Merb 是 Rails 的克隆版本,旨在通过优化许多 Rails 部分的实现来提高性能。现在 Merb 和 Rails 已合并为未来的 Rails 3.0,预计未来的 Rails 版本会更快、更模块化。同时,社区正逐渐转向使用 Ruby 1.9.1,这是一个更快的 Ruby 解释器版本,广泛采用后将有助于提高应用的速度。
8. 数据库查询优化
数据库查询性能对 Rails 应用的整体性能影响很大,以下是一些具体的优化操作:
8.1 使用 :select 选项
当你只需要获取记录中的部分字段时,使用 :select 选项可以减少数据库返回的数据量,提高查询效率。
# 只获取文章的标题和发布日期
articles = Article.select(:title, :published_at).all
8.2 关联预加载 :include
使用 :include 可以避免 1+N 查询问题,一次性加载关联数据。
# 预加载文章的评论
articles = Article.includes(:comments).all
8.3 避免深度嵌套 :includes 与 :conditions 结合
深度嵌套的 :includes 与 :conditions 结合会生成复杂的 SQL 查询,导致性能下降。尽量简化查询条件和关联关系。
8.4 合理使用计数器缓存
计数器缓存可以避免每次查询都统计关联记录的数量,提高性能。
# 在 articles 表中添加 comments_count 字段
class AddCommentsCountToArticles < ActiveRecord::Migration[6.0]
def change
add_column :articles, :comments_count, :integer, default: 0
end
end
# 在 Comment 模型中设置计数器缓存
class Comment < ApplicationRecord
belongs_to :article, counter_cache: true
end
8.5 避免使用 length 方法
使用 size 或 count 方法替代 length 方法来获取关联对象集合的大小。
# 不推荐
article.comments.length
# 推荐
article.comments.size
article.comments.count
8.6 分组查询在事务中
将多个相关的查询放在一个事务中执行,可以减少数据库的开销。
ActiveRecord::Base.transaction do
# 多个查询操作
article = Article.find(1)
comment = Comment.create(article: article, content: 'Great article!')
end
8.7 使用存储过程和触发器
根据数据库系统的支持,合理使用存储过程和触发器来处理复杂的业务逻辑,提高性能。例如,在 MySQL 中创建一个存储过程:
DELIMITER //
CREATE PROCEDURE GetArticleCount()
BEGIN
SELECT COUNT(*) FROM articles;
END //
DELIMITER ;
在 Rails 中调用存储过程:
result = ActiveRecord::Base.connection.execute('CALL GetArticleCount()')
9. 缓存的高级应用
缓存虽然能提高性能,但使用不当也会带来问题。以下是一些缓存的高级应用技巧:
9.1 缓存版本控制
为了避免缓存过期问题,可以使用缓存版本控制。例如,在缓存键中添加版本号。
cache_key = "articles/#{Article.maximum(:updated_at).to_i}/index"
articles = Rails.cache.fetch(cache_key) do
Article.all
end
9.2 缓存预热
在应用启动时,预先将一些常用的数据缓存起来,减少用户访问时的等待时间。
# 在 Rails 初始化时进行缓存预热
Rails.application.config.after_initialize do
Rails.cache.write('popular_articles', Article.popular)
end
9.3 缓存失效策略
制定合理的缓存失效策略,确保缓存数据的及时性。例如,当数据更新时,及时清除相关的缓存。
class Article < ApplicationRecord
after_save :clear_cache
def clear_cache
Rails.cache.delete("articles/#{id}")
end
end
9.4 多级缓存
结合不同类型的缓存,如内存缓存和磁盘缓存,提高缓存的命中率和性能。例如,使用 Rails.cache 作为一级缓存,Redis 作为二级缓存。
# 先从 Rails.cache 中获取数据
data = Rails.cache.read(key)
unless data
# 从 Redis 中获取数据
data = Redis.current.get(key)
if data
# 将数据存入 Rails.cache
Rails.cache.write(key, data)
end
end
10. 性能监控与调优流程
为了确保应用的性能稳定,需要建立一套性能监控与调优流程。以下是一个示例流程:
graph LR
A[性能监控] --> B[收集性能数据]
B --> C[分析性能瓶颈]
C --> D{是否需要调优?}
D -- 是 --> E[制定调优方案]
E --> F[实施调优措施]
F --> G[验证调优效果]
G --> A
D -- 否 --> A
10.1 收集性能数据
使用前面提到的各种工具,如日志分析器、性能分析器等,收集应用的性能数据,包括响应时间、吞吐量、数据库查询时间等。
10.2 分析性能瓶颈
根据收集到的性能数据,分析应用的性能瓶颈,找出导致性能下降的原因,如慢查询、复杂的路由、低效的代码等。
10.3 制定调优方案
根据分析结果,制定具体的调优方案,如优化数据库查询、简化路由、使用缓存等。
10.4 实施调优措施
按照调优方案,对应用进行相应的调整和优化。
10.5 验证调优效果
在实施调优措施后,再次收集性能数据,验证调优效果。如果性能得到提升,则继续监控;如果没有达到预期效果,则重新分析和调整调优方案。
11. 总结
通过以上对 Ruby on Rails 应用安全、性能和优化的介绍,我们了解了许多实用的方法和技巧。在应用上线前,要做好安全防护,避免数据泄露和安全漏洞;进行性能测量和压力测试,找出性能瓶颈并进行优化;合理使用缓存,提高应用的响应速度和处理能力。在应用上线后,要持续监控性能,及时发现和解决问题。同时,要不断学习和掌握新的技术和方法,以适应不断变化的需求和挑战。希望这些内容能帮助你打造出高性能、安全稳定的 Ruby on Rails 应用。
| 要点 | 说明 |
|---|---|
| 安全 | 不依赖难以猜测的 ID 保护数据,阅读官方安全指南,订阅相关博客和邮件列表 |
| 性能测量 | 使用日志分析、基准测试工具,关注每个请求的实际耗时 |
| 压力测试 | 使用 httperf 、 ab 等工具模拟高负载请求 |
| 商业监控 | 选择 New Relic、FiveRuns、Scout 等服务监控应用性能 |
| 缓存 | 使用页面缓存、动作缓存和片段缓存,合理设置缓存过期策略 |
| 应用级优化 | 简化路由、优化数据库查询、合理使用计数器缓存等 |
| 性能监控与调优 | 建立监控流程,及时发现和解决性能问题 |
超级会员免费看
3

被折叠的 条评论
为什么被折叠?



