架構師寫的BUG,非比尋常

原創:小姐姐味道(微信公衆號ID:xjjdog),歡迎分享,轉載請保留出處。java

部門新來了個架構師,BAT背景,住在三環,開寶立刻班,有車位。git

小夥話很少,但一旦說話斬釘截鐵,帶着沒法撼動的自信。緣由就是,有他着數億高併發經驗,每一秒鐘的請求,都是其餘企業運行一年也沒法企及的。這就讓人很是羨慕,畢竟他靠這個比我賺的錢要多。程序員

俗話說,要想在公司不出事故,那就不要寫代碼。幹活多了容易出事,一身輕鬆無人問津,這就是現實。安全

但有時候仍是要當作果的。新來的研發領導不懂技術,但他懂技術指標,因此就統計你們提交git的數量,若是git活動是一片綠色如A股,那就算過關了。bash

架構師思來想去,決定領一個併發量最高的需求:統計接口的平均響應時間和啓動以來的請求數。微信

爲何說它的併發量高呢?這是由於,它是統計全部接口的,天然比每個接口的請求量都要大。AOP代碼一包,每一個接口都得從他這裏走一圈。架構

該咱們的架構師上場了。代碼如圖。併發

架構師說,個人代碼不須要作註釋。所謂的註釋,都是給垃圾代碼用的。我深覺得是,他明顯是受到了Netflix公司的影響。高併發

程序考慮到了高併發場景,使用了線程安全的ConcurrentHashMap,而後每次經過監控key取出相應的數據,而後在value上遞增。這麼簡單的代碼,確實不須要增長什麼註釋。測試

做爲項目裏併發量最高的代碼,出於對高級架構師的信任,咱們並不須要作什麼代碼review,也不須要作什麼測試。你們都很忙,代碼您吶,到線上遛一遛吧。

我建議你先找一找代碼的問題,若是你發現了問題,那就比架構師還厲害;若是你沒發現,也不證實你比架構師弱,沒有什麼好傷心的。

下面插一副圖,阻斷一下思惟。

裝B遭雷劈,線上運行一段時間後,內存溢出了。

你們吵吵個沒完,畢竟xjjdog說過,內存溢出問題的排查週期很長,大約平均須要40天左右才能解決問題。在你們開始論證的時候,架構師偷偷的啓動了Eclipse MAT。MAT用來分析內存問題是很是合適的,但前提是你須要把堆棧給搗鼓下來。

架構師會用jmap,最主要的是權限大,因而本身搞了一份拷貝到線下分析。

我能理解到他的心情,畢竟問題定位到本身的代碼不是一件什麼值得高興的事情。他發現內存的堆裏面,滿滿的全是MonitorKeyMonitorValue

Monitor$MonitorKey@15aeb7ab
複製代碼

我和架構師關係比較好,因而他問我:我們的接口是否是特別的多?

我說:不是啊,你別看訪問量大,就這麼個狗屁業務能有多少接口?幾百個撐了天了。

他說:我在堆裏發現了幾千萬個...

說完他就不言語了,由於他發現裏面有很多是同樣的接口。必定是參數的緣由,因此他在代碼里加了這個,把後面的給截斷了。

key = key.split("\\?")[0];
複製代碼

結果發佈到線上,過不了多久內存又溢出了。此次終於引發了大牛們的注意,通過你們的分析,發現代碼是忘了給MonitorKey重寫equalshashCode方法了。

我不由臉紅起來。做爲好朋友,我不該該讓他出這個醜。但我又是隱隱快樂的,由於他工資比我高。

因此這就是一個很大的問題。不少同窗對HashMap的知識點對答如流,甚至還專門記憶了紅黑樹。但換一個方式去問,卻又一臉懵逼。

其中一種問法是這樣的:一個普通的對象,可以做爲HashMap的key麼?

答案顯然是能夠的,但須要注意重寫hashCode和equals方法。若是忘記重寫的話,大機率會形成內存泄漏。

很不幸,現實中忘記的案例不少。大牛架構師也會中招。

代碼重寫hashCode和equals方法後,線上就再也沒發生過內存溢出。


等等,還沒完。畢竟是架構師,僅僅這樣一個bug仍是證實不了水平的。架構師寫的bug,確定非比尋常。

這種事出現的多了,研發領導對技術的權威性就再也不是那麼感冒。咱們決定從併發量最高的代碼開始,進行一下代碼review。

很不幸,架構師的visit代碼出現問題了。雖然問題不是很大,但它畢竟是個問題。

在統計數據的時候,代碼使用了ConcurrentHashMap,但它並無什麼卵用。

visit方法,首先拿出了key,而後判空,再塞值。這明顯不是一個原子操做。

線程1:獲取key爲a的值
線程2:獲取key爲a的值
線程1:a爲null,生成一個b
線程2:a爲null,生成一個c
線程1:保存a=b
線程2:保存a=c
複製代碼

此時,B丟了。

業務能夠忍受,但嚴謹的技術大牛們忍受不了,提出了修改的意見。

架構師說,給visit方法加個synchronized不就成了。

public synchronized void visit(String url, String desc, long timeCost) 複製代碼

我說不行。有更優雅的寫法,效率更高。那就是使用putIfAbsent方法,代碼改動以下:

MonitorKey key = new MonitorKey(url, desc);
MonitorValue value = monitors.putIfAbsent(key, new MonitorValue());
value = monitors.get(key);
value.count.getAndIncrement();
value.totalTime.getAndAdd(timeCost);
value.avgTime = value.totalTime.get() / value.count.get();
複製代碼

你們就這兩種方式爭論了起來。

技術總監託着腮想了半天,看了看爭的面紅耳赤的同窗們,說:這就是我不放心大家的緣故。線上環境要儘可能保持穩定性,作最小的變動。既然加個synchronized就可以很容易簡單解決的問題,爲啥不直接用呢?下面這種代碼改動太大,有風險。

總監接着把頭轉向我:這個BUG非比尋常,爲了讓你們引覺得戒,你來作整個事故的覆盤。把問題的排查和獲得的教訓分享給你們,讓你們向這種至簡的架構看齊。咱們日常的工做中,也要儘可能以結果導向爲主,用什麼手段無所謂,能漂亮把事情辦好就行

這就是此篇文章的由來,我虛心受教,同時也明白本身的工資是漲不上去了。

你要是點個贊或者友情三連,或許還能安慰我一下下。​

做者簡介:小姐姐味道 (xjjdog),一個不容許程序員走彎路的公衆號。聚焦基礎架構和Linux。十年架構,日百億流量,與你探討高併發世界,給你不同的味道。個人我的微信xjjdog0,歡迎添加好友,​進一步交流。​

相關文章
相關標籤/搜索