Rails應用效能優化一例

2021-07-10 22:08:36 字數 4349 閱讀 8137

rails是一款經典的web開發框架,它的許多設計思想為使用者帶來了很多開發上的便利。它非常適合於快速搭建web應用程式,修改和維護的便利性使它受到許多創業型公司的青睞。然而過度地依賴其所帶來的便利而不加節制地使用,往往會隨著程式複雜度的增加,出現效能上的降低。對於大多數創業型公司的初級產品來說,還不值得動用複雜快取機制或者遷移資料庫來進行優化,此時針對rails應用本身的一些低效**進行優化,往往就能取得不錯的效果。

下面舉乙個實際中遇到的效能優化案例。專案背景是乙個小型電商系統,有許多種類的商品在站點上售賣,但是使用了一段時間後隨著商品種類增多,發現頁面載入緩慢,使用者體驗變差。

瀏覽器的開發工具,如chrome自帶的developer tools

壓力測試工具:apache ab

通過這些工具可以直觀地看出頁面的平均響應時間,幫助你分析確定效能瓶頸。例如new relic可以分析出乙個完整的請求響應過程內,資料庫查詢、應用處理以及頁面渲染等各個步驟各占用多長時間。還可以列舉出最耗時的是哪些請求。

通過分析發現,頁面的響應時間隨著吞吐量增加而顯著增加,而且主要耗費在應用處理過程中,而其他的資料庫查詢等耗時並沒有顯著增加。所以可以著手重點優化後台**。

找出最耗時的乙個請求進行優化,在本例中這個請求是乙個用於獲取商品分類資訊的介面。商品類別分為兩級,權且稱為「大類」和「小類」,每乙個大類下可以包含多個小類,但乙個小類只能歸屬於乙個大類。使用乙個名為category的model表示乙個分類,categories表中的parent字段用於表示小類記錄中的父類id。

之前該介面的**基本是這樣的:

class

categoriescontroller

defindex

categories = category.parent_categories.sale_in.for_city(city_id).order('priority desc')

render json: categories.map

endend

基本邏輯是先查詢出符合要求的categories,然後使用categorypresenter這樣乙個表示器,將資料轉化為json形式。具體用法參見roar這個gem包的說明。

categorypresenter的**是這樣的:

class

categorypresenter

< roar::decorator

include

roar::json

property :id

property :name

property :oss_url

collection :children_categories, as:

'children', extend:

categorypresenter

end

其中用到了children_categories方法,這一方法用於查詢該類別下的子類,在category類中是這樣定義的:

def

children_categories

children = category.where(parent:

self.id, status:

category.statuses[:sale_in]).order('priority desc')

children.presence || nil

end

將這幾部分的**聯絡起來仔細分析下,並結合後台日誌裡輸出的sql查詢語句,就會發現存在一些不必要的冗餘邏輯。categorypresenter這個表示器類中遞迴呼叫了自己,來對子分類children_categories迭代進行表示,直到子分類不再具有下一級子分類為止。然而,從業務上已知最多只有「大類」和「小類」兩層分類,每個小類不會存在更低一層的子分類,所以實際上迭代結束的出口處,對每個小類再做一遍查詢子分類的操作,是多餘的。

那麼實際上可以將categorypresenter根據大類和小類的不同用途拆分為不同的兩個表示器parentcategorypresentercategorypresenter,分別用於對大類和小類進行表示。這樣一來對於小類,就退化為乙個最簡形式的表示器,而不用再去迭代查詢子分類。

class

categorypresenter

< roar::decorator

include

roar::json

property :id

property :name

property :oss_url

endclass

parentcategorypresenter

< categorypresenter

collection :children_categories, as:

'children', extend:

categorypresenter

end

再觀察一下執行過程中產生的sql語句,可以發現有多次查詢存在,原因是原來的控制器**categoriescontroller中存在乙個迴圈,會對查出來的每個大類再分別進行一次查詢子分類的操作,是乙個典型的n+1次查詢:

categories = category.parent_categories.sale_in.for_city(city_id).order('priority desc')

render json: categories.map

解決的辦法是利用rails active record中提供的includes方法,將n+1次查詢變為一次查詢:

categories = category.parent_categories.sale_in.for_city(city_id).includes(:children_categories).order('priority desc')

render json: categories.map

要知道每一步優化有沒有起到效果,最好能夠快速得到反饋結果。如果使用new relic這樣的**效能監測工具去獲取反饋,需要比較長的週期。那麼本地開發時就可以使用ab這樣的壓測工具,來對被優化的介面快速進行一次效能測試。

在相同的測試條件下,優化前的測試結果是每次請求響應時間平均為54ms:

concurrency level:      10

time taken for tests: 5.427 seconds

complete requests: 100

failed requests: 0

total transferred: 238500 bytes

html transferred: 192900 bytes

requests per second: 18.43 [#/sec] (mean)

time per request: 542.674 [ms] (mean)

time per request: 54.267 [ms] (mean, across all concurrent requests)

transfer rate: 42.92 [kbytes/sec] received

優化後的測試結果是每次請求響應時間平均為22ms:

concurrency level:      10

time taken for tests: 2.256 seconds

complete requests: 100

failed requests: 0

total transferred: 238500 bytes

html transferred: 192900 bytes

requests per second: 44.34 [#/sec] (mean)

time per request: 225.552 [ms] (mean)

time per request: 22.555 [ms] (mean, across all concurrent requests)

transfer rate: 103.26 [kbytes/sec] received

從平均響應時間來看,效能得到了明顯提公升,已經足夠滿足當前需求。當然本例中只是針對rails部分**進行的優化,相信從其它角度入手,還有值得優化的空間。

Android應用效能優化

記憶體,ui,電量 1.記憶體 首先簡單介紹一下android系統記憶體管理機制.記憶體共享 預設情況 string vmheapsize systemproperties.get dalvik.vm.heapsize 16m 只有16m.可以通過在device.mk檔案中設定 product pr...

Android應用效能優化

1 anr 2 listview 卡頓,不流暢 3 activity啟動慢 4 動畫不流暢,啟動前卡頓 5 自定義view啟動慢 6 oom 7 資料庫大量操作 8 長時間執行後,程式變慢 1 語言層解決問題,語法上提高效能 2 合理的資料結構和演算法 3 布局優化,布局深度控制 4 工作執行緒與u...

Web應用效能優化思路

瓶頸是什麼?一條4車道的公路,執行非常順暢,突然出了點事故,事故車導致某個地方只剩下1車道,然後就開始堵車,因為四輛車同時塞向乙個車道裡。把這個事故清除了,故障車拖走了,道路會開始恢復了通暢。這個道理誰都懂,但偏偏有些傻瓜交警去把4車道變成8車道,但卻不清理事故路段。乙個web應用,不管是何種語言開...