1. 程式人生 > >記一次APP和DB間流量異常問題的排查

記一次APP和DB間流量異常問題的排查

異常流量分析 tcpdump抓取異常流量 wireshark流量分析 應用不定時異常流量分析

###情景
通過zabbix監控發現有一個應用和數據庫之間存在不定時的流量異常(也不頻繁),具體為應用server的入向流量和數據庫server的出向流量會有短時間(通常在一分鐘左右)的激增,甚至快達到千兆網卡的傳輸上限。
###分析過程
通過癥狀,幾乎可以斷定是由於某些sql語句需要返回大量數據導致。但這次問題的分析結果確是很不順利(其中有我前期方法不當的原因)。

初次分析:因為在那之前做了一個針對於故障/異常分析的日誌分析系統(詳見),並且基於此完成了兩次異常uri的定位為題。所以慣性思維還是用這套東西來對流量異常時間段內出現的請求,按照出現次數或響應時間或者響應大小排序,觀察。折騰了一通,然這個方法就不是分析這個問題最直接的方法,自然分析不出啥結果。

在這期間也想到了tcpdump抓包,但是有兩個問題:因為異常時間不固定;不合適的方法會導致抓包產生的文件體積非常大。當時也沒相處好方法來抓包,所以就沒再深究這個想法。

再次分析:仔細想還是抓包才是最直接的方法。於是寫了一個腳本,在應用server上每隔5s計算一下之前5s的入向流量的帶寬,根據應用和數據庫之間正常的流量設置一個閾值,超過次閾值則觸發抓包動作。這次是能看到一部分異常的流量了,但是由於這個腳本的機制是先判斷流量再觸發抓包,所以先天缺陷是無法抓到引發流量異常的sql的。

最終分析,大腦經過一夜的後臺運行,終於想到了一個“完美”的抓包方法,大致思路如下:

開始抓包,通過tcpdump的-w 參數將結果寫到文件。然後在死循環裏每隔5s計算一下前5s的平均流量,跟閾值相比:

如果小於閾值,則kill掉之前的tcpdump進行,重新開始一個tcpdump,抓包數據還寫到先前的文件中(會覆蓋掉之前內容,解決了抓包文件不斷變大的問題);
如果大於閾值,則繼續抓包,下一次循環再檢測流量,若下一個5s周期流量降到了閾值之內,則kill掉tcpdump進程,同時將抓包文件重命名(這個是可以用來分析的有效抓包數據),然後再開啟新的tcpdump重復之前的動作。

來看一下具體腳本(這個腳本是整個過程的關鍵)

#!/bin/sh
#by ljk 2017/03/18
#對於不定期出現的網絡流量異常進行合理抓包

file=/proc/net/dev
i=0    #用來標記是否出現了流量異常以及異常持續了幾個檢測周期
cd /usr/local/src/mysql-sniffer/logs/query/
alias capture=‘tcpdump -nnvv port 3306 -w tcpdump.txt &>/dev/null‘

(capture) &    #放到後臺執行,不至於阻塞主進程的邏輯

while true;do
    RX_bytes=`cat $file|grep eth0|sed ‘s/^ *//g‘|awk -F‘[ :]+‘ ‘{print $2}‘`
    sleep 10    #間隔10s
    RX_bytes_later=`cat $file|grep eth0|sed ‘s/^ *//g‘|awk -F‘[ :]+‘ ‘{print $2}‘`
    speed_RX=`echo "scale=0;($RX_bytes_later - $RX_bytes)*8/1024/1024/10"|bc`

    tcpdump_pid=`ps -ef|grep tcpdump|grep -v grep|awk ‘{print $2}‘`

    if [ $speed_RX -lt 15 ];then    #我的閾值是15Mb
        kill -9 $tcpdump_pid
        if [ $i -gt 0 ];then
            mv tcpdump.txt "$i"_tcpdump_`date  +"%F_%H:%M:%S"`
        fi

        (capture) &
        i=0
    else
        i=$(($i+1))
    fi
done

把該腳本放到後臺運行,然後我很輕松的抓取到了一次異常周期內的數據包,然後通過wireshark進行分析,也很快的找到了問題sql,不出所料,就是因為對用戶輸入的驗證不夠嚴謹,導致where條件裏出現了空字串進而需要返回將近全表的數據(真坑)。

記一次APP和DB間流量異常問題的排查