清明假日前一天湃缎,我們進行了服務(wù)器升級,然后第二天嗓违,同事給我來了一個電話知残,網(wǎng)站訪問異潮茸卡頓佳窑,在MySQL里面發(fā)現(xiàn)大量的slow log父能。
于是我立刻放棄了休假的打算,連上了服務(wù)器溉委,首先查看slow log爱榕,發(fā)現(xiàn)即使是單條記錄的主鍵查詢,也耗時將近1s多黔酥,這是完全不可能的事情。然后show processlist棵帽,發(fā)現(xiàn)大量的語句狀態(tài)為statistics逗概,根本不能快速的執(zhí)行完成忘衍。
然后查看了機器的CPU,發(fā)現(xiàn)MySQL的CPU很高隶垮,但IO的負載異常的低秘噪,表明MySQL真的在處理很大量的請求指煎。我們不停地show processlist至壤,發(fā)現(xiàn)有些select語句查詢枢纠,row examined竟然有幾十萬行之多,然后看語句,發(fā)現(xiàn)了很蛋疼的問題脓斩。類似如下:
SELECT * FROM tbl WHERE group = 123 and parent = 2
我們使用的是鄰接表模型在MySQL里面存儲樹狀結(jié)構(gòu)随静,也就是每條記錄需要存儲父節(jié)點的ID吗讶,上面那條語句就是在一個group里面查詢某個節(jié)點下面的子節(jié)點。
然后這個表里面的索引竟然只有g(shù)roup重绷,至于原因膜毁,貌似是最開始設(shè)計的同學認為一個group里面的數(shù)據(jù)不可能很多,即使全examined一次也無所謂爽茴∈易啵可是偏偏,一個用戶蛋疼的就往一個group里面放了幾十萬的數(shù)據(jù)昌简,然后纯赎,任何一次查詢都會極大地拉低整個MySQL的性能。經(jīng)過這一次犬金,再一次讓我意識到六剥,組里面的小盆友的MySQL知識還需要提升疗疟,如何用好索引,寫過高效的查詢語句真的不是一件簡單的事情策彤。
于是我們立刻更新了索引,然后慢查詢馬上就沒有了裹刮。但沒有了超時,我們?nèi)匀话l(fā)現(xiàn)囊咏,MySQL的CPU異常的高梅割,show processlist的時候出現(xiàn)了很多system lock的情況,表明寫入并發(fā)量大户辞,一直在爭鎖底燎。通過日志發(fā)現(xiàn)弹砚,一個用戶不停地往自己的group里面增加文件,而從升級之前到第二天下午朱沃,這個用戶已經(jīng)累計上傳了50w的文件茅诱。
最開始我們懷疑是被攻擊了瑟俭,但通過日志,發(fā)現(xiàn)這個用戶的文件都是很正常的文件失暴,并且完全像是在正常使用的微饥。但仍不能排除嫌疑,于是我們立刻升級了一臺服務(wù)器,將LVS的流量全部切過去(幸好放假了简软,不然一臺機器鐵定頂不住)痹升,但令我們吃驚的是,記錄的訪問請求壓根沒有這個用戶任何的信息肛跌,但是文件仍然在不停地新增衍慎。然后通過show processlist查看,MySQL的請求全部是另外幾臺機器發(fā)過來的稳捆,但另外幾臺機器現(xiàn)在已經(jīng)完全沒有流量了麦轰。
于是我們立刻想到了異步任務(wù),會不會有某個異步任務(wù)死循環(huán)導致不停地插入末荐,但監(jiān)控rabbitmq新锈,卻發(fā)現(xiàn)仍然沒有該用戶的任何信息壕鹉。這時候,一個同事看代碼负乡,突然發(fā)現(xiàn)脊凰,一個導致死循環(huán)的操作根本沒扔到異步任務(wù)里面狸涌,而是直接在服務(wù)里面go了一個coroutine跑了。于是我們立刻將其他幾臺機器重啟朝捆,然后MySQL正常了懒豹。
從晚上升級,到第二天真正發(fā)現(xiàn)問題并解決儒老,因為我們的代碼bug驮樊,導致了我們整個服務(wù)幾乎不可用,不可不說是一個嚴重的教訓挖腰。
- MySQL索引設(shè)計不合理曙聂,導致極端情況下面拖垮了整個性能鞠鲜。
- 代碼健壯性不足,對于可能引起死循環(huán)的應(yīng)用沒有做更多的檢查處理贤姆。
- 日志缺失,很多偷懶不寫日志坐漏,覺得沒啥用碧信,但偏偏遇到問題了才會有用砰碴。
- 統(tǒng)計缺失呈枉,沒有詳細的統(tǒng)計信息趁尼,用來監(jiān)控整個服務(wù)酥泞。
- 沒有更健壯的限頻限容策略芝囤,雖然我們開啟了凡人,但因為程序內(nèi)部bug,導致完全沒用。
有些時候耳幢,只有做好了完全的準備,才能更好的應(yīng)對對突發(fā)情況睛藻,畢竟我們是在做產(chǎn)品启上,要對用戶負責,也要對自己的職業(yè)負責店印。
最后冈在,這次的教訓再一次說明,節(jié)假日前一天晚上升級按摘,后果很嚴重包券。