運維告訴我CPU飆升300%,為什么我的程序上線就奔潰了
本文轉載自微信公眾號「六脈神劍的程序人生」,作者六脈神劍小六六。轉載本文請聯系六脈神劍的程序人生公眾號。
前言
文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/bin392328206/six-finger 種一棵樹最好的時間是十年前,其次是現在
大家好,我是小六六,目前在一線互聯網公司負責年營收過百億的支付中臺項目,感謝大家的支持,今天我們來看看 線上服務CPU飆升的問題
絮叨
- 功能開發完成僅僅是項目周期中的第一步,一個完美的項目是在運行期體現的
- 今天我們就來看看筆者之前遇到的一個問題CPU飆升的問題。代碼層面從功能上看沒有任何問題但是投入使用后卻讓我頭大
問題描述
系統上點擊數據錄入功能在全局監控中會收到相關消息的通知。此時服務器CPU飆升300%
問題定位
- 首先我們先梳理下Websocket的數據發送的簡單原理示意圖。往往定位問題得清楚我們的邏輯是什么
- 當一個客戶端啟動時除了和Websocket建立連接之外,我們還需要向Websocket服務注冊當前客戶端需要哪些接口的實時數據
- 我在代碼內部是通過一個Map來存儲這些接口簽名信息的。然后客戶注冊時候將這些接口和客戶端綁定在一起
- 當我們監聽程序堅挺到數據變動就會對綁定到相關接口的客戶端發送最新數據
業務定位
業務上很好定位,問題就是出現在我們的監聽程序中。當監聽到數據給websocket客戶端發送訂閱的最新變動接口時就會出現CPU飆升。持續時間還很長,稍等一會就會降下來
這很明顯是我們推送消息的時候出現了問題
隔離業務看本質
- 作為一個合格的程序員呢,必須擺脫業務才能有所收獲 。業務是我們代碼的外殼所有的問題基本上都是我們本質的問題。我們線上使用用戶1W內。在這種的并發場景下應該是不會出問題的。現在出了問題肯定我們的程序邏輯有缺陷
- 上面是我們的發送消息的代碼。代碼也很簡單。先獲取所有符合發送條件的客戶端 。然后通過客戶端內部提供的sendMessage方法進行推送。
- 但是這個時候的message 是我們的接口信息。在內部會基于客戶端保存的方法簽名進行反射調用從而獲取最新數據。在推送給客戶端的
- 在上面的代碼中核心的是WebsocketManager.messageParse 。這段是獲取消息然后發送。里面獲取消息是基于resultful格式解析的
- 這個方法內部我們有內置了我們的四種解析方式。這里我們只需要關心RequestMappingMessageParseHandlerImpl 這個協議。
- 關于我們內部的協議這里也不需要太在意。這是我們自己的一個設計。根據上面的圖示我們也能看的出來里面RequestMappingMessageParseHandlerImpl 是核心
產生原因
- 上面我們簡單的梳理了下代碼的邏輯。
- 仔細分析下我們是遍歷所有客戶端然后在反射調用接口數據進行返回的。實際上在消息推送時我們沒必要在每個客戶端內部調用數據。我們完全可以先調用數據然后在遍歷客戶端進行發送。
- 這也是導致CPU過高的問題。我們1W個用戶同事在線的可能有5000+ 。那么我們需要5000次以上的反射著肯定是吃不消的。這也是為什么本文開頭說功能正常不代表業務正常。
解決方案
- 這就是量變引起質變。在多客戶的情況下我們的設計弊端就暴露出來。這里也是筆者自己給自己挖坑。既然找到問題我們就好解決了。下面我們對代碼做了一下改動
- 我將數據緩存起來。因為在同一批次推送時本來也應該保證數據一致性。而且我們系統對數據實時性也是可以接受一定時間延遲的。我在這里又加上緩存這樣就解決了我們循環的問題
- 經過測試本次改動在CPU上大概優化了100倍。
總結
- 功能開發完成僅僅代表功能的實驗沒有問題
- 單用戶和多用戶完全是兩種不同的用戶形態。我們功能設計初期就應該盡量考慮數據量的問題
- 唯一做的好的地方是我通過責任鏈模式將數據解析隔離出來。否則這樣的問題定位將會更加麻煩