一次效能優化的總結
以前在傳統公司給企業開發內部使用的系統,面對的使用者量也就幾萬到幾十萬的量級,能達到百萬級別就已經是很厲害了,而併發量就更少了。還記得有次去面試,說我做過的一個南方電網的專案,併發量要求 2000,面試官還以為我說的是 2000萬 呢,可能他理解成了使用者量了。
現在做的面向企業級的公有云專案,使用者量也有幾百萬了。在我們專案中有個功能是給所有使用者發系統通知的,而問題就出在了給使用者推送通知的流程上。從訊息發出,到所有推送完成,總共花費了將近一天,可是通知的內容是要告知使用者,當天晚上有系統維護,可能會造成短時間的不穩定,可想而知,這麼遲來的通知,完全失去了意義,還給使用者帶來了困惑。因此,這兩天我專門針對這個問題,重新 review 了程式碼邏輯,爭取把這問題修復了,畢竟自己挖的坑,還得自己埋好。
其實邏輯很簡單,選擇要通知的組織,查出組織裡的所有使用者,批量給使用者推送。我舊的實現邏輯是,先找出要通知的組織,然後遍歷組織,針對每個組織查詢一次資料庫,然後呼叫推送介面。由於這個功能是在我們的系統才有幾千使用者的時候上線的,剛開始的時候執行良好。慢慢地,隨著我們系統使用者的爆發增長,直到最近再次使用這個功能時,才發現存在上述問題。
首先分析下舊邏輯存在的問題:1、沒有多執行緒操作,所有邏輯都在一個執行緒裡完成;2、一個組織查詢一次資料庫,多個組織查詢多次,而這正是本次問題的關鍵所在。於是,針對問題逐個處理,採取了以下的優化方式:1、把所有要推送的組織列表進行切分,比如切分為每組 1000 個組織,用一次查詢把這 1000 個組織對應的使用者列表查詢出來,然後用一個 Map 儲存下來;2、開多個執行緒執行任務,把上一步的操作當作一個任務放到多執行緒中執行,然後再新增任務處理對單個組織推送,每個任務處理一個組織,把上一步查詢到的使用者列表作為引數傳進任務裡,實現非同步排隊處理。經過這兩步優化後,在只有幾萬使用者的測試環境下對比了優化前後的時間消耗,居然就有 10 倍以上的效能提升。
好的架構是不斷演化出來的,而程式員的能力也是隨著架構的演化而得到提高。當資料量越來越大,系統架構越來越複雜的時候,新的問題也會不斷湧現。最近我正好在《極客時間》訂閱了《資料結構與演算法之美》的專欄,其中開頭兩節課就詳細介紹了怎麼分析時間複雜度和空間複雜度,掌握了複雜度的分析,自然就能在遇到效能問題時抓住重點,找到解決方案。同時,也很感謝公眾號《架構師之路》,最近幾遍演算法文章也讓我受益良多。
再提一下,今天寫程式碼時對 jdk 8 函數語言程式設計有了一點認識,把一個 Function 物件傳入一個模板程式碼裡:把一些煩瑣的程式碼比如異常處理,資源的請求和釋放等邏輯放到模板程式碼裡,然後把會變動的業務邏輯定義在 Function 物件裡,這樣使用起來竟然讓我有一種爽的感覺。而之前同事實現同樣的功能,是定義一個抽象類,每次都 new 一個匿名類物件並在裡面覆蓋其中的抽象方法,看著就很醜陋啊有沒有。看來得儘早擁抱 jdk 的新特性了,jdk 9 的模組化, jdk 10 的 var 變數,以及最近釋出的 jdk 11 ZGC。。。 感覺要學不過來了!!