最近又遇到了一次慢查把db(mariadb10)幾乎打掛的案例,作為一個核心支付系統的技術負責人,真是每日如履薄冰。因為之前支付系統經常出問題,現在各個BG對支付系統都盯得很緊。這次要不是我及時讓DB給暴力清理資料,沒準又提一個P2故障;
抱怨歸抱怨,事後覆盤,一絲都不能馬虎。首先,描述一下故障的全過程。起因是我們支付系統有一個非同步佇列,這個佇列使用的一張mysql表儲存,非同步回撥業務線的任務(姑且表名稱叫task),都會首先放這裡。同時這個task表還有其他的非同步任務,不同的任務使用task_type欄位來進行區分。然後應用系統有一個定時任務,掃描這張表是否有待消費的任務,如果有,則會取出來進行消費;典型的生產者消費者模型;
這裡的task說的再具體一點:
1、所有的非同步任務都在這張表,有支付成功通知業務線訊息,有給結算系統推送支付資訊的任務;
2、消費者在任務處理成功後,則會把任務從task表刪除。所以這張表經常是空的;
消費者根據不同的任務,呼叫不同的上游訂單系統和結算系統。出故障時,是因為推送支付資訊的結算系統介面超時,出了問題,導致任務被積壓到了task表。
任務積壓之後,消費者執行緒池很快就被積壓的任務佔滿,導致應該通知BG訂單支付成功的任務也被block住,進而影響到訂單支付成功率。
當時我已經意識到,我們的消費者執行緒池隔離沒有做到位,立刻找DBA將推送給結算系統的任務進行了備份並清理。並且囑咐DBA定時清理推送結算任務的資料。這樣才化解支付成功率繼續下滑的趨勢。
危機解除後,我們和DBA配合進一步排查問題,找出了事情的根本原因。
原來是推送結算資訊的邏輯中,有一個對task表的查詢,而這個查詢的sql,沒有建索引。這樣當這類任務數量積壓的比較多時,查詢會越來越慢,慢查導致mysql堵塞。堵塞導致消費者無法拉取任務,進而影響到其他通知BG的任務的消費;我們分析了一下日誌,其實我們的程式查詢數量當時3分鐘大概查詢了1萬多次,可以說qps不多。但是問題出現在sql無法命中索引,把mysql的worker thread都用完了。給我們研發的感覺,mysql是如此的脆弱,2w多條資料,查詢沒有索引,幾千個select,就能把它打掛。
幾乎類似的案例,一年前,我們也碰到過一次。當時支付系統有一個bug,使用者每支付一次,都會把支付客史中一個月之前的資料都清理一下(1月1日,清12月1日之前的資料,2月1清理1月1日之前的資料)。這個bug藏的很深,這塊程式碼也很少有業務需求,一直沒有被發現。但是,是雷就會有爆炸的一天。3月1日凌晨,支付系統突然所有介面都掛了。DBA最終定位是刪除支付客史的sql。這個問題,我們研發一開始是不承認的,畢竟這個sql,線上上跑了2年多,一直沒有出過問題。DBA說這個delete語句刪3000w資料,而且在不斷的請求,資料庫當然扛不住,我們反駁說,這個客史表一共才3000w資料。事後我們發現,DBA的描述有誤,不是說刪除3000w,而是這個delete語句沒有走索引,每次要掃描3000w資料。這樣才能解釋通,也就是說,這個delete進行了全表掃描。而實際上,這個delete是按照時間刪除的,並且時間欄位是有單列索引的,但是為什麼這個delete沒有走索引呢?我們最後猜測,可能是因為2月份天數太少導致。以前,可能資料比較少,每次刪一天,或者2天的資料,mysql可能會走索引。但是3月1日比較特殊,因2月28日刪的是1月28日一天的資料,3月1日卻要刪除1月29,30,31三天的資料,mysql可能認為刪除這麼多資料,沒有必要走索引了。
遇到類似問題如何解決?
1、讀寫分離。
2、提前消滅慢查詢;
3、對非同步任務做好執行緒隔離;
關於mysql的執行緒池,我最近也瞭解了一下,收穫也不小,給大家推薦一篇好文章;
https://www.jianshu.com/p/88e606eca2a5
關注我的公眾號“猿界汪汪隊”,關注大併發架構實戰。