如何提升 RailS 应用的性能?

提升 RAILS 应用性能的一些建议
提升 RAILS 应用性能的一些建议

Is rails slow?

「铁路很慢」,你也许听过这个笑话,那么我们的 Rails 框架呢?
如果说 Rails 慢,那么如何提升 Rails APP 的性能就成了开发者们最关注的问题。

也许你听说过很多提升 RoR APP 性能的方法,它们有难有易,我们需要在选择其中最能帮助开发者脱离性能困境的。

这里列举了几种不同的提升 Rails 应用性能的方法。

1. 数据库索引

你的 APP 被 DB 性能限制,优秀的数据库索引可以在大型数据库表中带给你100倍的性能提升。然而并非所有 Rails 开发者都明白这一点有多重要。

添加 indexes 很容易:

class AddIndexToClientIndustry < ActiveRecord::Migration
    def change
        add_index :client_industries, :client_id
    end
end

接下来就有无 Index 的情况做个对比。

有 Index 的情况:

CREATE INDEX 
addresses_addressable_id_addressable_type_idx  ON
addresses  USING btree  (addressable_id,addressable_type);
t1 = Time.now
 c = Company.find(178389)
 a = c.addresses.first
t2 = Time.now
puts "---Operation took #{t2-t1} seconds---”

Result with index:
---Operation took 0.012412 seconds---

没有 Index 的情况:

DROP INDEX
addresses_addressable_id_addressable_type_idx;

t1 = Time.now
 c = Company.find(178389)
 a = c.addresses.first
t2 = Time.now
puts "---Operation took #{t2-t1} seconds---”

Result without index:
---Operation took 0.378073 seconds---

0.378073 / 0.012412 = 30.46 没有索引比有索引慢了30.46秒。

因此工程师可以在所有引用参数,或者其他经常查询的参数中加入 Indexes。但是不能加太多, 因为每一个都会增加 DB Size 从而影响性能。

2. 数据库查询数量

RoR让编程更快捷,但反过来也让每条请求的数据库查询次数难以控制。举个例子,如果每一个 Client 有一或多个 Industries。 我们想要显示 Client List 和它们的 Primary Industries:

<% @clients.each do |client| %>
  <tr>
      <td><%= client.id %></td>
      <td><%= client.business_name %></td>
      <td><%= client.industries.first.name %></td>
  </tr>
<% end %>

# app/controllers/clients_controller.rb
def index
    @clients = Client.all
end

如果有50个 Clients, 则会有51条数据库查询:

    Processing by ClientsController#index as HTML
    SELECT "clients".* FROM "clients" 
    SELECT "industries".* FROM "industries" INNER JOIN "client_industries" ON "industries"."id" = "client_industries"."industry_id" WHERE "client_industries"."client_id" = 1 LIMIT 1
    SELECT "industries".* FROM "industries" INNER JOIN "client_industries" ON "industries"."id" = "client_industries"."industry_id" WHERE "client_industries"."client_id" = 2 LIMIT 1
    SELECT "industries".* FROM "industries" INNER JOIN "client_industries" ON "industries"."id" = "client_industries"."industry_id" WHERE "client_industries"."client_id" = 3 LIMIT 1
    …

解决方案: Eager Loading

# app/controllers/clients_controller.rb
def index
    @clients = Client.includes(:industries).all
end

现在只有2至3条数据库查询而非51条:

    Processing by ClientsController#index as HTML
    SELECT "clients".* FROM "clients" 
    SELECT "client_industries".* FROM
    "client_industries" WHERE
    "client_industries"."client_id" IN (1, 2, 3)
    SELECT "industries".* FROM "industries" WHERE "industries"."id" IN (1, 5, 7, 8, 4)

3. 减少内存占用

  • 只用真正需要的gem
  • 使用时再加载对象
  • 分批处理海量数据。

一个使用真实数据的例子——find_each:

Using find:
t1 = Time.now  
Company.where(:country_id=>1).find do |c|
puts "do something!" if ['Mattski Test'].include?(c.common_name)
end
t2 = Time.now
puts "---Operation took #{t2-t1} seconds---”
Result:
1 query, taking 46.65 seconds
Now using find_each:
t1 = Time.now  
Company.where(:country_id=>1).find_each do |c|
    puts "do something!" if ['Mattski Test'].include?(c.common_name)
end  
t2 = Time.now
puts "---Operation took #{t2-t1} seconds---"
Result:
100 queries, taking 15.53 seconds in total (3x faster)

也有查询多了反而快的情况。

4. 使用缓存

缓存的使用对性能有巨大影响,首先确保数据模型正确,缓存可以帮你隐藏结构问题。

  • 对象缓存
    在使用对象缓存的情况下,应该把查询方法的 include 去掉,避免关联查询无法利用缓存的现象。

  • 查询缓存
    在不要求实时的情况下,对于统计类耗时查询,那么可以使用 memcache-client 将查询结果缓存到 memcached 里。

  • 页面局部缓存
    对象缓存和查询缓存都会降低数据库访问负载,但如果 RoR 的负载很高,就只能依靠页面局部缓存了。

「web2.0网站比较常用使用页面局部缓存,Rails 的页面局部缓存有一个缺点,就是和页面查询结果对应的 Action 当中的查询语句要放在 View 里面,否则每次 Action 里面的查询还是会被执行,但是这样做会破坏程序代码良好的 MVC 结构。这种情况下,也可以采用另外一个 Cache 插件: better rails caching,在缓存页面的同时可以缓存 Action 当中的查询语句。」

5. 让 web 请求更快

只有少量可用进程用于服务 web 请求,因此需要使 web 请求更快。理想情况下, web 进程一般在毫秒内完成,1至2秒算是慢的,10秒以上是非常慢的。如果你的 web 请求很慢,你的Rails APP 将无法支撑同一时间的大量用户。

解决方案:使用后台运行
对长时间运行的项目使用后台运行诸如 delayed jobs, 从而释放你的 web 进程来解决更多请求。

6. 性能监控

对 APP 进行性能监控从而便于发现哪部分运行的慢,甚至快速定位到问题所在,可以利用国内应用性能监控做的最好的 OneAPM 监控工具。

OneAPM for Ruby 能够深入到所有 Ruby 应用内部完成应用性能管理和监控,包括代码级别性能问题的可见性、性能瓶颈的快速识别与追溯、真实用户体验监控、服务器监控和端到端的应用性能管理。 追溯性能瓶颈至:性能表现差的 SQL 语句、第三方 API、Web Services、Caching Layers、后台任务等。

alt text
alt text

图为使用 OneAPM 进行监控的总览页面,在这里可以对请求在服务器端耗时有个初步印象。可以直观的看到不同时间 web 事物、后台任务、数据库和外部服务的平响应时间、吞吐量、执行次数等指标,图中 web 事物在15:41的时候响应时间出现峰值,响应速度较慢。

alt text
alt text

为了进一步确定问题所在,点进 web 事物界面可以进一步了解各慢事物响应时间占比,快速定位到 api/medicines/index 的响应时间较长。

alt
alt

点击错误的请求地址,将会列出该错误的 URL、第一次和最后一次发生时间、错误发生次数、监测到错误的 Agent 名称、错误信息和堆栈信息。

好的应用性能监控往往需要花大量的时间和精力实现,因此选择优秀的第三方监控工具将极大地提高运维效率,这对提升 Rails APP 性能有极大帮助。

7. 使用内存数据库

当查询和排序都在内存中完成,数据库将会运行的更快,而它们需要在磁盘上运行的时候就变得很慢。

解决方案:

  • 限制 DB 的大小,保证它完全适合内存。
  • 将不紧急的信息移出主要数据库,移入次要数据库或其他地方。
  • 如果有大量存储需求,考虑使用非关系型数据库。

8. 更多性能建议:

  • 对静态文件使用内容分发网络,例如使用 AWS CloudFront。
  • 对需要1-2秒的加载项使用延迟加载。
  • 使用服务导向架构,使一些进程在托管栈同步进行。

相信选择一种或几种适合的性能提升方法,可以使 RoR APP 更令用户满意。

备注:本文参考并翻译了 Matt Kuklinski 在 slideshare 上关于 提升 Rails 性能所分享的部分内容。

本文系 OneAPM 工程师编译整理。OneAPM 是应用性能管理领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和 SQL 语句的实时抓取。想阅读更多技术文章,请访问 OneAPM 官方博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容