如何提升 RailS 應用的性能?

dmc3 9年前發布 | 37K 次閱讀 Rails Ruby開發

提升 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

圖為使用 OneAPM 進行監控的總覽頁面,在這里可以對請求在服務器端耗時有個初步印象。可以直觀的看到不同時間 web 事物、后臺任務、數據庫和外部服務的平響應時間、吞吐量、執行次數等指標,圖中 web 事物在15:41的時候響應時間出現峰值,響應速度較慢。

alt text

為了進一步確定問題所在,點進 web 事物界面可以進一步了解各慢事物響應時間占比,快速定位到api/medicines/index的響應時間較長。

alt

點擊錯誤的請求地址,將會列出該錯誤的 URL、第一次和最后一次發生時間、錯誤發生次數、監測到錯誤的 Agent 名稱、錯誤信息和堆棧信息。

好的應用性能監控往往需要花大量的時間和精力實現,因此選擇優秀的第三方監控工具將極大地提高運維效率,這對提升 Rails APP 性能有極大幫助。

7. 使用內存數據庫

當查詢和排序都在內存中完成,數據庫將會運行的更快,而它們需要在磁盤上運行的時候就變得很慢。

解決方案:

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

8. 更多性能建議:

  • 對靜態文件使用內容分發網絡,例如使用 AWS CloudFront。
  • 對需要1-2秒的加載項使用延遲加載。
  • 使用服務導向架構,使一些進程在托管棧同步進行。

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

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

本文系 OneAPM 工程師編譯整理。OneAPM 是應用性能管理領域的新興領軍企業,能幫助企業用戶和開發者輕松實現:緩慢的程序代碼和 SQL 語句的實時抓取。想閱讀更多技術文章,請訪問 OneAPM 官方博客

 本文由用戶 dmc3 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!