如何提升 RailS 應(yīng)用的性能?

提升 RAILS 應(yīng)用性能的一些建議
提升 RAILS 應(yīng)用性能的一些建議

Is rails slow?

「鐵路很慢」,你也許聽過這個(gè)笑話,那么我們的 Rails 框架呢?
如果說 Rails 慢,那么如何提升 Rails APP 的性能就成了開發(fā)者們最關(guān)注的問題。

也許你聽說過很多提升 RoR APP 性能的方法,它們有難有易,我們需要在選擇其中最能幫助開發(fā)者脫離性能困境的。

這里列舉了幾種不同的提升 Rails 應(yīng)用性能的方法。

1. 數(shù)據(jù)庫索引

你的 APP 被 DB 性能限制,優(yōu)秀的數(shù)據(jù)庫索引可以在大型數(shù)據(jù)庫表中帶給你100倍的性能提升。然而并非所有 Rails 開發(fā)者都明白這一點(diǎn)有多重要。

添加 indexes 很容易:

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

接下來就有無 Index 的情況做個(gè)對比。

有 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秒。

因此工程師可以在所有引用參數(shù),或者其他經(jīng)常查詢的參數(shù)中加入 Indexes。但是不能加太多, 因?yàn)槊恳粋€(gè)都會增加 DB Size 從而影響性能。

2. 數(shù)據(jù)庫查詢數(shù)量

RoR讓編程更快捷,但反過來也讓每條請求的數(shù)據(jù)庫查詢次數(shù)難以控制。舉個(gè)例子,如果每一個(gè) Client 有一或多個(gè) 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個(gè) Clients, 則會有51條數(shù)據(jù)庫查詢:

    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

現(xiàn)在只有2至3條數(shù)據(jù)庫查詢而非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. 減少內(nèi)存占用

  • 只用真正需要的gem
  • 使用時(shí)再加載對象
  • 分批處理海量數(shù)據(jù)。

一個(gè)使用真實(shí)數(shù)據(jù)的例子——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. 使用緩存

緩存的使用對性能有巨大影響,首先確保數(shù)據(jù)模型正確,緩存可以幫你隱藏結(jié)構(gòu)問題。

  • 對象緩存
    在使用對象緩存的情況下,應(yīng)該把查詢方法的 include 去掉,避免關(guān)聯(lián)查詢無法利用緩存的現(xiàn)象。

  • 查詢緩存
    在不要求實(shí)時(shí)的情況下,對于統(tǒng)計(jì)類耗時(shí)查詢,那么可以使用 memcache-client 將查詢結(jié)果緩存到 memcached 里。

  • 頁面局部緩存
    對象緩存和查詢緩存都會降低數(shù)據(jù)庫訪問負(fù)載,但如果 RoR 的負(fù)載很高,就只能依靠頁面局部緩存了。

「web2.0網(wǎng)站比較常用使用頁面局部緩存,Rails 的頁面局部緩存有一個(gè)缺點(diǎn),就是和頁面查詢結(jié)果對應(yīng)的 Action 當(dāng)中的查詢語句要放在 View 里面,否則每次 Action 里面的查詢還是會被執(zhí)行,但是這樣做會破壞程序代碼良好的 MVC 結(jié)構(gòu)。這種情況下,也可以采用另外一個(gè) Cache 插件: better rails caching,在緩存頁面的同時(shí)可以緩存 Action 當(dāng)中的查詢語句。」

5. 讓 web 請求更快

只有少量可用進(jìn)程用于服務(wù) web 請求,因此需要使 web 請求更快。理想情況下, web 進(jìn)程一般在毫秒內(nèi)完成,1至2秒算是慢的,10秒以上是非常慢的。如果你的 web 請求很慢,你的Rails APP 將無法支撐同一時(shí)間的大量用戶。

解決方案:使用后臺運(yùn)行
對長時(shí)間運(yùn)行的項(xiàng)目使用后臺運(yùn)行諸如 delayed jobs, 從而釋放你的 web 進(jìn)程來解決更多請求。

6. 性能監(jiān)控

對 APP 進(jìn)行性能監(jiān)控從而便于發(fā)現(xiàn)哪部分運(yùn)行的慢,甚至快速定位到問題所在,可以利用國內(nèi)應(yīng)用性能監(jiān)控做的最好的 OneAPM 監(jiān)控工具。

OneAPM for Ruby 能夠深入到所有 Ruby 應(yīng)用內(nèi)部完成應(yīng)用性能管理和監(jiān)控,包括代碼級別性能問題的可見性、性能瓶頸的快速識別與追溯、真實(shí)用戶體驗(yàn)監(jiān)控、服務(wù)器監(jiān)控和端到端的應(yīng)用性能管理。 追溯性能瓶頸至:性能表現(xiàn)差的 SQL 語句、第三方 API、Web Services、Caching Layers、后臺任務(wù)等。

alt text
alt text

圖為使用 OneAPM 進(jìn)行監(jiān)控的總覽頁面,在這里可以對請求在服務(wù)器端耗時(shí)有個(gè)初步印象。可以直觀的看到不同時(shí)間 web 事物、后臺任務(wù)、數(shù)據(jù)庫和外部服務(wù)的平響應(yīng)時(shí)間、吞吐量、執(zhí)行次數(shù)等指標(biāo),圖中 web 事物在15:41的時(shí)候響應(yīng)時(shí)間出現(xiàn)峰值,響應(yīng)速度較慢。

alt text
alt text

為了進(jìn)一步確定問題所在,點(diǎn)進(jìn) web 事物界面可以進(jìn)一步了解各慢事物響應(yīng)時(shí)間占比,快速定位到 api/medicines/index 的響應(yīng)時(shí)間較長。

alt
alt

點(diǎn)擊錯(cuò)誤的請求地址,將會列出該錯(cuò)誤的 URL、第一次和最后一次發(fā)生時(shí)間、錯(cuò)誤發(fā)生次數(shù)、監(jiān)測到錯(cuò)誤的 Agent 名稱、錯(cuò)誤信息和堆棧信息。

好的應(yīng)用性能監(jiān)控往往需要花大量的時(shí)間和精力實(shí)現(xiàn),因此選擇優(yōu)秀的第三方監(jiān)控工具將極大地提高運(yùn)維效率,這對提升 Rails APP 性能有極大幫助。

7. 使用內(nèi)存數(shù)據(jù)庫

當(dāng)查詢和排序都在內(nèi)存中完成,數(shù)據(jù)庫將會運(yùn)行的更快,而它們需要在磁盤上運(yùn)行的時(shí)候就變得很慢。

解決方案:

  • 限制 DB 的大小,保證它完全適合內(nèi)存。
  • 將不緊急的信息移出主要數(shù)據(jù)庫,移入次要數(shù)據(jù)庫或其他地方。
  • 如果有大量存儲需求,考慮使用非關(guān)系型數(shù)據(jù)庫。

8. 更多性能建議:

  • 對靜態(tài)文件使用內(nèi)容分發(fā)網(wǎng)絡(luò),例如使用 AWS CloudFront。
  • 對需要1-2秒的加載項(xiàng)使用延遲加載。
  • 使用服務(wù)導(dǎo)向架構(gòu),使一些進(jìn)程在托管棧同步進(jìn)行。

相信選擇一種或幾種適合的性能提升方法,可以使 RoR APP 更令用戶滿意。

備注:本文參考并翻譯了 Matt Kuklinski 在 slideshare 上關(guān)于 提升 Rails 性能所分享的部分內(nèi)容。

本文系 OneAPM 工程師編譯整理。OneAPM 是應(yīng)用性能管理領(lǐng)域的新興領(lǐng)軍企業(yè),能幫助企業(yè)用戶和開發(fā)者輕松實(shí)現(xiàn):緩慢的程序代碼和 SQL 語句的實(shí)時(shí)抓取。想閱讀更多技術(shù)文章,請?jiān)L問 OneAPM 官方博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容