掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢(xún)/運(yùn)營(yíng)咨詢(xún)/技術(shù)建議/互聯(lián)網(wǎng)交流
部門(mén)新來(lái)了個(gè)架構(gòu)師,BAT 背景,住在三環(huán),開(kāi)寶馬上班,有車(chē)位。

成都創(chuàng)新互聯(lián)公司是一家集網(wǎng)站建設(shè),衢州企業(yè)網(wǎng)站建設(shè),衢州品牌網(wǎng)站建設(shè),網(wǎng)站定制,衢州網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷(xiāo),網(wǎng)絡(luò)優(yōu)化,衢州網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M(mǎn)足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專(zhuān)業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶(hù)成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。
圖片來(lái)自 Pexels
小伙話(huà)不多,但一旦說(shuō)話(huà)斬釘截鐵,帶著無(wú)法撼動(dòng)的自信。原因就是,有他著數(shù)億高并發(fā)經(jīng)驗(yàn),每一秒鐘的請(qǐng)求,都是其他企業(yè)運(yùn)行一年也無(wú)法企及的。這就讓人非常羨慕,畢竟他靠這個(gè)比我賺的錢(qián)要多。
俗話(huà)說(shuō),要想在公司不出事故,那就不要寫(xiě)代碼。干活多了容易出事,一身輕松無(wú)人問(wèn)津,這就是現(xiàn)實(shí)。
但有時(shí)候還是要看成果的。新來(lái)的研發(fā)領(lǐng)導(dǎo)不懂技術(shù),但他懂技術(shù)指標(biāo),所以就統(tǒng)計(jì)大家提交 Git 的數(shù)量,如果 Git 活動(dòng)是一片綠色如 A 股,那就算過(guò)關(guān)了。
架構(gòu)師思來(lái)想去,決定領(lǐng)一個(gè)并發(fā)量最高的需求:統(tǒng)計(jì)接口的平均響應(yīng)時(shí)間和啟動(dòng)以來(lái)的請(qǐng)求數(shù)。
為什么說(shuō)它的并發(fā)量高呢?這是因?yàn)?,它是統(tǒng)計(jì)所有接口的,自然比每一個(gè)接口的請(qǐng)求量都要大。AOP 代碼一包,每個(gè)接口都得從他這里走一圈。
該我們的架構(gòu)師上場(chǎng)了,代碼如下圖:
架構(gòu)師說(shuō),我的代碼不需要做注釋。所謂的注釋?zhuān)际墙o垃圾代碼用的。我深以為是,他明顯是受到了 Netflix 公司的影響。
程序考慮到了高并發(fā)場(chǎng)景,使用了線(xiàn)程安全的 ConcurrentHashMap,然后每次通過(guò)監(jiān)控 Key 取出相應(yīng)的數(shù)據(jù),然后在 Value 上遞增。這么簡(jiǎn)單的代碼,確實(shí)不需要增加什么注釋。
作為項(xiàng)目里并發(fā)量最高的代碼,出于對(duì)高級(jí)架構(gòu)師的信任,我們并不需要做什么代碼 Review,也不需要做什么測(cè)試。大家都很忙,代碼您吶,到線(xiàn)上遛一遛吧。
我建議你先找一找代碼的問(wèn)題,如果你發(fā)現(xiàn)了問(wèn)題,那就比架構(gòu)師還厲害;如果你沒(méi)發(fā)現(xiàn),也不證明你比架構(gòu)師弱,沒(méi)有什么好傷心的。
下面插一副圖,阻斷一下思維:
裝 B 遭雷劈,線(xiàn)上運(yùn)行一段時(shí)間后,內(nèi)存溢出了。
大家吵吵個(gè)沒(méi)完,畢竟我說(shuō)過(guò),內(nèi)存溢出問(wèn)題的排查周期很長(zhǎng),大約平均需要 40 天左右才能解決問(wèn)題。
在大家開(kāi)始論證的時(shí)候,架構(gòu)師偷偷的啟動(dòng)了 Eclipse MAT。MAT 用來(lái)分析內(nèi)存問(wèn)題是非常合適的,但前提是你需要把堆棧給搗鼓下來(lái)。
架構(gòu)師會(huì)用 Jmap,最主要的是權(quán)限大,于是自己搞了一份拷貝到線(xiàn)下分析。
我能理解到他的心情,畢竟問(wèn)題定位到自己的代碼不是一件什么值得高興的事情。
他發(fā)現(xiàn)內(nèi)存的堆里面,滿(mǎn)滿(mǎn)的全是 MonitorKey 和 MonitorValue:
- Monitor$MonitorKey@15aeb7ab
我和架構(gòu)師關(guān)系比較好,于是他問(wèn)我:咱們的接口是不是特別的多?
我說(shuō):不是啊,你別看訪(fǎng)問(wèn)量大,就這么個(gè)狗屁業(yè)務(wù)能有多少接口?幾百個(gè)撐了天了。
他說(shuō):我在堆里發(fā)現(xiàn)了幾千萬(wàn)個(gè)...
說(shuō)完他就不言語(yǔ)了,因?yàn)樗l(fā)現(xiàn)里面有不少是一樣的接口。一定是參數(shù)的原因,所以他在代碼里加了這個(gè),把?后面的給截?cái)嗔恕?/p>
- key = key.split("\\?")[0];
結(jié)果發(fā)布到線(xiàn)上,過(guò)不了多久內(nèi)存又溢出了。這次終于引起了大牛們的注意,經(jīng)過(guò)大家的分析,發(fā)現(xiàn)代碼是忘了給 MonitorKey 重寫(xiě) equals 和 hashCode 方法了。
我不禁臉紅起來(lái),作為好朋友,我不應(yīng)該讓他出這個(gè)丑。但我又是隱隱快樂(lè)的,因?yàn)樗べY比我高。
所以這就是一個(gè)很大的問(wèn)題。很多同學(xué)對(duì) HashMap 的知識(shí)點(diǎn)對(duì)答如流,甚至還專(zhuān)門(mén)記憶了紅黑樹(shù)。但換一個(gè)方式去問(wèn),卻又一臉懵逼。
其中一種問(wèn)法是這樣的:一個(gè)普通的對(duì)象,能夠作為 HashMap 的 Key 么?
答案顯然是可以的,但需要注意重寫(xiě) hashCode 和 equals 方法。如果忘記重寫(xiě)的話(huà),大概率會(huì)造成內(nèi)存泄漏。
很不幸,現(xiàn)實(shí)中忘記的案例很多。大牛架構(gòu)師也會(huì)中招。代碼重寫(xiě) hashCode 和 equals 方法后,線(xiàn)上就再也沒(méi)發(fā)生過(guò)內(nèi)存溢出。
等等,還沒(méi)完。畢竟是架構(gòu)師,僅僅這樣一個(gè) Bug 還是證明不了水平的。架構(gòu)師寫(xiě)的 Bug,肯定非比尋常。
這種事出現(xiàn)的多了,研發(fā)領(lǐng)導(dǎo)對(duì)技術(shù)的權(quán)威性就不再是那么感冒。我們決定從并發(fā)量最高的代碼開(kāi)始,進(jìn)行一下代碼 Review。
很不幸,架構(gòu)師的 visit 代碼出現(xiàn)問(wèn)題了。雖然問(wèn)題不是很大,但它畢竟是個(gè)問(wèn)題。
在統(tǒng)計(jì)數(shù)據(jù)的時(shí)候,代碼使用了 ConcurrentHashMap,但它并沒(méi)有什么卵用。
visit 方法,首先拿出了 Key,然后判空,再塞值。這明顯不是一個(gè)原子操作。
- 線(xiàn)程1:獲取key為a的值
- 線(xiàn)程2:獲取key為a的值
- 線(xiàn)程1:a為null,生成一個(gè)b
- 線(xiàn)程2:a為null,生成一個(gè)c
- 線(xiàn)程1:保存a=b
- 線(xiàn)程2:保存a=c
此時(shí),B 丟了。業(yè)務(wù)可以忍受,但嚴(yán)謹(jǐn)?shù)募夹g(shù)大牛們?nèi)淌懿涣?,提出了修改的意?jiàn)。
架構(gòu)師說(shuō),給 visit 方法加個(gè) Synchronized 不就成了。
- public synchronized void visit(String url, String desc, long timeCost)
我說(shuō)不行。有更優(yōu)雅的寫(xiě)法,效率更高。那就是使用 putIfAbsent 方法,代碼改動(dòng)如下:
- MonitorKey key = new MonitorKey(url, desc);
- MonitorValue value = monitors.putIfAbsent(key, new MonitorValue());
- value.count.getAndIncrement();
- value.totalTime.getAndAdd(timeCost);
- value.avgTime = value.totalTime.get() / value.count.get();
大家就這兩種方式爭(zhēng)論了起來(lái)。
技術(shù)總監(jiān)托著腮想了半天,看了看爭(zhēng)的面紅耳赤的同學(xué)們,說(shuō):這就是我不放心你們的緣故。線(xiàn)上環(huán)境要盡量保持穩(wěn)定性,做最小的變更。
既然加個(gè) Synchronized 就能夠很容易簡(jiǎn)單解決的問(wèn)題,為啥不直接用呢?下面這種代碼改動(dòng)太大,有風(fēng)險(xiǎn)。
總監(jiān)接著把頭轉(zhuǎn)向我:這個(gè) Bug 非比尋常,為了讓大家引以為戒,你來(lái)做整個(gè)事故的復(fù)盤(pán)。把問(wèn)題的排查和得到的教訓(xùn)分享給大家,讓大家向這種至簡(jiǎn)的架構(gòu)看齊。
我們平常的工作中,也要盡量以結(jié)果導(dǎo)向?yàn)橹鳎檬裁词侄螣o(wú)所謂,能漂亮把事情辦好就行。
這就是此篇文章的由來(lái),我虛心受教,同時(shí)也明白自己的工資是漲不上去了。你要是點(diǎn)個(gè)贊或者友情三連,或許還能安慰我一下下。
作者:小姐姐養(yǎng)的狗
簡(jiǎn)介:一個(gè)不允許程序員走彎路的公眾號(hào)。聚焦基礎(chǔ)架構(gòu)和 Linux。十年架構(gòu),日百億流量,與你探討高并發(fā)世界,給你不一樣的味道。
編輯:陶家龍
出處:轉(zhuǎn)載自微信公眾號(hào)小姐姐味道(微信公眾號(hào)ID:xjjdog)

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢(xún)/運(yùn)營(yíng)咨詢(xún)/技術(shù)建議/互聯(lián)網(wǎng)交流