32、Ruby on Rails 应用上线的安全、性能与优化指南

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 等服务监控应用性能
缓存 使用页面缓存、动作缓存和片段缓存,合理设置缓存过期策略
应用级优化 简化路由、优化数据库查询、合理使用计数器缓存等
性能监控与调优 建立监控流程,及时发现和解决性能问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值