Linux上TCP的幾個內核參數調優

Linux做爲一個強大的操做系統,提供了一系列內核參數供咱們進行調優。光TCP的調優參數就有50多個。在和線上問題鬥智鬥勇的過程當中,筆者積累了一些在內網環境應該進行調優的參數。在此分享出來,但願對你們有所幫助。java

調優清單

好了,在這裏先列出調優清單。請記住,這裏只是筆者在內網進行TCP內核參數調優的經驗,僅供參考。同時,筆者還會在餘下的博客裏面詳細解釋了爲何要進行這些調優!golang

序號 內核參數 備註
1.1 /proc/sys/net/ipv4/tcp_max_syn_backlog 2048
1.2 /proc/sys/net/core/somaxconn 2048
1.3 /proc/sys/net/ipv4/tcp_abort_on_overflow 1
2.1 /proc/sys/net/ipv4/tcp_tw_recycle 0 NAT環境必須爲0
2.2 /proc/sys/net/ipv4/tcp_tw_reuse 1
3.1 /proc/sys/net/ipv4/tcp_syn_retries 3
3.2 /proc/sys/net/ipv4/tcp_retries2 5
3.3 /proc/sys/net/ipv4/tcp_slow_start_after_idle 0

tcp_max_syn_backlog,somaxconn,tcp_abort_on_overflow

tcp_max_syn_backlog,somaxconn,tcp_abort_on_overflow這三個參數是關於
內核TCP鏈接緩衝隊列的設置。若是應用層來不及將已經三次握手創建成功的TCP鏈接從隊列中取出,溢出了這個緩衝隊列(全鏈接隊列)以後就會丟棄這個鏈接。以下圖所示:

從而產生一些詭異的現象,這個現象詭異之處就在於,是在TCP第三次握手的時候丟棄鏈接

就如圖中所示,第二次握手的SYNACK發送給client端了。因此就會出現client端認爲鏈接成功,而Server端確已經丟棄了這個鏈接的現象!因爲沒法感知到Server已經丟棄了鏈接。
因此若是沒有心跳的話,只有在發出第一個請求後,Server纔會發送一個reset端通知這個鏈接已經被丟棄了,創建鏈接後次日再用,也會報錯!因此咱們要調大Backlog隊列!網絡

echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog
echo 2048 > /proc/sys/net/core/somaxconn

固然了,爲了儘可能避免第一筆調用失敗問題,咱們也同時要設置socket

echo 1 > /proc/sys/net/ipv4/tcp_abort_on_overflow

設置這個值之後,Server端內核就會在這個鏈接被溢出以後發送一個reset包給client端。

若是咱們的client端是NIO的話,就能夠收到一個socket close的事件以感知到鏈接被關閉!
tcp

注意Java默認的Backlog是50

這個TCP Backlog的隊列大小值是min(tcp_max_syn_backlog,somaxconn,應用層設置的backlog),而Java若是不作額外設置,Backlog默認值僅僅只有50。C語言在使用listen調用的時候須要傳進Backlog參數。ide

tcp_tw_recycle

tcp_tw_recycle這個參數通常是用來抑制TIME_WAIT數量的,可是它有一個反作用。即在tcp_timestamps開啓(Linux默認開啓),tcp_tw_recycle會常常致使下面這種現象。

也即,若是你的Server開啓了tcp_tw_recycle,那麼別人若是經過NAT之類的調用你的Server的話,NAT後面的機器只有一臺機器能正常工做,其它狀況大機率失敗。具體緣由呢由下圖所示:

在tcp_tw_recycle=1同時tcp_timestamps(默認開啓的狀況下),對同一個IP的鏈接會作這樣的限制,也即以前後創建的鏈接的時間戳必需要大於以前創建鏈接的最後時間戳,可是通過NAT的一個IP後面是不一樣的機器,時間戳相差極大,就會致使內核直接丟棄時間戳較低的鏈接的現象。因爲這個參數致使的問題,高版本內核已經去掉了這個參數。若是考慮TIME_WAIT問題,能夠考慮設置一下函數

echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse

tcp_syn_retries

這個參數值得是client發送SYN若是server端不回覆的話,重傳SYN的次數。對咱們的直接影響呢就是connet創建鏈接時的超時時間。固然Java經過一些C原生系統調用的組合使得咱們能夠進行超時時間的設置。在Linux裏面默認設置是5,下面給出建議值3和默認值5之間的超時時間。微服務

tcp_syn_retries timeout
1 min(so_sndtimeo,3s)
2 min(so_sndtimeo,7s)
3 min(so_sndtimeo,15s)
4 min(so_sndtimeo,31s)
5 min(so_sndtimeo,63s)

下圖給出了,重傳和超時狀況的對應圖:

固然了,不一樣內核版本的超時時間可能不同,由於初始RTO在內核小版本間都會有細微的變化。因此,有時候在抓包時候可能會出現(3,6,12......)這樣的序列。固然Java的API有超時時間:測試

java: // 函數調用中攜帶有超時時間
 public void connect(SocketAddress endpoint, int timeout) ;

因此,對於Java而言,這個內核參數的設置沒有那麼重要。可是,有些代碼可能會有忘了設置timeout的狀況,例如某個版本的Kafka就是,因此它在咱們一些混沌測試的狀況下,容災恢復的時間會達到一分多鐘,主要時間就是卡在connect上面-_-!,而這時咱們的tcp_syn_retries設置的是5,也即超時時間63s。減小這個恢復時間的手段就是:操作系統

echo 3 > /proc/sys/net/ipv4/tcp_syn_retries

tcp_retries2

tcp_retries2這個參數表面意思是在傳輸過程當中tcp的重傳次數。但在某個版本以後Linux內核僅僅用這個tcp_retries2來計算超時時間,在這段時間的重傳次數純粹由RTO等環境因素決定,重傳超時時間在5/15下的表現爲:

tcp_retries2 對端無響應
5 25.6s-51.2s根據動態rto定
15 924.6s-1044.6s根據動態rto定

若是咱們在應用層設置的Socket全部ReadTimeout都很小的話(例如3s),這個內核參數調整是沒有必要的。可是,筆者常常發現有的系統,由於一兩個慢的接口或者SQL,因此將ReadTimeout設的很大的狀況。

日常這種狀況是沒有問題的,由於慢請求頻率很低,不會對系統形成什麼風險。可是,物理機忽然宕機時候的狀況就不同了,因爲ReadTimeOut設置的過大,致使全部落到這臺宕機的機器都會在min(ReadTimeOut,(924.6s-1044.6s)(Linux默認tcp_retries2是15))後才能從read系統調用返回。假設ReadTimeout設置了個5min,系統總線程數是200,那麼只要5min內有200個請求落到宕機的server就會使A系統失去響應!

但若是將tcp_retries2設置爲5,那麼超時返回時間即爲min(ReadTimeOut 5min,25.6-51.2s),也就是30s左右,極大的緩解了這一狀況。

echo 5 > /proc/sys/net/ipv4/tcp_retries2

可是針對這種現象,最好要作資源上的隔離,例如線程上的隔離或者機器級的隔離。

golang的goroutine調度模型就能夠很好的解決線程資源不夠的問題,但缺點是goroutine裏面不能有阻塞的系統調用,否則也會和上面同樣,但僅僅對於系統之間互相調用而言,都是非阻塞IO,因此golang作微服務仍是很是Nice的。固然了我大Java用純IO事件觸發編寫代碼也不會有問題,就是對心智負擔過高-_-!

物理機忽然宕機和進程宕不同

值得注意的是,物理機宕機和進程宕但內核還存在表現徹底不同。

僅僅進程宕而內核存活,那麼內核會立馬發送reset給對端,從而不會卡住A系統的線程資源。

tcp_slow_start_after_idle

還有一個可能須要調整的參數是tcp_slow_start_after_idle,Linux默認是1,即開啓狀態。開啓這個參數後,咱們的TCP擁塞窗口會在一個RTO時間空閒以後重置爲初始擁塞窗口(CWND)大小,這無疑大幅的減小了長鏈接的優點。對應Linux源碼爲:

static void tcp_event_data_sent(struct tcp_sock *tp,				struct sk_buff *skb, struct sock *sk){	// 若是開啓了start_after_idle,並且此次發送的時間-上次發送的時間>一個rto,就重置tcp擁塞窗口
	if (sysctl_tcp_slow_start_after_idle &&
	    (!tp->packets_out && (s32)(now - tp->lsndtime) > icsk->icsk_rto))
		tcp_cwnd_restart(sk, __sk_dst_get(sk));
}


關閉這個參數後,無疑會提升某些請求的傳輸速度(在帶寬夠的狀況下)。

echo 0 > /proc/sys/net/ipv4/tcp_slow_start_after_idle

固然了,Linux啓用這個參數也是有理由的,若是咱們的網絡狀況是時刻在變化的,例如拿個手機處處移動,那麼將擁塞窗口重置確實是個不錯的選項。可是就咱們內網系統間調用而言,是不太必要的了。

初始CWND大小

毫無疑問,新建鏈接以後的初始TCP擁塞窗口大小也直接影響到咱們的請求速率。在Linux2.6.32源碼中,其初始擁塞窗口是(2-4個)mss大小,對應於內網估計也就是(2.8-5.6K)(MTU 1500),這個大小對於某些大請求可能有點捉襟見肘。
在Linux 2.6.39以上或者某些RedHat維護的小版本中已經把CWND
增大到RFC 6928所規定的的10段,也就是在內網裏面估計14K左右(MTU 1500)。

Linux 新版本/* TCP initial congestion window */#define TCP_INIT_CWND		10

公衆號

關注筆者公衆號,獲取更多幹貨文章

總結

Linux提供了一大堆內參參數供咱們進行調優,其默認設置的參數在不少狀況下並非最佳實踐,因此咱們須要潛心研究,找到最適合當前環境的組合。

相關文章
相關標籤/搜索