線上服務(wù)CPU飆升的一次經(jīng)歷
前言
- 功能開發(fā)完成僅僅是項(xiàng)目周期中的第一步,一個(gè)完美的項(xiàng)目是在運(yùn)行期體現(xiàn)的
- 今天我們就來(lái)看看筆者之前遇到的一個(gè)問(wèn)題CPU飆升的問(wèn)題。 代碼層面從功能上看沒(méi)有任何問(wèn)題但是投入使用后卻讓我頭大
問(wèn)題描述
系統(tǒng)上點(diǎn)擊數(shù)據(jù)錄入功能在全局監(jiān)控中會(huì)受到相關(guān)消息的通知。此時(shí)服務(wù)器CPU飆升300%
問(wèn)題定位
- 首先我們先梳理下
Websocket的數(shù)據(jù)發(fā)送的簡(jiǎn)單原理示意圖。往往定位問(wèn)題得清楚我們的邏輯是什么 - 當(dāng)一個(gè)客戶端啟動(dòng)時(shí)除了和
Websocket建立連接之外,我們還需要向Websocket服務(wù)注冊(cè)當(dāng)前客戶端需要哪些接口的實(shí)時(shí)數(shù)據(jù) - 我在代碼內(nèi)部是通過(guò)一個(gè)Map來(lái)存儲(chǔ)這些接口簽名信息的。然后客戶注冊(cè)時(shí)候?qū)⑦@些接口和客戶端綁定在一起
- 當(dāng)我們監(jiān)聽程序堅(jiān)挺到數(shù)據(jù)變動(dòng)就會(huì)對(duì)綁定到相關(guān)接口的客戶端發(fā)送最新數(shù)據(jù)
業(yè)務(wù)定位
- 業(yè)務(wù)上很好定位,問(wèn)題就是出現(xiàn)在我們的監(jiān)聽程序中。當(dāng)監(jiān)聽到數(shù)據(jù)給
websocket客戶端發(fā)送訂閱的最新變動(dòng)接口時(shí)就會(huì)出現(xiàn)CPU飆升。持續(xù)時(shí)間還很長(zhǎng),稍等一會(huì)就會(huì)降下來(lái) - 這很明顯是我們推送消息的時(shí)候出現(xiàn)了問(wèn)題
隔離業(yè)務(wù)看本質(zhì)
- 作為一個(gè)合格的程序員呢,必須擺脫業(yè)務(wù)才能有所收獲 。業(yè)務(wù)是我們代碼的外殼所有的問(wèn)題基本上都是我們本質(zhì)的問(wèn)題。我們線上使用用戶1W內(nèi)。在這種的并發(fā)場(chǎng)景下應(yīng)該是不會(huì)出問(wèn)題的?,F(xiàn)在出了問(wèn)題肯定我們的程序邏輯有缺陷
- 上面是我們的發(fā)送消息的代碼。代碼也很簡(jiǎn)單。先獲取所有符合發(fā)送條件的客戶端 。然后通過(guò)客戶端內(nèi)部提供的
sendMessage方法進(jìn)行推送。 - 但是這個(gè)時(shí)候的
message是我們的接口信息。在內(nèi)部會(huì)基于客戶端保存的方法簽名進(jìn)行反射調(diào)用從而獲取最新數(shù)據(jù)。在推送給客戶端的 - 在上面的代碼中核心的是
WebsocketManager.messageParse。這段是獲取消息然后發(fā)送。里面獲取消息是基于resultful格式解析的
- 這個(gè)方法內(nèi)部我們有內(nèi)置了我們的四種解析方式。這里我們只需要關(guān)心
RequestMappingMessageParseHandlerImpl這個(gè)協(xié)議。
- 關(guān)于我們內(nèi)部的協(xié)議這里也不需要太在意。這是我們自己的一個(gè)設(shè)計(jì)。根據(jù)上面的圖示我們也能看的出來(lái)里面
RequestMappingMessageParseHandlerImpl是核心
產(chǎn)生原因
- 上面我們簡(jiǎn)單的梳理了下代碼的邏輯。
- 仔細(xì)分析下我們是遍歷所有客戶端然后在反射調(diào)用接口數(shù)據(jù)進(jìn)行返回的。實(shí)際上在消息推送時(shí)我們沒(méi)必要在每個(gè)客戶端內(nèi)部調(diào)用數(shù)據(jù)。我們完全可以先調(diào)用數(shù)據(jù)然后在遍歷客戶端進(jìn)行發(fā)送。
- 這也是導(dǎo)致CPU過(guò)高的問(wèn)題。我們1W個(gè)用戶同事在線的可能有5000+ 。 那么我們需要5000次以上的反射著肯定是吃不消的。這也是為什么本文開頭說(shuō)功能正常不代表業(yè)務(wù)正常。
解決方案
- 這就是量變引起質(zhì)變。在多客戶的情況下我們的設(shè)計(jì)弊端就暴露出來(lái)。這里也是筆者自己給自己挖坑。既然找到問(wèn)題我們就好解決了。下面我們對(duì)代碼做了一下改動(dòng)
我將數(shù)據(jù)緩存起來(lái)。因?yàn)樵谕慌瓮扑蜁r(shí)本來(lái)也應(yīng)該保證數(shù)據(jù)一致性。而且我們系統(tǒng)對(duì)數(shù)據(jù)實(shí)時(shí)性也是可以接受一定時(shí)間延遲的。我在這里又加上緩存這樣就解決了我們循環(huán)的問(wèn)題
經(jīng)過(guò)測(cè)試本次改動(dòng)在CPU上大概優(yōu)化了100倍。
總結(jié)
- 功能開發(fā)完成僅僅代表功能的實(shí)驗(yàn)沒(méi)有問(wèn)題
- 單用戶和多用戶完全是兩種不同的用戶形態(tài)。我們功能設(shè)計(jì)初期就應(yīng)該盡量考慮數(shù)據(jù)量的問(wèn)題
- 唯一做的好的地方是我通過(guò)責(zé)任鏈模式將數(shù)據(jù)解析隔離出來(lái)。否則這樣的問(wèn)題定位將會(huì)更加麻煩
作者: zxhtom
鏈接:https://juejin.cn/post/6960795554406367269
來(lái)源:juejin
有時(shí)候一個(gè)小小的問(wèn)題就會(huì)引起很大的波動(dòng)現(xiàn)象,那么處理起來(lái)也是非常的痛楚,只有找到合適的方法,這次筆者自己給自己挖坑,在這里做了一下記錄。特別是在高并發(fā)到的時(shí)候,小問(wèn)題會(huì)帶來(lái)更大的麻煩。
技術(shù)github學(xué)習(xí)地址:https://github.com/codeGoogler/JavaCodeHub
程序員編程書籍:https://github.com/codeGoogler/ProgramBooks
關(guān)于如何學(xué)習(xí)Java,一方面需要不斷的去學(xué)習(xí),把基礎(chǔ)知識(shí)學(xué)扎實(shí),另一方面也要認(rèn)識(shí)到j(luò)ava的學(xué)習(xí)不能僅僅靠理論,更多的是靠實(shí)操,所以要多練習(xí)多做項(xiàng)目,在實(shí)踐中學(xué)習(xí)才是最好的學(xué)習(xí)方法。很多人剛開始不知道怎么去學(xué)習(xí),這里我將一些重要的技術(shù)文章整理到了github上開源項(xiàng)目上,希望能給大家?guī)?lái)一些幫助,項(xiàng)目是:JavaCodeHub

另外,還整理了一些針對(duì)于程序員的編程書籍項(xiàng)目,都放到了github上面,項(xiàng)目為:ProgramBooks 需要的話可以自取。地址:https://github.com/codeGoogler/ProgramBooks
如果github訪問(wèn)太慢?我同時(shí)也把去放到了碼云上面ProgramBooks
最后,照舊安利一波我們的公號(hào):「終端研發(fā)部」,目前每天都會(huì)推薦一篇優(yōu)質(zhì)的技術(shù)相關(guān)的文章,主要分享java相關(guān)的技術(shù)與面試技巧,我們的目標(biāo)是: 知道是什么,為什么,打好基礎(chǔ),做好每一點(diǎn)!這個(gè)主創(chuàng)技術(shù)公眾號(hào)超級(jí)值得大家關(guān)注。