前言
上一篇《.NET-記一次架構最佳化實戰與方案-梳理篇》整理了基本的業務知識,同時也羅列了存在的問題,本篇主要是針對任務串列的頁面進行效能最佳化。
該篇主要涉及的是程式碼實現上的最佳化,實現上的問題是戰術債務,也就是我們平常出現的各種BUG,這種問題一齣直接影響業務運營與系統運作。
你永遠想象不到同一條SQL相差個3.5秒鐘,遍歷兩次就導致了 3.5秒*2次 = 7秒的耗時。具體請看下文。
二八原則
有接觸過效能問題的朋友應該都瞭解過,一般效能瓶頸都是在某行程式碼或者某個方法,而不是整一個程式碼實現流程。
例如:遍歷計算、沒使用到索引的SQL陳述句、多餘重覆的介面請求等等。
以二八原則的思想來考慮,80%效能耗時由20%的程式碼引起,因此我們處理原則就是具體定位,具體問題,針對解決。
現象描述
任務串列頁面問題主要體現於載入任務串列過慢的效能低效問題,就如上一篇所說的載入事件需要11秒!這種對於使用者來說是不能忍受的,特別是以現狀JOB觸發的方式時效如此低,使用者多看兩次,估計就會有放棄該產品的衝動。
因此我們需要遵守3秒鐘原則。
3秒鐘原則
現代人的生活節奏都很快,網頁間的切換速度也越來越快。所謂“3秒鐘原則”,就是要在極短的時間內展示重要資訊,給使用者留下深刻的第一印象。當然,這裡的3秒只是一個象徵意義上的快速瀏覽表述,在實際瀏覽網頁的時候,並非真的嚴格遵守3秒。
因此,在設計網際網路產品的頁面時,使用者等待時間越少,使用者體驗越好
最佳化實施
任務串列頁面為以資訊展示的讀操作為主,因此對於 I/O 密集型程式,問題主要體現於兩點:
- 慢查詢陳述句
- 多次建立查詢
多次建立查詢
該問題主要從程式碼實現方式上解決,場景又分為兩種情況:
資訊重覆查詢
描述:函式 A 查詢了一次 Users 資訊,其函式 A 的子函式 B 又進行了一次查詢了一次Users 資訊。
解決方案:去除子函式 B 的重覆查詢,並提供引數由函式 A 傳入
遍歷查詢
描述:item.foreach(item=> _userIdRespository.Get(a=>userId == item.userId) )
解決方案:先批次查詢,然後在記憶體過濾。
var userIds = item.Select(a=>a.UserId);
var users = _userIdRespository.ToList(a=>userIds .Contains(a.userId));
Item.foreach(item=>{
Var user = users .where(a=>a.userId == item.userId)
})
以上並不是什麼特別牛逼的技術,但是往往是某些地方效能瓶頸點,而導致這樣的原因也只有一點,貪方便。上遍歷查詢的例子看出,兩種寫法的程式碼量的確差了幾行,但是在實際使用場景中效能會差幾倍,而且隨著業務的增長其差距越發的明顯。
慢查詢陳述句
對可能出現慢查詢的陳述句的進行日誌埋點記錄耗時(特別是手寫 SQL 與複雜檢視),定位後可與專業人士溝通最佳化,我們有DBA,因此我只要把問題定位到就好了。
下麵展示一個我在最佳化時候遇到一個的情況:
最佳化前是查詢一個複雜檢視,因為查詢沒用到索引,單次查詢了3.5秒,在生產環境還有遍歷2次的情況,一個7秒。
最佳化後將檢視改成儲存過程,並透過業務瞭解到一個使用者只會查詢出一條記錄,重覆查的情況,耗時直接降到120+毫秒
最佳化經歷
我剛完成這個需求二期上線,就收到載入慢的訊息,整個最佳化過程並非一步到位的,主要分了三步:
第一步,能立刻可預見的,比較低階的優化了,並將串列載入改成非同步,因為需求已經上線了,要先唬住使用者。
第二步,把多次建立查詢和部分已經在測試環境很慢的陳述句。最佳化完了之後發到了生產,快了2秒多,但是仍然不理想
第三步,給所有有可能查詢慢的地方都寫上日誌,後來定位到了好幾個慢查詢,其中上面是罪魁禍首。
釋出上線後,從原來的11秒耗時,降到1秒到2秒,細心的朋友會看見,在載入串列有一段UpdateUserTaskStatus的程式碼,這個是在讀頁面做更新操作,具體原因與分析放到下一篇進行講解
結尾
本篇主要講解了我在最佳化頁面載入效能過程中一些經歷,當然想最佳化到極致還有更多做法,例如徹底的前後端分離,讀快取等等。
然而我們需要在投入與產出比之間做出權衡,以有限的成本,使用樸實的技術,達到有效的標的效果。