程序員:一個(gè)bug我花了4天時(shí)間,網(wǎng)友:你這還算好的了

我印象深刻的一個(gè) Bug挤渐,是一個(gè)服務(wù)器網(wǎng)絡(luò)框架無鎖隊(duì)列的 Bug 苹享。那個(gè) Bug 連續(xù)查找了五天的時(shí)間,才最后定位出來浴麻。

當(dāng)時(shí)我們的分布式存儲(chǔ)系統(tǒng)出現(xiàn)了性能瓶頸得问,定位后發(fā)現(xiàn)瓶頸是在服務(wù)器網(wǎng)絡(luò)框架上,所以我們決定為此替換一個(gè)最新研發(fā)的網(wǎng)絡(luò)框架软免。這個(gè)新的網(wǎng)絡(luò)框架為了追求高的性能宫纬,采用了無鎖隊(duì)列的設(shè)計(jì)。

第一天編碼測試完成后膏萧,在測試環(huán)境跑漓骚,完全正常,特地搞了一堆 Log 來重放請求向抢,程序跑得特別歡快认境。

解決了當(dāng)時(shí)的性能瓶頸,感覺特別的開心挟鸠,但好景不長,服務(wù)部署到現(xiàn)網(wǎng)環(huán)境亩冬,跑不到一個(gè)小時(shí)艘希,就 Core Dump 了。

嘗試上線了幾次硅急,每次都是跑半個(gè)多小時(shí)覆享,就 Core Dump 。

當(dāng)時(shí)的第一反應(yīng)是新網(wǎng)絡(luò)框架的問題营袜,這是很直覺的反應(yīng)撒顿,但很快就產(chǎn)生了懷疑,因?yàn)檫@個(gè)框架也有其它模塊在用荚板,也沒產(chǎn)生問題凤壁,當(dāng)時(shí)覺得詭異了。

仔細(xì)查看了代碼的修改記錄跪另,特定檢查了版本管理系統(tǒng)的 Log拧抖,做了代碼 Diff,確定確實(shí)只有這部分的修改免绿。

憑著經(jīng)驗(yàn)唧席,我說服自己,這個(gè)時(shí)候應(yīng)該堅(jiān)信最明確的邏輯,不要走到其他歪路上去淌哟。

第二天迹卢,我把這個(gè)框架單獨(dú)拎了出來,特地寫了一個(gè)測試的模塊徒仓,并用測試代碼生成了一堆的請求數(shù)據(jù)婶希,發(fā)送給測試模塊。

小編是一個(gè)有著6年工作經(jīng)驗(yàn)的工程師蓬衡,關(guān)于C++喻杈,編程,自己有做材料的整合狰晚,一個(gè)完整的C++編程學(xué)習(xí)路線筒饰,學(xué)習(xí)資料和工具,能夠進(jìn)我的群7253壁晒,-91790收取瓷们,免費(fèi)送給大家,希望你也能憑著自己的努力秒咐,成為下一個(gè)優(yōu)秀的程序員

它瘋狂的運(yùn)轉(zhuǎn)起來谬晕,跑了一個(gè)多小時(shí),跑得很歡快携取,一切正常攒钳,啥 Bug 都沒有。

開大了并發(fā)雷滋,繼續(xù)壓不撑,依然沒有問題。懵逼了晤斩!不知咋回事焕檬。再次小心得灰度系統(tǒng)上線,跑不到一個(gè)小時(shí)還是 Core Dump 了澳泵,這個(gè)時(shí)候实愚,我開始懷疑人生了,這個(gè)是咋回事了兔辅,都想砸鍵盤了都腊敲。

然后我冷靜了下來,經(jīng)驗(yàn)告訴我幢妄,這時(shí)應(yīng)該按照正常流程完整地跑一遍測試模塊兔仰。

于是我把那個(gè)測試模塊打包成了現(xiàn)網(wǎng)模塊,切走了一臺(tái)現(xiàn)網(wǎng)機(jī)器的流量蕉鸳,把測試模塊給上線到了一臺(tái)現(xiàn)網(wǎng)機(jī)器乎赴。

之后用工具往現(xiàn)網(wǎng)機(jī)器發(fā)送數(shù)據(jù)忍法,不到一個(gè)小時(shí),Core Dump 了榕吼。終于復(fù)現(xiàn)了這個(gè) Corde Dump, 那一刻猶如哥倫布發(fā)現(xiàn)了新大陸饿序,簡直欣喜若狂啊。這個(gè)時(shí)候已經(jīng)是第三天了羹蚣。

我復(fù)現(xiàn)了 Bug原探,但依然沒辦法定位出具體的原因。Core Dump 出來的棧是全亂的顽素,沒有任何價(jià)值咽弦,接下來,就開始用 Log 跟蹤法了胁出。

我依據(jù)數(shù)據(jù)的流轉(zhuǎn)過程型型,在每個(gè)關(guān)鍵點(diǎn),都打上 Log全蝶,Log 包含了所在的函數(shù)闹蒜,行數(shù),程序邏輯的編號(hào)抑淫,全部的關(guān)鍵數(shù)據(jù)等等绷落。

我仔細(xì)地設(shè)計(jì)了這個(gè) Log,爭取打得不多不少始苇,太多容易看暈頭砌烁,而且太多無效的信息,會(huì)掩蓋了真正的問題埂蕊;太少往弓,信息不足,又不足以判斷蓄氧,所以這種情況下打 Log 也是個(gè)藝術(shù)活。

通過精心設(shè)計(jì)的 Log槐脏,終于發(fā)現(xiàn)數(shù)據(jù)在一個(gè)特定的環(huán)節(jié)混亂后喉童,程序就一定會(huì) Core Dump。

分析 Log , 發(fā)現(xiàn)數(shù)據(jù)包在最后時(shí)刻是完整的顿天,但包似乎出現(xiàn)了亂序和重復(fù)堂氯。這個(gè)時(shí)候,才開始意識(shí)到可能是無鎖隊(duì)列的問題牌废,因?yàn)橹挥嘘?duì)列出問題咽白,包的進(jìn)出順序才會(huì)亂掉。

然后又花了半天的時(shí)間鸟缕,專門為無鎖隊(duì)列寫了測試用例晶框,用數(shù)據(jù)瘋狂地懟排抬。在測試環(huán)境,依然一切安好授段,但上線到正式環(huán)境蹲蒲,壓測半小時(shí)后,終于掛了侵贵。終于看到了勝利的曙光届搁!這個(gè)時(shí)候已經(jīng)是第四天了!

到現(xiàn)在已經(jīng)很明確是無鎖隊(duì)列的問題了窍育。但這個(gè)數(shù)據(jù)結(jié)構(gòu)的代碼不到 200 行卡睦。我拉了兩個(gè)同事一起 Review,都沒發(fā)現(xiàn)問題漱抓。但就是 Core Dump 了表锻。

奇葩了,又陷入了人生懷疑辽旋,開始懷疑內(nèi)存浩嫌,懷疑 CPU,結(jié)果換了機(jī)器补胚,還是一樣码耐。

后來,仔細(xì)地對比了現(xiàn)網(wǎng)環(huán)境和測試環(huán)境的區(qū)別溶其,機(jī)器類型骚腥,操作系統(tǒng)版本都一樣。然后編譯器瓶逃?咦束铭!編譯器?上去看了一下厢绝,結(jié)果發(fā)現(xiàn)編譯器的版本不一樣契沫!

這段時(shí)間我所使用的現(xiàn)網(wǎng)編譯環(huán)境升級(jí)了新的 GCC 版本,但測試編譯環(huán)境昔汉,還是舊的版本的懈万。(這個(gè)也比較坑)

當(dāng)時(shí)的直覺是肯定跟編譯器相關(guān),但代碼都一樣靶病,難道是編譯器 Bug会通?不可能吧 ?娄周!

后來想涕侈,不如將它們轉(zhuǎn)換成匯編看看吧。于是用兩個(gè)版本的編譯器將 C 的代碼各自轉(zhuǎn)換成了匯編煤辨。然后 Diff 匯編代碼裳涛,哇木张!發(fā)現(xiàn)真的有一行是不同的!

后來自己分析對比调违,發(fā)現(xiàn)是因?yàn)槲覀冮_啟了 GCC 最高級(jí)別的代碼性能優(yōu)化窟哺,不同版本的 GCC 在一些沒有特定依賴的語句上的優(yōu)化是不同的。

說人話技肩,就是有一段代碼且轨,如果加了鎖,兩個(gè)版本的編譯器下虚婿,都會(huì)產(chǎn)生一樣的匯編旋奢,如果沒有加鎖,代碼有一行的順序被調(diào)整了然痊,當(dāng)然至朗,從編譯器優(yōu)化的角度講是對的,是我們使用姿勢不對剧浸。

但無論怎么樣锹引,終于找出了這個(gè)問題。蒼天啊! 找了五天呀唆香!最后當(dāng)然是開開心心地上線了嫌变。

查這個(gè) Bug 確實(shí)花費(fèi)了很多的時(shí)間,不過也是沒辦法躬它,你不解決 Bug腾啥,就不能上線,但中間也收獲不少冯吓,特別是對編譯器優(yōu)化有了很深的印象倘待,也算是為自己的查 Bug 能力,又貢獻(xiàn)了一波經(jīng)驗(yàn)吧组贺。

對于 Bug , 我分享下自己的一些認(rèn)識(shí)和建議凸舵。

面對 Bug 的態(tài)度

只要你持續(xù)地寫代碼,就一定會(huì)持續(xù)的產(chǎn)生 Bug失尖,所以第一個(gè)事情是要擺正對 Bug 的心態(tài)贞间。我遇到過兩個(gè)極端。

第一個(gè)極端

遇到過一個(gè) Leader雹仿,對系統(tǒng)質(zhì)量相當(dāng)重視,對我們寫的代碼要求很高整以,每次設(shè)計(jì)并寫完一個(gè)新的系統(tǒng)胧辽,他喜歡跟你算這次的系統(tǒng)上線,產(chǎn)生了多少次故障公黑,這半年時(shí)間產(chǎn)生了多少個(gè) Bug邑商,每個(gè) Bug 的影響范圍如何摄咆。

我們一堆人被搞得特別累,戰(zhàn)戰(zhàn)兢兢人断,到后面吭从,大家都比較排斥去做優(yōu)化,去重構(gòu)代碼恶迈,只求無過涩金,不求有功了。

所以暇仲,我覺得這種方式不好步做,對 Bug 帶著一種比較包容的態(tài)度去看待,可以減少不少的心理負(fù)擔(dān)奈附。

第二個(gè)極端

后來去了一個(gè)新的團(tuán)隊(duì)全度。新的團(tuán)隊(duì)很重視業(yè)務(wù)和工程迭代的速度筐带,所以對代碼質(zhì)量和 Bug 的容忍度很高给猾。如果是一個(gè)新上線的業(yè)務(wù)乓序,是默許 Bug 存在的钧栖。

這種對質(zhì)量過于松散的要求洛二,在后面也帶來不好的影響奸绷。大家對 Bug 太免疫了埂伦,以至于出現(xiàn) Bug 和故障的時(shí)候顿涣,大家都不夠緊張漩符。

系統(tǒng)質(zhì)量有一段時(shí)間出現(xiàn)比較大的問題一喘,還因此被部門經(jīng)理特訓(xùn)了一番,后面通過各種措施嗜暴,才慢慢提高了整體的系統(tǒng)質(zhì)量凸克。

上面兩種極端都不可取,應(yīng)該很重視 Bug闷沥,盡量避免 Bug萎战,但也不應(yīng)該唯 Bug 多少論業(yè)績。

具體到 Bug 的查找上舆逃,我說說我的一些經(jīng)驗(yàn)蚂维。

Bug 的復(fù)現(xiàn)

我把 Bug 分為可重現(xiàn)的 Bug 和不可復(fù)現(xiàn)的 Bug 。

對于可重現(xiàn)的 Bug , 查找起來比較容易路狮,比如可以用”二分查找“的方式虫啥,從模塊層面開始定位起,每次折半奄妨,每次折半地縮小范圍涂籽,一直到代碼層面。

在代碼層面砸抛,遵循一些常用的原則评雌,比如:

看到內(nèi)存拷貝树枫,直覺上要想到內(nèi)存越界。

看到數(shù)組景东,就要考慮是否索引越界砂轻。

看到指針,就要考慮是否正確解構(gòu)斤吐。

看到多線程搔涝,就要考慮是否線程安全。

對于不可重現(xiàn)的 Bug曲初,第一步就是要把它重現(xiàn)出來体谒。

有時(shí)候特別的難,特別是并發(fā)形態(tài)下產(chǎn)生的 Bug臼婆,出現(xiàn)的時(shí)機(jī)和觸發(fā)條件都不清楚抒痒。對于這種 Bug,只能通過各種嘗試去復(fù)現(xiàn)它颁褂。

比如將多并發(fā)調(diào)整為單并發(fā)的方式故响,看能否復(fù)現(xiàn),如果可以復(fù)現(xiàn)颁独,就可以轉(zhuǎn)化為可復(fù)現(xiàn)的 Bug彩届,用”二分查找“的方式去排查。

如果不能復(fù)現(xiàn)誓酒,那極大概率是并發(fā)問題樟蠕。這個(gè)時(shí)候最好先停止排查,仔細(xì)分析程序在并發(fā)狀態(tài)下可能出問題的點(diǎn)靠柑。

大部分并發(fā)問題的根源寨辩,是互斥數(shù)據(jù)沒有被正確讀寫,或者一些共享狀態(tài)被錯(cuò)誤修改歼冰。

靜態(tài)代碼檢查

利用 Coverity 等代碼檢查工具進(jìn)行代碼的靜態(tài)檢查可以發(fā)現(xiàn)很多潛在的問題靡狞,而且修復(fù)的成本很低。團(tuán)隊(duì)后來引入了這個(gè)檢查工具隔嫡,確實(shí)帶來了不錯(cuò)的效果甸怕。類似變量未初始化,疑似的內(nèi)存越界等都有可能被檢查出來腮恩。

編譯器的 Warning梢杭。有些同學(xué)一開始的時(shí)候?qū)?Warning 不重視。我們團(tuán)隊(duì)早期也遇到過這個(gè)情況秸滴。

那時(shí)候產(chǎn)品迭代的速度很快式曲,所以大家寫完代碼,能夠編譯通過,就進(jìn)行各種測試吝羞,然后準(zhǔn)備上線了。

一開始的一兩個(gè) Warning内颗,不太理會(huì)钧排。后面發(fā)現(xiàn)越積越多,到最后終于成為一個(gè)不得不解決的問題均澳。

部門還為此特地立項(xiàng)恨溜,來消除 Warning。先是在內(nèi)部多次強(qiáng)調(diào)了這個(gè)理念找前,然后從基礎(chǔ)庫糟袁,基礎(chǔ)模塊開始實(shí)施,基礎(chǔ)代碼部分統(tǒng)一 Fix Warning躺盛,然后開啟編譯器把 Warning 當(dāng) Error 的開關(guān)项戴。

完成之后,再逐步地推業(yè)務(wù)模塊進(jìn)行修改槽惫。反正折騰了好一段時(shí)間周叮。

工欲善其事必先利其器

代碼 Bug 出現(xiàn)的時(shí)候,善用一些排查工具可以極大得提升效率界斜。比如對于 C/C++ 的 GDB 調(diào)試仿耽,內(nèi)存泄漏時(shí)候 Valgrind 的檢測,Linux 下面用 Perf Top 來析 CPU 的消耗等各薇。

一開始的時(shí)候项贺,我對這些工具不重視,老是覺得真正使用的時(shí)候峭判,去查文檔就行开缎。

后面才發(fā)現(xiàn),用工具查著問題時(shí)候朝抖,遇到不會(huì)用的命令或功能啥箭,再去查文檔,是個(gè)痛苦的事情治宣,來回切換的開銷也使得效率低下急侥。

后面就對這些輔助工具的使用重視了起來,專門花時(shí)間去學(xué)習(xí)和練習(xí)使用侮邀,反而提升了不少的效率坏怪。

打 Log 的藝術(shù)

很多時(shí)候,出現(xiàn)一個(gè) Bug绊茧,未能定位出來铝宵,需要打上更多額外的 Log 來輔助排查。一開始的時(shí)候,是想到一點(diǎn)鹏秋,打一個(gè) Log尊蚁,后面發(fā)現(xiàn)這么做沒有章法,邏輯不清晰, 排查效率低下侣夷。

后來學(xué)會(huì)了横朋,遇到 Bug 后,先在腦子里面分析一番百拓,然后花一兩個(gè)小時(shí)詳細(xì)地設(shè)計(jì) Log 的格式和打 Log 的位置琴锭。發(fā)現(xiàn)這種方式對查問題的效率提升很大。

所以遇到 Bug 的時(shí)候不要急躁衙传,先靜下來心來分析决帖,在腦海里盡力重現(xiàn)出完整的運(yùn)行邏輯,然后仔細(xì)地進(jìn)行 Log 設(shè)計(jì)蓖捶,包括 Log 包含的字段地回,打 Log 的點(diǎn)等。這樣能極大的提升排查問題的效率腺阳。

結(jié)語

Bug 是程序員最不愿意面對落君,但又經(jīng)常出現(xiàn)的一個(gè) “詭異生物“。對待 Bug 要有正確的心態(tài)亭引,最好不要跟業(yè)績強(qiáng)綁定绎速,也不能太過于疏忽。

Bug 的排查是個(gè)很復(fù)雜的事情焙蚓,每個(gè)人都有自己的方式和做法纹冤,如果你有好的做法和建議,也歡迎在留言區(qū)分享給大家购公!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末萌京,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子宏浩,更是在濱河造成了極大的恐慌知残,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件比庄,死亡現(xiàn)場離奇詭異求妹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)佳窑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門制恍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人神凑,你說我怎么就攤上這事净神。” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵鹃唯,是天一觀的道長爱榕。 經(jīng)常有香客問我,道長俯渤,這世上最難降的妖魔是什么呆细? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮八匠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘趴酣。我一直安慰自己梨树,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布岖寞。 她就那樣靜靜地躺著抡四,像睡著了一般。 火紅的嫁衣襯著肌膚如雪仗谆。 梳的紋絲不亂的頭發(fā)上指巡,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音隶垮,去河邊找鬼藻雪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛狸吞,可吹牛的內(nèi)容都是我干的勉耀。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼蹋偏,長吁一口氣:“原來是場噩夢啊……” “哼便斥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起威始,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤枢纠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后黎棠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晋渺,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年葫掉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了些举。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡俭厚,死狀恐怖户魏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤叼丑,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布关翎,位于F島的核電站,受9級(jí)特大地震影響鸠信,放射性物質(zhì)發(fā)生泄漏纵寝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一星立、第九天 我趴在偏房一處隱蔽的房頂上張望爽茴。 院中可真熱鬧,春花似錦绰垂、人聲如沸室奏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胧沫。三九已至,卻和暖如春占业,著一層夾襖步出監(jiān)牢的瞬間绒怨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工谦疾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留南蹂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓餐蔬,卻偏偏與公主長得像碎紊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子樊诺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容