包拯斷案 | create connections failed的深度剖析 還故障一個真相

萬里資料庫發表於2023-03-10

作為DBA運維的你是否有過這些苦惱



1)什麼?應用又連不上資料庫了?剛剛不是還好好的嗎,怎麼又不行了?


對資料庫一頓操作猛如虎,最後發現還是重啟好使,拍著胸脯對leader說okay,沒問題啦!結果悲劇如期上演,打臉的速度趕上了找地縫的速度,此時誰來help me呢?




2)有時候出去面試,明明感覺對面試官的問題答得挺好,但面試完又沒後續,上百度一逛,答案沒問題呀。


長此以往,心中天天萬馬奔騰,疑惑+鬱悶+容嬤嬤附體的心中不停在問 why? Why? Why?




面試官:應用連線資料庫無法建立新的連線,請給出你的排查思路。


應聘者:可以透過show processlist檢視一下當前會話,然後檢查一下max_connections的引數,看一下wait_timeout空閒會話的超時時間,如果當前會話已經達到max_connections的最大值,則連線會話滿了;


面試官:如果當前空閒會話沒有達到最大值,但是仍然無法建立連線,該從何處下手排查?


應聘者:啥?啥?啥?弄啥嘞,我回答得還不全面嗎?面試官腦子xx了嗎?




其實站在面試官的角度看,也合乎情理。


為啥嘞?細想一下:




如果面試官問了你一個故障現象,而不是一個具體的報錯資訊,那他其實是想看你的知識儲備和是否有豐富的運維經驗,是否能勝任複雜的運維工作。如果你回答的是百度上隨處可見的答案,面試官聽了也會覺得耽誤他的時間,因此我們如何才能讓面試官兩眼放光呢?


答案顯而易見:除了美貌之外剩下的就是才華了~






 1、心中有章,遇事不慌



作為DBA的你,遇到問題無從下手,除了在問題面前徘徊,又能如何選擇?如果你第一次(或多次)遇到該問題還是無法解決,又很懊惱,該如何排憂呢?


## 2023包拯斷案系列之包拯秘籍


✨整套故障排錯及應對策略送給你,讓你像包拯一樣斷案如神:


#首先


遇到此類問題後 我們要做到心中有章(章程),遇事不慌。一定要冷靜,仔細瞭解故障現象(和研發或使用者反饋的問題仔細溝通故障現象、操作流程、資料庫架構等資訊)


#其次


我們要根據故障現象進行初步分析。心中要想什麼情況會導致會話建立失敗?例如:資料庫本身限制了連線數的大小?作業系統資源限制了連線數的建立?網路限制給阻塞掉了?


#然後


針對上述的思考,我們需要進行逐步驗證並排除,確定問題排查方向。


#接著


確定了問題方向,進行具體分析。透過現象得出部分結論,透過部分結論繼續排查及論證。


#最後


針對問題有了具體分析,並進行線下復現,從而梳理故障報告。



2、真刀實戰,我們能贏





說了這麼多理論,想必實戰更讓你心動。那我們就拿一個真實案例進行分析——當應用連線資料庫失敗,導致業務受阻,該如何進行快速分析處理:




-2.1 故障處理場景


(加班不是福報,摸魚才是王道。)


突然的資訊打破了正在划水的你,什麼!???


⚠️ 突然收到一條某客戶的資料庫例項DBA運維人員發來的資訊:應用無法執行sql語句,業務卡頓!






瞬間警醒,慌得一匹,資料庫down啦?記憶體滿啦?無數個【可能】從腦海中閃過……




收到資訊10s後:聯絡客戶的DBA運維人員,溝通具體的故障資訊場景,並指導後續操作


• 大腦回復:開發人員反饋,某個介面異常,檢視日誌發現連線資料庫執行sql語句失敗;






收到資訊20s後:排查當前資料庫執行狀態


• 傳送命令:


netstat -lntup|grep `pidof greatdb` || ps   -aux|grep `pidof greatdb`;



• 大腦回復:當前資料庫執行狀態正常;






收到資訊30s後:使用客戶端手動連線資料庫進行本地連線測試


• 傳送命令:


greatdb -uxxx -pxxx -h127.0.0.1 -P xxx -e “select 1”;



• 大腦回復:程式卡住,無法獲取到1的返回值;






收到資訊35s後:檢視系統的資源限制


• 傳送命令:


ulimit -a;



• 大腦回復: 檔案控制程式碼軟硬連線限制都是1024;






收到資訊40s後:檢視檔案控制程式碼限制


• 傳送命令:


cat   /etc/security/limits.conf  &&   cat/etc/security/limits.d/20-nproc.conf;



• 大腦回復:無任何配置;






收到資訊45s後:檢視當前程式的資源限制


• 傳送命令:


cat /proc/`pidof greatdb`/limits|grep "Max   open files";



• 大腦回復:檔案控制程式碼軟硬連線限制都是1024;






收到資訊50s後:檢視當前資料庫使用了多少檔案控制程式碼,是否超限


• 傳送命令:


lsof -p `pidof greatdb`|wc -l  && lsof -p `pidof greatdb`|grep   TCP|wc -l;



• 大腦回復:目前總共佔用了1111個連線,TCP連線佔用的檔案控制程式碼是988個;






收到資訊55s後:檢視當前作業系統允許的最大檔案控制程式碼數


• 傳送命令:


cat /proc/sys/fs/file-max;



• 大腦回復:當前作業系統最大允許開啟789968個檔案控制程式碼數;






收到資訊60s後:確認一下該服務的tcp套接字


• 傳送命令:


netstat -alntup|grep IP:Port|wc -l ;



• 大腦回復:當前的連線已經佔用檔案控制程式碼達到了1000 ;




(故障已定位並反饋,進行相應的應急處理...)






處理手段:


關閉非業務應用的資料庫訪問,釋放部分資源,讓業務正常執行(主要保證正在執行的DML),修改limit限制,重啟資料庫。






經過上述一頓猛如虎的操作和排查,1-2分鐘內快速定位了問題原因,且及時和客戶業務方進行了溝通,最大化地減少並避免了業務受到的影響。




同時,在故障排查過程中保留了排查步驟及結果圖,故障處理完成後進行故障報告編寫,全流程專業、順暢、有序的操作得到了客戶的認可與肯定。





本 期 復 盤 總 結





1)本次故障主要是因為底層環境導致的資料庫出現故障


2)指定資料庫執行環境及執行狀態的定期巡檢


3)增加相關引數的定期監控


4)根據巡檢項開發自動化運維指令碼並與告警相結合






## 番外篇-展昭答疑解惑



1)在排查問題時,為什麼沒有先看資料庫的錯誤日誌?




答:其一:由於時間緊迫,業務受到影響。如果有些error日誌因為歷史原因沒有做割接,那麼載入>1G的檔案也是比較耗時的;其二:影響連線建立的情況常見的就那幾種,我們透過測試驗證即可用排查法快速定位問題方向。




2)遇到此類問題,為什麼監控沒有提前告警?




答:其一:監控是有延遲性的,針對爆發性的問題無法處理;其二:監控項檢測多少,對資料庫本身也是一種負擔。




3)為什麼此情況就是檔案控制程式碼問題,而不是連線數打滿?




答:在前面手動驗證時透過問題表現就已知曉,當連線數打滿時客戶端會報too many connections錯誤。




4)建立連線的影響因素有很多,如何排除不是網路問題?




答:在手動驗證時使用的是127.0.0.1的本地連線,此時連線都不正常,說明是資料庫本機的問題,間接性排除了網路問題。







業務模擬程式碼:


```
```sql
```powershell
```powershell
```sql
package main
import (
        "database/sql"
        "fmt"
        _ "github.com/go-sql-driver/mysql"   //go連線greatdb的驅動
        "sync"
)
var wg sync.WaitGroup
func openDb(jdbc string) {
        db, err := sql.Open("mysql", jdbc)    //go連線greatdb的驅動型別,由於載入的驅動是MySQL,所以此處填寫的是mysql,jdbc是資料庫的連線串
        if err != nil {
                fmt.Println(err)
        }
        if err = db.Ping(); err != nil {
                fmt.Println("(0) database connection fail. Error Info: ", err)
        }
        db.SetMaxIdleConns(100)
        db.SetMaxOpenConns(100)
        db.SetConnMaxLifetime(-1)
        db.SetConnMaxIdleTime(-1)
        _, err = db.Exec("select sleep(100);")
        if err != nil {
                fmt.Println(err)
        }
        wg.Done()
}
func main() {
        jdbc := fmt.Sprintf("%s:%s@tcp(%s:%s)/information_schema?charset=utf8", "使用者名稱", "密碼", "資料庫ip", "資料庫埠")
        for i := 0; i < 1000; i++ {
                wg.Add(1)
                go openDb(jdbc)
        }
        wg.Wait()
```


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69997641/viewspace-2939036/,如需轉載,請註明出處,否則將追究法律責任。

相關文章