清明假日前一天,我們進(jìn)行了服務(wù)器升級(jí),然后第二天,同事給我來了一個(gè)電話,網(wǎng)站訪問異常卡頓,在MySQL里面發(fā)現(xiàn)大量的slow log。
于是我立刻放棄了休假的打算,連上了服務(wù)器,首先查看slow log,發(fā)現(xiàn)即使是單條記錄的主鍵查詢,也耗時(shí)將近1s多,這是完全不可能的事情。然后show processlist,發(fā)現(xiàn)大量的語句狀態(tài)為statistics,根本不能快速的執(zhí)行完成。
然后查看了機(jī)器的CPU,發(fā)現(xiàn)MySQL的CPU很高,但I(xiàn)O的負(fù)載異常的低,表明MySQL真的在處理很大量的請(qǐng)求。我們不停地show processlist,發(fā)現(xiàn)有些select語句查詢,row examined竟然有幾十萬行之多,然后看語句,發(fā)現(xiàn)了很蛋疼的問題。類似如下:
SELECT * FROM tbl WHERE group = 123 and parent = 2
我們使用的是鄰接表模型在MySQL里面存儲(chǔ)樹狀結(jié)構(gòu),也就是每條記錄需要存儲(chǔ)父節(jié)點(diǎn)的ID,上面那條語句就是在一個(gè)group里面查詢某個(gè)節(jié)點(diǎn)下面的子節(jié)點(diǎn)。
然后這個(gè)表里面的索引竟然只有g(shù)roup,至于原因,貌似是最開始設(shè)計(jì)的同學(xué)認(rèn)為一個(gè)group里面的數(shù)據(jù)不可能很多,即使全examined一次也無所謂。可是偏偏,一個(gè)用戶蛋疼的就往一個(gè)group里面放了幾十萬的數(shù)據(jù),然后,任何一次查詢都會(huì)極大地拉低整個(gè)MySQL的性能。經(jīng)過這一次,再一次讓我意識(shí)到,組里面的小盆友的MySQL知識(shí)還需要提升,如何用好索引,寫過高效的查詢語句真的不是一件簡(jiǎn)單的事情。
于是我們立刻更新了索引,然后慢查詢馬上就沒有了。但沒有了超時(shí),我們?nèi)匀话l(fā)現(xiàn),MySQL的CPU異常的高,show processlist的時(shí)候出現(xiàn)了很多system lock的情況,表明寫入并發(fā)量大,一直在爭(zhēng)鎖。通過日志發(fā)現(xiàn),一個(gè)用戶不停地往自己的group里面增加文件,而從升級(jí)之前到第二天下午,這個(gè)用戶已經(jīng)累計(jì)上傳了50w的文件。
最開始我們懷疑是被攻擊了,但通過日志,發(fā)現(xiàn)這個(gè)用戶的文件都是很正常的文件,并且完全像是在正常使用的。但仍不能排除嫌疑,于是我們立刻升級(jí)了一臺(tái)服務(wù)器,將LVS的流量全部切過去(幸好放假了,不然一臺(tái)機(jī)器鐵定頂不?。?,但令我們吃驚的是,記錄的訪問請(qǐng)求壓根沒有這個(gè)用戶任何的信息,但是文件仍然在不停地新增。然后通過show processlist查看,MySQL的請(qǐng)求全部是另外幾臺(tái)機(jī)器發(fā)過來的,但另外幾臺(tái)機(jī)器現(xiàn)在已經(jīng)完全沒有流量了。
于是我們立刻想到了異步任務(wù),會(huì)不會(huì)有某個(gè)異步任務(wù)死循環(huán)導(dǎo)致不停地插入,但監(jiān)控rabbitmq,卻發(fā)現(xiàn)仍然沒有該用戶的任何信息。這時(shí)候,一個(gè)同事看代碼,突然發(fā)現(xiàn),一個(gè)導(dǎo)致死循環(huán)的操作根本沒扔到異步任務(wù)里面,而是直接在服務(wù)里面go了一個(gè)coroutine跑了。于是我們立刻將其他幾臺(tái)機(jī)器重啟,然后MySQL正常了。
從晚上升級(jí),到第二天真正發(fā)現(xiàn)問題并解決,因?yàn)槲覀兊拇abug,導(dǎo)致了我們整個(gè)服務(wù)幾乎不可用,不可不說是一個(gè)嚴(yán)重的教訓(xùn)。
- MySQL索引設(shè)計(jì)不合理,導(dǎo)致極端情況下面拖垮了整個(gè)性能。
- 代碼健壯性不足,對(duì)于可能引起死循環(huán)的應(yīng)用沒有做更多的檢查處理。
- 日志缺失,很多偷懶不寫日志,覺得沒啥用,但偏偏遇到問題了才會(huì)有用。
- 統(tǒng)計(jì)缺失,沒有詳細(xì)的統(tǒng)計(jì)信息,用來監(jiān)控整個(gè)服務(wù)。
- 沒有更健壯的限頻限容策略,雖然我們開啟了,但因?yàn)槌绦騼?nèi)部bug,導(dǎo)致完全沒用。
有些時(shí)候,只有做好了完全的準(zhǔn)備,才能更好的應(yīng)對(duì)對(duì)突發(fā)情況,畢竟我們是在做產(chǎn)品,要對(duì)用戶負(fù)責(zé),也要對(duì)自己的職業(yè)負(fù)責(zé)。
最后,這次的教訓(xùn)再一次說明,節(jié)假日前一天晚上升級(jí),后果很嚴(yán)重。