高並發系統之大忌-慢查詢
最近又遇到了一次慢查把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