出處:https://mp.weixin.qq.com/s?__biz=MzI1NzM3NTYxMw==&mid=2247483685&idx=1&sn=95c8bb46388dc0e40b8c48c508b34008&scene=1&srcid=0802bE2wuDrKD9zWy1zlsSJx#rdhtml
https://mp.weixin.qq.com/s?src=3×tamp=1571033580&ver=1&signature=o3nEQrvNvSysQY*mEsaHtsDoGhjvUcM9vkrNbsqk1Wy9VbEIdC-a7yRBSPwz6EMlBUDfPNUMxlFesS7kSW29A4SeTqKjF0n22TBp9r8lCe44YxSjclmFDCx9*jOk1s8YKd0zbtmHffUfF3ccbxAHs5U5nth6tfXvGpS6-KY3I30=服務器
一 問題現象cookie
本次故障的反饋現象是:從辦公網訪問公網服務器不穩定,服務器某些端口訪問常常超時,但Ping測試顯示客戶端與服務器的鏈路始終是穩定低延遲的。網絡
經過在服務器端抓包,咱們發現還有兩個特色:app
從辦公網訪問服務器有多個客戶端,是同一個出口IP,有少部分是始終可以穩定鏈接的,另外一部分間歇訪問超時或延遲很高tcp
同一時刻的訪問,不管哪一個客戶端的數據包先到達,服務端會及時處理部分客戶端的SYN請求,對另外一部分客戶端的SYN包「視而不見」工具
如何排查?怎樣解決?測試
服務器能正常接收到數據包,問題能夠限定在兩種可能:優化
部分客戶端發出的數據包自己異常;.net
服務器處理部分客戶端的數觸發了某種機制丟棄了數據包。
由於出問題的客戶端可以正常訪問公網上其餘服務,後者的可能性更大。
那麼,有哪些狀況會致使Linux服務器丟棄數據包?
============================================ 可能緣由一 ========================================
可能緣由一:防火牆攔截
服務器端口沒法鏈接,一般就是查看防火牆配置了,雖然這裏已經確認同一個出口IP的客戶端有的可以正常訪問,但也不排除配置了DROP特定端口範圍的可能性。
--->如何確認
查看iptables Filter表,確認是否有相應規則會致使此丟包行爲,容易排除防火牆攔截的可能性。
============================================可能緣由二========================================
可能緣由二:鏈接跟蹤表溢出
除了防火牆自己配置DROP規則外,與防火牆有關的還有鏈接跟蹤表nf_conntrack,Linux爲每一個通過內核網絡棧的數據包,生成一個新的鏈接記錄項,當服務器處理的鏈接過多時,鏈接跟蹤表被打滿,服務器會丟棄新建鏈接的數據包。
如何確認
經過dmesg能夠確認是否有該狀況發生:若是輸出值中有「nf_conntrack: table full, dropping packet」,說明服務器nf_conntrack表已經被打滿。
經過conntrack工具或/proc文件系統查看nf_conntrack表實時狀態:在本案例中,當前鏈接數遠沒有達到跟蹤表最大值,所以排除這個因素。
如何解決
若是確認服務器因鏈接跟蹤表溢出而開始丟包,首先須要查看具體鏈接判斷是否正遭受DOS攻擊,若是是正常的業務流量形成,則能夠考慮調整nf_conntrack的參數:
nf_conntrack_max決定鏈接跟蹤表的大小,默認值是65535,能夠根據系統內存大小計算一個合理值:CONNTRACK_MAX = RAMSIZE(in bytes)/16384/(ARCH/32),如32G內存能夠設置1048576
nf_conntrack_buckets決定存儲conntrack條目的哈希表大小,默認值是nf_conntrack_max的1/4,延續這種計算方式:BUCKETS = CONNTRACK_MAX/4,如32G內存能夠設置262144
nf_conntrack_tcp_timeout_established決定ESTABLISHED狀態鏈接的超時時間,默認值是5天,能夠縮短到1小時,即3600
============================================可能緣由三========================================
可能緣由三:Ring Buffer溢出
排除了防火牆的因素,咱們從底向上來看Linux接收數據包的處理過程,首先是網卡驅動層。
以下圖所示,物理介質上的數據幀到達後首先由NIC(網絡適配器)讀取,寫入設備內部緩衝區Ring Buffer中,再由中斷處理程序觸發Softirq從中消費,Ring Buffer的大小因網卡設備而異。當網絡數據包到達(生產)的速率快於內核處理(消費)的速率時,Ring Buffer很快會被填滿,新來的數據包將被丟棄。
如何確認
經過ethtool -S指令或查看/proc/net/dev能夠獲得因Ring Buffer滿而丟棄的包統計,在統計項中以fifo標識:本案例中服務器的接收方向的fifo丟包數並無增長,天然也排除這個緣由。
如何解決
若是發現服務器上某個網卡的fifo數持續增大,能夠去確認CPU中斷是否分配均勻,也能夠嘗試增長Ring Buffer的大小,經過ethtool -g eth0能夠查看網卡設備Ring Buffer最大值,ethtool -G修改Ring Buffer當前設置。
============================================可能緣由四========================================
可能緣由四:netdev_max_backlog溢出
netdev_max_backlog是內核從NIC收到包後,交由協議棧(如IP、TCP)處理以前的緩衝隊列。每一個CPU核都有一個backlog隊列,與Ring Buffer同理,當接收包的速率大於內核協議棧處理的速率時,CPU的backlog隊列不斷增加,當達到設定的netdev_max_backlog值時,數據包將被丟棄。
如何確認
經過查看/proc/net/softnet_stat能夠肯定是否發生了netdev backlog隊列溢出。其中:
每一行表明每一個CPU核的狀態統計,從CPU0依次往下
每一列表明一個CPU核的各項統計:第一列表明中斷處理程序收到的包總數;第二列即表明因爲netdev_max_backlog隊列溢出而被丟棄的包總數
在本案例的服務器統計中,並無由於netdev_max_backlog致使的丟包。
如何解決
netdev_max_backlog的默認值是1000,在高速鏈路上,可能會出現上述第二列統計不爲0的狀況,能夠適當調大內核參數net.core.netdev_max_backlog到2000來解決。
============================================可能緣由五========================================
可能緣由五:反向路由過濾
反向路由過濾機制是Linux經過反向路由查詢,檢查收到的數據包源IP是否可路由(Loose mode)、是否最佳路由(Strict mode),若是沒有經過驗證,則丟棄數據包,設計的目的是防範IP地址欺騙攻擊。rp_filter提供了三種模式供配置:
0 - 不驗證
1 - RFC3704定義的嚴格模式:對每一個收到的數據包,查詢反向路由,若是數據包入口和反向路由出口不一致,則不經過
2 - RFC3704定義的鬆散模式:對每一個收到的數據包,查詢反向路由,若是任何接口都不可達,則不經過
如何確認
查看當前rp_filter策略配置,若是設置爲1,就須要查看主機的網絡環境和路由策略是否可能會致使客戶端的入包沒法經過反向路由驗證。
從原理來看這個機制工做在網絡層,所以,若是客戶端可以Ping通服務器,就可以排除這個因素了。
如何解決
根據實際網絡環境將rp_filter設置爲0或2。
============================================可能緣由六========================================
半鏈接隊列指的是TCP傳輸中服務器收到SYN包但還未完成三次握手的鏈接隊列,隊列大小由內核參數tcp_max_syn_backlog定義。
當服務器保持的半鏈接數量達到tcp_max_syn_backlog後,內核將會丟棄新來的SYN包。
如何確認
經過dmesg能夠確認是否有該狀況發生:若是輸出值中有「TCP: drop open request from」,說明半鏈接隊列已被打滿。
半鏈接隊列的鏈接數量能夠經過netstat統計SYN_RECV狀態的鏈接得知。大多數狀況下這個值應該是0或很小,由於半鏈接狀態從第一次握手完成時進入,第三次握手完成後退出,正常的網絡環境中這個過程發生很快,若是這個值較大,服務器極有可能受到了SYN Flood攻擊。
如何解決
tcp_max_syn_backlog的默認值是256,一般推薦內存大於128MB的服務器能夠將該值調高至1024,內存小於32MB的服務器調低到128,一樣,該參數經過sysctl修改。
另外,上述行爲受到內核參數tcp_syncookies的影響,若啓用syncookie機制,當半鏈接隊列溢出時,並不會直接丟棄SYN包,而是回覆帶有syncookie的SYC+ACK包,設計的目的是防範SYN Flood形成正常請求服務不可用。
============================================可能緣由七========================================
可能緣由七:PAWS :內核參數/proc/sys/net/ipv4/tcp_tw_recycle 控制
PAWS全名Protect Againest Wrapped Sequence numbers,目的是解決在高帶寬下,TCP序列號在一次會話中可能被重複使用而帶來的問題。
如上圖所示,客戶端發送的序列號爲A的數據包A1因某些緣由在網絡中「迷路」,在必定時間沒有到達服務端,客戶端等待ACK超時後重發序列號爲A的數據包A2,接下來的通訊用盡了序列號空間,從新回到A。此時,服務端等待的是序列號爲A的數據包A3,而恰巧此時前面「迷路」的A1到達服務端,若是服務端僅靠序列號A就判斷數據包合法,就會將錯誤的數據傳遞到用戶態程序,形成程序異常。
PAWS要解決的就是上述問題,它依賴於timestamp機制,理論依據是:在一條正常的TCP流中,按序接收到的全部TCP數據包中的timestamp都應該是單調非遞減的,這樣就能判斷那些timestamp小於當前TCP流已處理的最大timestamp值的報文是延遲到達的重複報文,能夠予以丟棄。在上文的例子中,服務器已經處理數據包Z,然後到來的A1包的timestamp必然小於Z包的timestamp,所以服務端會丟棄遲到的A1包,等待正確的報文到來。
PAWS機制的實現關鍵是內核保存了Per-Connection的最近接收時間戳,若是加以改進,就能夠用來優化服務器TIME_WAIT狀態的快速回收。
TIME_WAIT狀態是TCP四次揮手中主動關閉鏈接的一方須要進入的最後一個狀態,而且一般須要在該狀態保持2*MSL(報文最大生存時間),它存在的意義有兩個:
可靠地實現TCP全雙工鏈接的關閉:關閉鏈接的四次揮手過程當中,最終的ACK由主動關閉鏈接的一方(稱爲A)發出,若是這個ACK丟失,對端(稱爲B)將重發FIN,若是A不維持鏈接的TIME_WAIT狀態,而是直接進入CLOSED,則沒法重傳ACK,B端的鏈接所以不能及時可靠釋放。
等待「迷路」的重複數據包在網絡中因生存時間到期消失:通訊雙方A與B,A的數據包因「迷路」沒有及時到達B,A會重發數據包,當A與B完成傳輸並斷開鏈接後,若是A不維持TIME_WAIT狀態2*MSL時間,便有可能與B再次創建相同源端口和目的端口的「新鏈接」,而前一次鏈接中「迷路」的報文有可能在這時到達,並被B接收處理,形成異常,維持2*MSL的目的就是等待前一次鏈接的數據包在網絡中消失。
TIME_WAIT狀態的鏈接須要佔用服務器內存資源維持,Linux內核提供了一個參數來控制TIME_WAIT狀態的快速回收:tcp_tw_recycle,它的理論依據是:
在PAWS的理論基礎上,若是內核保存Per-Host的最近接收時間戳,接收數據包時進行時間戳比對,就能避免TIME_WAIT意圖解決的第二個問題:前一個鏈接的數據包在新鏈接中被當作有效數據包處理的狀況。這樣就沒有必要維持TIME_WAIT狀態2*MSL的時間來等待數據包消失,僅須要等待足夠的RTO(超時重傳),解決ACK丟失須要重傳的狀況,來達到快速回收TIME_WAIT狀態鏈接的目的。
但上述理論在多個客戶端使用NAT訪問服務器時會產生新的問題:同一個NAT背後的多個客戶端時間戳是很難保持一致的(timestamp機制使用的是系統啓動相對時間),對於服務器來講,兩臺客戶端主機各自創建的TCP鏈接表現爲同一個對端IP的兩個鏈接,按照Per-Host記錄的最近接收時間戳會更新爲兩臺客戶端主機中時間戳較大的那個,而時間戳相對較小的客戶端發出的全部數據包對服務器來講都是這臺主機已過時的重複數據,所以會直接丟棄。
如何確認
經過netstat -s能夠獲得因PAWS機制timestamp驗證被丟棄的數據包統計,其中的「passive connections rejected because of time stamp」和「packets rejects in established connections because of timestamp」分別表明了因時間戳驗證失敗而拒絕的鏈接數統計和已有鏈接中拒絕的包數量統計。
經過sysctl查看是否啓用了tcp_tw_recycle和tcp_timestamp,若是這兩個選項同時開啓,則有可能會致使上述現象。咱們此次的問題正是由於服務器作了這個配置,而客戶端正是使用NAT來訪問服務器,形成啓動時間相對較短的客戶端得不到服務器的正常響應。
如何解決
若是服務器做爲服務端提供服務,且明確客戶端會經過NAT網絡訪問,或服務器以前有7層轉發設備會替換客戶端源IP時,是不該該開啓tcp_tw_recycle的,而timestamps除了支持tcp_tw_recycle外還被其餘機制依賴,推薦繼續開啓。
操做:
臨時設置生效:echo 0 > /proc/sys/net/ipv4/tcp_tw_recycle
永久生效修改配置文件:echo ‘net.ipv4.tcp_tw_recycle = 0’ >> /etc/sysctl.conf ; sysctl -p