效能優化知多少

發表於2017-07-05

效能優化知多少

1. 引言

最近一段時間,系統新版本要釋出,在beta客戶測試期間,暴露了很多問題,除了一些業務和異常問題外,其他都集中在效能上。有幸接觸到這些效能調優的機會,當然要學習總結了。

效能優化是一個老生常談的問題了,典型的效能問題如頁面響應慢、介面超時,伺服器負載高、併發數低,資料庫頻繁死鎖等。而造成效能問題又有很多種,比如磁碟I/O、記憶體、網路、演算法、大資料量等等。我們可以大致把效能問題分為四個層次:程式碼層次、資料庫層次、演算法層次、架構層次。
所以下面我會結合實際效能優化案例,和大家分享下效能調優的工具、方法和技巧。

2. 先說心態

說到效能問題,你可能首先就想到的是麻煩或者頭大,因為一般效能問題都比較緊急,輕則影響客戶體驗,重則當機導致財務損失,而且效能問題比較隱蔽,不易發現。因此一時間無從下手,而這時我們就很容易從心底開始去排斥它,不願接這燙手的山芋。

而恰巧,效能調優是體現程式設計師水平的一個重要指標。

因為處理bug、崩潰、調優、入侵等突發事件比程式設計本身更能體現平庸程式設計師與理想程式設計師的差距。當面對一個未知的問題時,如何定位複雜條件下的核心問題、如何抽絲剝繭地分析問題的潛在原因、如何排除干擾還原一個最小的可驗證場景、如何抓住關鍵資料驗證自己的猜測與實驗,都是體現程式設計師思考力的最好場景。是的,在衡量理想程式設計師的標準上,思考力比經驗更加重要。

所以,若你不甘平庸,請擁抱效能調優的每一個機會。當你擁有一個正確的心態,你所面對的效能問題就已經解決了一半。

3. 再說技巧

拿到一個效能問題,不要忙著先上工具,先了解問題出現的背景,問題的嚴重程度。然後大致根據自己的經驗積累作出預估。比如客戶來了個效能問題說系統當機了,已經造成資金損失了。這種涉及到錢的問題,大家都比較敏感,根據自己的level,決定是否要接這個鍋。這不是逃避,而是自知之明。

瞭解問題背景之後,下一步就來嘗試問題重現。如果在測試環境能夠重現,那這種問題就很好跟蹤分析。如果問題不能穩定重現或僅能在生產環境重現,那問題就相對比較棘手,這時要立刻收集現場證據,包括但不限於抓dump、收集應用程式以及系統日誌、關注CPU記憶體情況、資料庫備份等等,之後不妨再嘗試重現,比如恢復客戶資料庫到測試環境重現。

不管問題能否重現,下一步,我們就要大致對問題進行分類,是程式碼層次的業務邏輯問題還是資料庫層次的操作耗時問題,又或是系統架構的吞吐量問題。那如何確定呢?而我傾向於先從資料庫動手。我的習慣做法是,使用資料庫監控工具,先跟蹤下Sql耗時情況。如果監控到耗時較長的SQL語句,那基本上就是資料庫層次的問題,否則就是程式碼層次。若為程式碼層次,再研究完程式碼後,再細化為演算法或架構層次問題。

確定問題種類後,是時候上工具來精準定位問題點了:

精準定位問題點後,就是著手優化了。相信到這一步,就是優化策略的選擇了,這裡就不展開了。

優化後,最後當然要進行測試了,畢竟優化了多少,我們也要做到心裡有譜才行。

以上囉囉嗦嗦有點多,下面我們直接上案例。

4. 案例分享

下面就分享下我針對程式碼層面、資料庫層面和演算法層面的優化案例。

4.1. SQL優化案例

案例1:客戶反饋某結算報表統計十天內的資料耗時10mins左右。

由於前幾天剛學會用RedGate的分析工具,拿到這個問題,本地嘗試重現後,就直接想使用工具分析。然而,這工具在使用webdev模式起站點時,總是報錯,而當時時一根筋,老是想解決這個工具的報錯問題。結果,白白搞了半天也沒搞定。最後不得已放棄工具,轉而選擇使用sql server profiler去監控sql語句耗時。一跟蹤不要緊,問題就直接暴露了,整個全屏的重複sql語句,如下圖。

Sql Profiler監控結果

這下問題就很明顯了,八成是程式碼在迴圈拼接sql執行語句。根據抓取到sql關鍵字往程式碼中去搜尋,果然如此。

看到這段程式碼,我們先不評判這段程式碼的優劣,因為畢竟程式碼註釋清晰,省了我們理清業務的功夫。這段sql主要是想做去重處理,很顯然選用了錯誤的方案。改後程式碼如下:

改後測試相同資料量,耗時由10mins降到10s左右。

4.2. 程式碼優化案例

案例2:客戶反饋銷售訂單100條分錄行,儲存進行可髮量校驗時,耗時7mins左右。

拿到這個問題後,本地重現後,監控sql耗時沒有異常,那就著重分析程式碼了。因為可髮量校驗的業務邏輯極其複雜,又加上又直接再一個類檔案實現該功能,3500+行的程式碼,加上零星註釋,真是讓人避之不及。逃避不是辦法,還是上工具分析一把。
這次我選用的時VS自帶的Performance Profiler,開發環境下極其強大的效能調優工具。針對我們當前案例,我們僅需要跟蹤指定服務對應的dll即可,使用步驟如下:

  1. Analyze–>Profiler–>New Performance Session
  2. 開啟Performance Explorer
  3. 找到新新增的Performance Session,右鍵Targets,然後選擇Add Target Binary,新增要跟蹤的dll檔案即可
  4. 將應用跑起來
  5. 選中Performance Session,右鍵Attach對應程式即可跟蹤分析效能了
  6. 在跟蹤過程中,可隨時暫停跟蹤和停止跟蹤

圖示步驟

跟蹤結束後本案例跟蹤到的取樣結果如下圖:

VS Performance Profiler分析報告

同時Performance Profiler也給出了問題的建議,如下圖:
VS Performance Profiler分析提示

其中第1、4條大致說明程式I/O消耗大,第一代的GC上存在未及時釋放的垃圾佔比過高。而根據上圖的取樣結果,我們可以直接看出是由於再程式碼中頻繁操作DataTable引起的效能瓶頸。走讀程式碼發現的確如此,所有的數量統計都是在程式碼中迴圈遍歷DataTable進行處理的。而最終的優化策略,就相當於一次大的重構,將所有程式碼中通過遍歷DataTable的計算邏輯全部挪到SQL中去做。由於程式碼過多,就不再放出。

案例3:客戶反饋批量引入1000張訂單,耗時40mins左右,且容易中斷。

同樣,我們還是先嚐試本地重寫。經測試批量引入101張單據,就耗時5mins左右。下一步開啟Sql監控工具也未發現耗時語句。但考慮到是批量匯入操作,雖然單個耗時不多,但乘以100這個基數,就明顯了。下面我們就使用RedGate的Ants Performance Profiler跟蹤一下。

該工具比較直觀,可以同時監控程式碼和SQL執行情況。第一步,New Profiler Session,第二步進行設定,如下圖。根據自己的應用程式類別,選擇相應的跟蹤方式。

跟蹤設定

針對這個問題,我們跟蹤到的呼叫堆疊和SQL耗時結果如下圖:

呼叫堆疊監控結果

SQL監控結果

首先從呼叫堆疊中的Hit Count,我們可以首先看出它是一個批量過程,因為入口函式僅呼叫一次;第二個我們可以程式碼中是迴圈處理每一個單據,因為Hit Count與我們批量引入的單據數量相符;第三個,突然來了個10201,如果有一定的數字敏感性的話,這次效能問題的原因就被你找到了。這裡就不賣關子了,101 x 101 = 10201。
是不是明白了什麼,存在迴圈巢狀迴圈的情況。我們走讀程式碼確定一下:

好嘛,外層套了一個空迴圈卻什麼也沒做。修改就很簡單了,刪除無效外層迴圈即可。

4.3. 演算法優化案例

案例4:某全流程跟蹤報表超時。

這個報表是用來跟蹤所有單據從下單到出庫的業務流程資料流轉情況。而所有的流程資料都是按照樹形結果儲存在資料庫表中的,類似這樣:

流程樹表

圖中的流程為:
銷售合同–>銷售訂單–>發貨通知單–>銷售出庫單

為了構造流程圖,之前的處理方法是把流程資料取回來,通過程式碼構造流程圖。這也就是效能差的原因。

而針對這種情況,就是考驗我們平時經驗積累了。對於樹形結構的表,我們也是可以通過SQL來進行直接查詢的,這就要用到了SQL Server的CTE語法來進行遞迴查詢。關於遞迴查詢,可參考我這篇文章:SQL遞迴查詢知多少。這裡就不展開了。

5.總結

效能調優是一個循序漸進的過程,不可能一蹴而就,重在平時的點滴積累。關於工具的選擇和使用,本文並未展開,也希望讀者也不要糾結與此。當你真正想解決一個問題的時候,相信工具的使用是難不住你的。

最後就大致總結下我的調優思路:

  1. 調整心態,積極應對
  2. 瞭解效能背景, 收集證據, 嘗試重現
  3. 問題分類,先監控SQL耗時,大致確定是SQL或是程式碼層次原因
  4. 使用效能分析工具,確定問題點
  5. 調優測試

相關文章