1. 程式人生 > 實用技巧 >意想不到,這個神奇的bug讓我加班到深夜

意想不到,這個神奇的bug讓我加班到深夜

給大家分享一個近期解決的線上問題,起因是這樣的,近期參與公司的一個專案,工程量很大,程式碼編寫測試過後終於到了緊張的上線時刻。

專案上線

上線前照例忐忑不安了一番,因為工程量比較大,預估可能不會很順利,但還不至於到了祈禱伺服器不要出bug的地步,bug對於程式設計師來說簡直是家常便飯,沒有bug反而可能會嘀咕半天,這都是職業病,沒治。

緊張了一會兒,我屏氣凝神,點了上線按鈕,那一刻簡直就像在點核按鈕一樣,生怕點下去後伺服器會轟的一聲炸掉。

圖片

結果一切正常。。。

這不對啊,這時博主的職業病又犯了,這麼大的改動不會這麼順利吧,記憶體、CPU、tp99耗時一切正常,這太不正常了吧。

說曹操曹操就到

就在博主想為什麼沒有bug時,bug簡直就像聽到了我的召喚一樣如約而至,博主當時甚至在想為什麼夢想中的500萬彩票就這麼不聽話呢?

(bug妹妹:“程式設計師哥哥,我來啦”;

500萬妹妹:“不,程式設計師哥哥,不要說門,窗戶都沒有。。。”)

上線最初一切正常,問題就出在了接下來的一段時間裡。

在接下來的時間裡,tp99耗時就像通貨膨脹一樣不可遏制的一路上揚,線上收入就像股市一樣不可遏制的一路重挫。

圖片

這時博主的內心反而踏實了很多,沒錯,就是這個味兒,還是熟悉的配方還是熟悉的味道,I know it。

廢話少說,趕緊回滾,線上恢復正常後接下來就是問題排查了。

排查問題

從監控上看,一次請求的處理時間會越來越高,那麼顯然問題的關鍵就是定位耗時出現在了哪段程式碼上。

沒有辦法,只能一點一點的去找監控了,幸好程式碼中監控比較豐富,一番梳理後最終鎖定在了這樣一行程式碼:

//監控程式碼
obja=b;
//監控程式碼

這段程式碼本質上是在幹什麼呢?很簡單,就是物件的copy,而且在我們的實現中還是淺copy,也就是僅僅copy了一些欄位。

從監控看就是這行程式碼導致了tp99耗時不斷上漲。

這。。這怎麼可能呢?簡簡單單的一個物件拷貝竟然會讓耗時上漲那麼多,而且還是隨著時間緩慢上漲,這也太神奇了吧。

圖片

當人遇到自己不能理解的問題時通常會歸因於外部因素,博主也不能免俗。

接下來就是懷疑人生的時刻。

不會是監控有問題吧,不會是編譯器的原因吧,不會是硬體的原因吧,不會是天氣的原因的吧,總之不是我的原因。

圖片

一番思索後最終理性戰勝了自己,哪有那麼多原因,在沒有其它證據下目前看就是這個物件拷貝導致的。

那麼為什麼一個簡單的物件拷貝會導致CPU消耗越來越高,耗時越漲越多呢?

這裡的關鍵在於意識到這一點,既然隨著時間的推移耗時會越來越高,那麼很顯然是某個全域性性質的資料隨著請求的處理越堆積越多,而出問題的這個物件使用到了這個全域性資料。沒錯,就是這樣,終於要見到曙光啦,哈哈,激動!

圖片

這就解釋了為什麼這個物件隨著時間的推移就和美債一樣越滾越多,變得越來越龐大了,雖然美國政府可能沒打算還美債,但是CPU拷貝越來越多的資料必然導致耗時越來越高。

找出bug

既然明確了方向,接下來就有針對性了。

首先去看一下這個物件都有哪些成員變數,對於內建型別像int、bool之類肯定不會有問題,因為這些型別的變數大小是固定的,需要注意的就是這種vector、set之類的容器。

最終經過一番檢查後斷定問題就是出在了某個vector成員變數上,同時也驗證了上述猜想。

真相大白

問題是這樣的。

這個物件的某個vector成員變數每次在處理請求時都要用另一個物件(假設為物件A)的資料來進行初始化,就像這樣:

圖片

在每次處理一個請求之前,A持有的Data都會被push一些特定的資料。

而系統為了優化記憶體分配開銷,物件A被放到了記憶體池中,就像這樣:

圖片

由於物件被放到了記憶體池,因此物件A是不會被釋放的,這就讓物件A無形中變為了全域性性質的物件

現在,有的同學可能已經發現問題了,那就是,如果物件A在放回記憶體池後沒有清空持有的Data,那麼就會導致這樣的一個問題,那就是A持有的Data隨著每個請求的到來不斷的被push資料,這就會導致A持有的Data就像泡沫一樣越吹越大,相應的Obj物件持有的vector也會越來越大:

圖片

在這種情況下拷貝Obj物件必然要拷貝持有的vector,由於vector越來越大,因此消耗在拷貝上的時間也越來越多,使用記憶體池本意是好的,但由於使用完後忘記清理其儲存的舊資料反而造成了記憶體洩漏

經驗教訓

這次的問題從程式碼編寫角度看是這樣的,物件A中Data欄位的清理工作沒有放在物件A的Clear函式,反而要靠使用者自己清理,由於程式碼非常複雜極其容易疏漏,博主就在這裡踩坑了。。。

因此,總結下來經驗教訓就是:

  1. 向類中新增新成員時一定要注意其清理工作,使用前一定要確保該成員是嶄新的,裡面沒有之前舊存貨。
  2. 程式碼中新增必要的監控,有利於排查問題
  3. 測試的時間要稍微長一些,否則類似這裡的問題不容易暴露
  4. 程式設計師遇到bug是很正常的,大膽假設,小心求證,每次問題的解決都是能力的提升

查到問題後,修bug、自測、驗證、程式碼提交一氣呵成,再次上線就明天了,收工回家。

從下午上線發現問題到問題解決耗時超過6小時,博主到家時,已是滿天繁星。

希望本文能幫助大家避開一些坑。