線上服務CPU飆升
前言
- 功能開發完成僅僅是專案週期中的第一步,一個完美的專案是在執行期體現的
- 今天我們就來看看筆者之前遇到的一個問題CPU飆升的問題。 程式碼層面從功能上看沒有任何問題但是投入使用後卻讓我頭大
問題描述
- 系統上點選資料錄入功能在全域性監控中會受到相關訊息的通知。此時伺服器CPU飆升300%
問題定位
- 首先我們先梳理下
Websocket
的資料傳送的簡單原理示意圖。往往定位問題得清楚我們的邏輯是什麼 - 當一個客戶端啟動時除了和
Websocket
建立連線之外,我們還需要向Websocket
服務註冊當前客戶端需要哪些介面的實時資料 - 我在程式碼內部是通過一個Map來儲存這些介面簽名資訊的。然後客戶註冊時候將這些介面和客戶端繫結在一起
- 當我們監聽程式堅挺到資料變動就會對繫結到相關介面的客戶端傳送最新資料
業務定位
- 業務上很好定位,問題就是出現在我們的監聽程式中。當監聽到資料給
websocket
客戶端傳送訂閱的最新變動介面時就會出現CPU飆升。持續時間還很長,稍等一會就會降下來 - 這很明顯是我們推送訊息的時候出現了問題
隔離業務看本質
- 作為一個合格的程式設計師呢,必須擺脫業務才能有所收穫 。業務是我們程式碼的外殼所有的問題基本上都是我們本質的問題。我們線上使用使用者1W內。在這種的併發場景下應該是不會出問題的。現在出了問題肯定我們的程式邏輯有缺陷
- 上面是我們的傳送訊息的程式碼。程式碼也很簡單。先獲取所有符合傳送條件的客戶端 。然後通過客戶端內部提供的
sendMessage
方法進行推送。 - 但是這個時候的
message
是我們的介面資訊。在內部會基於客戶端儲存的方法簽名進行反射呼叫從而獲取最新資料。在推送給客戶端的 - 在上面的程式碼中核心的是
WebsocketManager.messageParse
。這段是獲取訊息然後傳送。裡面獲取訊息是基於resultful格式解析的
- 這個方法內部我們有內建了我們的四種解析方式。這裡我們只需要關心
RequestMappingMessageParseHandlerImpl
這個協議。
- 關於我們內部的協議這裡也不需要太在意。這是我們自己的一個設計。根據上面的圖示我們也能看的出來裡面
RequestMappingMessageParseHandlerImpl
是核心
產生原因
- 上面我們簡單的梳理了下程式碼的邏輯。
- 仔細分析下我們是遍歷所有客戶端然後在反射呼叫介面資料進行返回的。實際上在訊息推送時我們沒必要在每個客戶端內部呼叫資料。我們完全可以先呼叫資料然後在遍歷客戶端進行傳送。
- 這也是導致CPU過高的問題。我們1W個使用者同事線上的可能有5000+ 。 那麼我們需要5000次以上的反射著肯定是吃不消的。這也是為什麼本文開頭說功能正常不代表業務正常。
解決方案
- 這就是量變引起質變。在多客戶的情況下我們的設計弊端就暴露出來。這裡也是筆者自己給自己挖坑。既然找到問題我們就好解決了。下面我們對程式碼做了一下改動
-
我將資料快取起來。因為在同一批次推送時本來也應該保證資料一致性。而且我們系統對資料實時性也是可以接受一定時間延遲的。我在這裡又加上快取這樣就解決了我們迴圈的問題
-
經過測試本次改動在CPU上大概優化了100倍。
總結
- 功能開發完成僅僅代表功能的實驗沒有問題
- 單使用者和多使用者完全是兩種不同的使用者形態。我們功能設計初期就應該儘量考慮資料量的問題
- 唯一做的好的地方是我通過責任鏈模式將資料解析隔離出來。否則這樣的問題定位將會更加麻煩