從TCP/IP協(xié)議談Linux內(nèi)核參數(shù)優(yōu)化
在硬件資源有限的情況下,最大的壓榨服務(wù)器性能,提高服務(wù)器的并發(fā)處理能力,是很多技術(shù)人員思考的問題,除了優(yōu)化Nginx/PHP-FPM/Mysql/Redis這類服務(wù)軟件配置外,還可以通過修改Linux的內(nèi)核相關(guān)TCP參數(shù),來最大的提高服務(wù)器性能。
在Linux內(nèi)核參數(shù)優(yōu)化之前,我們需要先搞懂TCP/IP協(xié)議,這是我們實(shí)施優(yōu)化的理論依據(jù)。
TCP/IP協(xié)議
TCP/IP協(xié)議是十分復(fù)雜的協(xié)議,完全掌握不是一件容易的事情,但作為基本知識(shí),我們必須知道TCP/IP協(xié)的三次握手和四次揮手的邏輯過程。
三次握手
所謂三次握手是指建立一個(gè) TCP 連接時(shí)需要客戶端和服務(wù)器端總共發(fā)送三個(gè)包以確認(rèn)連接的建立。在socket編程中,這一過程由客戶端執(zhí)行connect來觸發(fā)。
三次握手流程圖:

三次握手流程
第一次握手:客戶端將標(biāo)志位SYN置為1,隨機(jī)產(chǎn)生一個(gè)值seq=J,并將該數(shù)據(jù)包發(fā)送給服務(wù)器端,客戶端進(jìn)入SYN_SENT狀態(tài),等待服務(wù)器端確認(rèn)。
第二次握手:服務(wù)器端收到數(shù)據(jù)包后由標(biāo)志位SYN=1知道客戶端請(qǐng)求建立連接,服務(wù)器端將標(biāo)志位SYN和ACK都置為1,ack=J+1,隨機(jī)產(chǎn)生一個(gè)值seq=K,并將該數(shù)據(jù)包發(fā)送給客戶端以確認(rèn)連接請(qǐng)求,服務(wù)器端進(jìn)入SYN_RCVD狀態(tài)。
第三次握手:客戶端收到確認(rèn)后,檢查ack是否為J+1,ACK是否為1,如果正確則將標(biāo)志位ACK置為1,ack=K+1,并將該數(shù)據(jù)包發(fā)送給服務(wù)器端,服務(wù)器端檢查ack是否為K+1,ACK是否為1,如果正確則連接建立成功,客戶端和服務(wù)器端進(jìn)入ESTABLISHED狀態(tài),完成三次握手,隨后客戶端與服務(wù)器端之間可以開始傳輸數(shù)據(jù)了。
四次揮手
四次揮手即終止TCP連接,就是指斷開一個(gè)TCP連接時(shí),需要客戶端和服務(wù)端總共發(fā)送4個(gè)包以確認(rèn)連接的斷開。在socket編程中,這一過程由客戶端或服務(wù)端任一方執(zhí)行close來觸發(fā)。
由于TCP連接是全雙工的,因此,每個(gè)方向都必須要單獨(dú)進(jìn)行關(guān)閉,這一原則是當(dāng)一方完成數(shù)據(jù)發(fā)送任務(wù)后,發(fā)送一個(gè)FIN來終止這一方向的連接,收到一個(gè)FIN只是意味著這一方向上沒有數(shù)據(jù)流動(dòng)了,即不會(huì)再收到數(shù)據(jù)了,但是在這個(gè)TCP連接上仍然能夠發(fā)送數(shù)據(jù),直到這一方向也發(fā)送了FIN。首先進(jìn)行關(guān)閉的一方將執(zhí)行主動(dòng)關(guān)閉,而另一方則執(zhí)行被動(dòng)關(guān)閉。
四次揮手的流程圖:

四次揮手流程
- 中斷連接端可以是客戶端,也可以是服務(wù)器端。
- 第一次揮手:客戶端發(fā)送一個(gè)FIN=M,用來關(guān)閉客戶端到服務(wù)器端的數(shù)據(jù)傳送,客戶端進(jìn)入FIN_WAIT_1狀態(tài)。意思是說”我客戶端沒有數(shù)據(jù)要發(fā)給你了”,但是如果你服務(wù)器端還有數(shù)據(jù)沒有發(fā)送完成,則不必急著關(guān)閉連接,可以繼續(xù)發(fā)送數(shù)據(jù)。
- 第二次揮手:服務(wù)器端收到FIN后,先發(fā)送ack=M+1,告訴客戶端,你的請(qǐng)求我收到了,但是我還沒準(zhǔn)備好,請(qǐng)繼續(xù)你等我的消息。這個(gè)時(shí)候客戶端就進(jìn)入FIN_WAIT_2狀態(tài),繼續(xù)等待服務(wù)器端的FIN報(bào)文。
- 第三次揮手:當(dāng)服務(wù)器端確定數(shù)據(jù)已發(fā)送完成,則向客戶端發(fā)送FIN=N報(bào)文,告訴客戶端,好了,我這邊數(shù)據(jù)發(fā)完了,準(zhǔn)備好關(guān)閉連接了。服務(wù)器端進(jìn)入LAST_ACK狀態(tài)。
- 第四次揮手:客戶端收到FIN=N報(bào)文后,就知道可以關(guān)閉連接了,但是他還是不相信網(wǎng)絡(luò),怕服務(wù)器端不知道要關(guān)閉,所以發(fā)送ack=N+1后進(jìn)入TIME_WAIT狀態(tài),如果Server端沒有收到ACK則可以重傳。服務(wù)器端收到ACK后,就知道可以斷開連接了??蛻舳说却?MSL后依然沒有收到回復(fù),則證明服務(wù)器端已正常關(guān)閉,那好,我客戶端也可以關(guān)閉連接了。最終完成了四次握手。
序列號(hào)與確認(rèn)應(yīng)答
大家都知道TCP/IP協(xié)議是以一種高可靠的通信協(xié)議,通過序列號(hào)與確認(rèn)應(yīng)答來保障通信高可靠,有如下幾個(gè)關(guān)鍵點(diǎn):
- 當(dāng)發(fā)送端的數(shù)據(jù)到達(dá)接收主機(jī)時(shí),接收端主機(jī)會(huì)返回一個(gè)已收到消息的通知。這個(gè)消息叫做確認(rèn)應(yīng)答(ACK)。當(dāng)發(fā)送端將數(shù)據(jù)發(fā)出之后會(huì)等待對(duì)端的確認(rèn)應(yīng)答。如果有確認(rèn)應(yīng)答,說明數(shù)據(jù)已經(jīng)成功到達(dá)對(duì)端。反之,則數(shù)據(jù)丟失的可能性很大。
- 在一定時(shí)間內(nèi)沒有等待到確認(rèn)應(yīng)答,發(fā)送端就可以認(rèn)為數(shù)據(jù)已經(jīng)丟失,并進(jìn)行重發(fā)。由此,即使產(chǎn)生了丟包,仍然能夠保證數(shù)據(jù)能夠到達(dá)對(duì)端,實(shí)現(xiàn)可靠傳輸。
- 未收到確認(rèn)應(yīng)答并不意味著數(shù)據(jù)一定丟失。也有可能是數(shù)據(jù)對(duì)方已經(jīng)收到,只是返回的確認(rèn)應(yīng)答在途中丟失。這種情況也會(huì)導(dǎo)致發(fā)送端誤以為數(shù)據(jù)沒有到達(dá)目的地而重發(fā)數(shù)據(jù)。
- 此外,也有可能因?yàn)橐恍┢渌驅(qū)е麓_認(rèn)應(yīng)答延遲到達(dá),在源主機(jī)重發(fā)數(shù)據(jù)以后才到達(dá)的情況也屢見不鮮。此時(shí),源主機(jī)只要按照機(jī)制重發(fā)數(shù)據(jù)即可。
- 對(duì)于目標(biāo)主機(jī)來說,反復(fù)收到相同的數(shù)據(jù)是不可取的。為了對(duì)上層應(yīng)用提供可靠的傳輸,目標(biāo)主機(jī)必須放棄重復(fù)的數(shù)據(jù)包。為此我們引入了序列號(hào)。
- 序列號(hào)是按照順序給發(fā)送數(shù)據(jù)的每一個(gè)字節(jié)(8位字節(jié))都標(biāo)上號(hào)碼的編號(hào)。接收端查詢接收數(shù)據(jù) TCP 首部中的序列號(hào)和數(shù)據(jù)的長(zhǎng)度,將自己下一步應(yīng)該接收的序列號(hào)作為確認(rèn)應(yīng)答返送回去。通過序列號(hào)和確認(rèn)應(yīng)答號(hào),TCP 能夠識(shí)別是否已經(jīng)接收數(shù)據(jù),又能夠判斷是否需要接收,從而實(shí)現(xiàn)可靠傳輸。
- 重發(fā)超時(shí)是指在重發(fā)數(shù)據(jù)之前,等待確認(rèn)應(yīng)答到來的那個(gè)特定時(shí)間間隔。如果超過這個(gè)時(shí)間仍未收到確認(rèn)應(yīng)答,發(fā)送端將進(jìn)行數(shù)據(jù)重發(fā)。最理想的是,找到一個(gè)最小時(shí)間,它能保證“確認(rèn)應(yīng)答一定能在這個(gè)時(shí)間內(nèi)返回”。
- TCP 要求不論處在何種網(wǎng)絡(luò)環(huán)境下都要提供高性能通信,并且無論網(wǎng)絡(luò)擁堵情況發(fā)生何種變化,都必須保持這一特性。為此,它在每次發(fā)包時(shí)都會(huì)計(jì)算往返時(shí)間及其偏差。將這個(gè)往返時(shí)間和偏差時(shí)間相加,重發(fā)超時(shí)的時(shí)間就是比這個(gè)總和要稍大一點(diǎn)的值。
- 數(shù)據(jù)被重發(fā)之后若還是收不到確認(rèn)應(yīng)答,則進(jìn)行再次發(fā)送。此時(shí),等待確認(rèn)應(yīng)答的時(shí)間將會(huì)以2倍、4倍的指數(shù)函數(shù)延長(zhǎng)。
- 此外,數(shù)據(jù)也不會(huì)被無限、反復(fù)地重發(fā)。達(dá)到一定重發(fā)次數(shù)之后,如果仍沒有任何確認(rèn)應(yīng)答返回,就會(huì)判斷為網(wǎng)絡(luò)或?qū)Χ酥鳈C(jī)發(fā)生了異常,強(qiáng)制關(guān)閉連接。并且通知應(yīng)用通信異常強(qiáng)行終止。
TCP/IP協(xié)議缺陷
了解了TCP/IP協(xié)議之后,我們就會(huì)發(fā)現(xiàn)幾個(gè)問題:
- 在三次握手中,如果客戶端發(fā)起第一次握手后就中斷或者不響應(yīng)服務(wù)器發(fā)回的ACK=1數(shù)據(jù)包,那服務(wù)器就會(huì)不斷的重試發(fā)送數(shù)據(jù)包,直到超時(shí)。 沒錯(cuò),這就是SYN FLOOD攻擊原理。
- 在四次揮手中,主動(dòng)關(guān)閉連接的客戶端處在TIME_WAIT狀態(tài)后,會(huì)一直持續(xù)2MSL時(shí)間長(zhǎng)度,MSL就是maximum segment lifetime(最大分節(jié)生命期),這是一個(gè)IP數(shù)據(jù)包能在互聯(lián)網(wǎng)上生存的最長(zhǎng)時(shí)間,超過這個(gè)時(shí)間將在網(wǎng)絡(luò)中消失(TIME_WAIT狀態(tài)一般維持在1-4分鐘)。通過2MSL時(shí)間長(zhǎng)度來確保舊的連接狀態(tài)不會(huì)對(duì)新連接產(chǎn)生影響。處于TIME_WAIT狀態(tài)的連接占用的資源不會(huì)被內(nèi)核釋放,所以作為服務(wù)器,在可能的情 況下,盡量不要主動(dòng)斷開連接,以減少TIME_WAIT狀態(tài)造成的資源浪費(fèi)。如果我們的服務(wù)器是負(fù)載均衡服務(wù)器,上游服務(wù)器長(zhǎng)時(shí)間沒有影響,負(fù)載均衡服務(wù)器將主動(dòng)關(guān)閉鏈接,高并發(fā)場(chǎng)景下將導(dǎo)致TIME_WAIT狀態(tài)的累積。
- 在四次揮手中,如果客戶端在收到FIN 報(bào)文后,應(yīng)用沒有返回 ACK,服務(wù)端同樣會(huì)不斷嘗試發(fā)送FIN報(bào)文,這樣服務(wù)端就會(huì)出現(xiàn)CLOSE_WAIT狀態(tài)的累積。
SYN Flood攻擊
Syn Flood攻擊是當(dāng)前網(wǎng)絡(luò)上最為常見的DDoS攻擊,也是最為經(jīng)典的拒絕服務(wù)攻擊,它利用了TCP協(xié)議實(shí)現(xiàn)上的一個(gè)缺陷,通過向網(wǎng)絡(luò)服務(wù)所在端口發(fā)送大量的偽造源地址的攻擊報(bào)文,就可能造成目標(biāo)服務(wù)器中的半開連接隊(duì)列被占滿,從而阻止其他合法用戶進(jìn)行訪問。
Syn Flood攻擊原理
攻擊者首先偽造地址對(duì)服務(wù)器發(fā)起SYN請(qǐng)求(我可以建立連接嗎?),服務(wù)器就會(huì)回應(yīng)一個(gè)ACK+SYN(可以+請(qǐng)確認(rèn))。而真實(shí)的IP會(huì)認(rèn)為,我沒有發(fā)送請(qǐng)求,不作回應(yīng)。服務(wù)器沒有收到回應(yīng),會(huì)重試3-5次并且等待一個(gè)SYN Time(一般30秒-2分鐘)后,丟棄這個(gè)連接。
如果攻擊者大量發(fā)送這種偽造源地址的SYN請(qǐng)求,服務(wù)器端將會(huì)消耗非常多的資源來處理這種半連接,保存遍歷會(huì)消耗非常多的CPU時(shí)間和內(nèi)存,何況還要不斷對(duì)這個(gè)列表中的IP進(jìn)行SYN+ACK的重試。TCP是可靠協(xié)議,這時(shí)就會(huì)重傳報(bào)文,默認(rèn)重試次數(shù)為5次,重試的間隔時(shí)間從1s開始每次都番倍,分別為1s + 2s + 4s + 8s +16s = 31s,第5次發(fā)出后還要等32s才知道第5次也超時(shí)了,所以一共是31 + 32 = 63s。
一段假的syn報(bào)文,會(huì)占用TCP準(zhǔn)備隊(duì)列63s之久,而半連接隊(duì)列默認(rèn)為1024,在沒有任何防護(hù)的情況下,每秒發(fā)送20個(gè)偽造syn包,就足夠撐爆半連接隊(duì)列,從而使真正的連接無法建立,無法響應(yīng)正常請(qǐng)求。 最后的結(jié)果是服務(wù)器無暇理睬正常的連接請(qǐng)求—拒絕服務(wù)。
內(nèi)核TCP參數(shù)優(yōu)化
編輯文件/etc/sysctl.conf,加入以下內(nèi)容:
- net.ipv4.tcp_fin_timeout = 2
- net.ipv4.tcp_tw_reuse = 1
- net.ipv4.tcp_tw_recycle = 1
- net.ipv4.tcp_syncookies = 1
- net.ipv4.tcp_keepalive_time = 600
- net.ipv4.ip_local_port_range = 4000 65000
- net.ipv4.tcp_max_syn_backlog = 16384
- net.ipv4.tcp_max_tw_buckets = 36000
- net.ipv4.route.gc_timeout = 100
- net.ipv4.tcp_syn_retries = 1
- net.ipv4.tcp_synack_retries = 1
- net.core.somaxconn = 16384
- net.core.netdev_max_backlog = 16384
- net.ipv4.tcp_max_orphans = 16384
然后執(zhí)行 sysctl -p 讓參數(shù)生效。
作用說明:
- net.ipv4.tcp_fin_timeout 表示套接字由本端要求關(guān)閉,這個(gè)參數(shù)決定了它保持在FIN-WAIT-2狀態(tài)的時(shí)間,默認(rèn)值是60秒。 該參數(shù)對(duì)應(yīng)系統(tǒng)路徑為:/proc/sys/net/ipv4/tcp_fin_timeout 60
- net.ipv4.tcp_tw_reuse 表示開啟重用。允許將TIME-WAIT sockets重新用于新的TCP連接,默認(rèn)值為0,表示關(guān)閉。 該參數(shù)對(duì)應(yīng)系統(tǒng)路徑為:/proc/sys/net/ipv4/tcp_tw_reuse 0
- net.ipv4.tcp_tw_recycle 表示開啟TCP連接中TIME-WAIT sockets的快速回收。 該參數(shù)對(duì)應(yīng)系統(tǒng)路徑為:/proc/sys/net/ipv4/tcp_tw_recycle,默認(rèn)為0,表示關(guān)閉。 提示:reuse和recycle這兩個(gè)參數(shù)是為防止生產(chǎn)環(huán)境下Web、Squid等業(yè)務(wù)服務(wù)器time_wait網(wǎng)絡(luò)狀態(tài)數(shù)量過多設(shè)置的。
- net.ipv4.tcp_syncookies 表示開啟SYN Cookies功能。當(dāng)出現(xiàn)SYN等待隊(duì)列溢出時(shí),啟用Cookies來處理,可防范少量SYN攻擊,這個(gè)參數(shù)也可以不添加。 該參數(shù)對(duì)應(yīng)系統(tǒng)路徑為:/proc/sys/net/ipv4/tcp_syncookies,默認(rèn)值為1
- net.ipv4.tcp_keepalive_time 表示當(dāng)keepalive啟用時(shí),TCP發(fā)送keepalive消息的頻度。默認(rèn)是2小時(shí),建議改為10分鐘。 該參數(shù)對(duì)應(yīng)系統(tǒng)路徑為:/proc/sys/net/ipv4/tcp_keepalive_time,默認(rèn)為7200秒。
- net.ipv4.ip_local_port_range 該選項(xiàng)用來設(shè)定允許系統(tǒng)打開的端口范圍,即用于向外連接的端口范圍。 該參數(shù)對(duì)應(yīng)系統(tǒng)路徑為:/proc/sys/net/ipv4/ip_local_port_range 32768 61000
- net.ipv4.tcp_max_syn_backlog 表示SYN隊(duì)列的長(zhǎng)度,默認(rèn)為1024,建議加大隊(duì)列的長(zhǎng)度為8192或更多,這樣可以容納更多等待連接的網(wǎng)絡(luò)連接數(shù)。 該參數(shù)為服務(wù)器端用于記錄那些尚未收到客戶端確認(rèn)信息的連接請(qǐng)求最大值。 該參數(shù)對(duì)象系統(tǒng)路徑為:/proc/sys/net/ipv4/tcp_max_syn_backlog
- net.ipv4.tcp_max_tw_buckets 表示系統(tǒng)同時(shí)保持TIME_WAIT套接字的最大數(shù)量,如果超過這個(gè)數(shù)值,TIME_WAIT套接字將立刻被清除并打印警告信息。 默認(rèn)為180000,對(duì)于Apache、Nginx等服務(wù)器來說可以將其調(diào)低一點(diǎn),如改為5000~30000,不通業(yè)務(wù)的服務(wù)器也可以給大一點(diǎn),比如LVS、Squid。 此項(xiàng)參數(shù)可以控制TIME_WAIT套接字的最大數(shù)量,避免Squid服務(wù)器被大量的TIME_WAIT套接字拖死。 該參數(shù)對(duì)應(yīng)系統(tǒng)路徑為:/proc/sys/net/ipv4/tcp_max_tw_buckets
- net.ipv4.tcp_synack_retries 參數(shù)的值決定了內(nèi)核放棄連接之前發(fā)送SYN+ACK包的數(shù)量。 該參數(shù)對(duì)應(yīng)系統(tǒng)路徑為:/proc/sys/net/ipv4/tcp_synack_retries,默認(rèn)值為5
- net.ipv4.tcp_syn_retries 表示在內(nèi)核放棄建立連接之前發(fā)送SYN包的數(shù)量。 該參數(shù)對(duì)應(yīng)系統(tǒng)路徑為:/proc/sys/net/ipv4/tcp_syn_retries 5
- net.ipv4.tcp_max_orphans 用于設(shè)定系統(tǒng)中最多有多少個(gè)TCP套接字不被關(guān)聯(lián)到任何一個(gè)用戶文件句柄上。 如果超過這個(gè)數(shù)值,孤立連接將被立即被復(fù)位并打印出警告信息。 這個(gè)限制只有為了防止簡(jiǎn)單的DoS攻擊。不能過分依靠這個(gè)限制甚至認(rèn)為減少這個(gè)值,更多的情況是增加這個(gè)值。 該參數(shù)對(duì)應(yīng)系統(tǒng)路徑為:/proc/sys/net/ipv4/tcp_max_orphans 65536
- net.core.somaxconn 該選項(xiàng)默認(rèn)值是128,這個(gè)參數(shù)用于調(diào)節(jié)系統(tǒng)同時(shí)發(fā)起的TCP連接數(shù),在高并發(fā)的請(qǐng)求中,默認(rèn)的值可能會(huì)導(dǎo)致鏈接超時(shí)或重傳,因此,需要結(jié)合并發(fā)請(qǐng)求數(shù)來調(diào)節(jié)此值。 該參數(shù)對(duì)應(yīng)系統(tǒng)路徑為:/proc/sys/net/core/somaxconn 128
- net.core.netdev_max_backlog 表示當(dāng)每個(gè)網(wǎng)絡(luò)接口接收數(shù)據(jù)包的速率比內(nèi)核處理這些包的速率快時(shí),允許發(fā)送到隊(duì)列的數(shù)據(jù)包最大數(shù)。 該參數(shù)對(duì)應(yīng)系統(tǒng)路徑為:/proc/sys/net/core/netdev_max_backlog,默認(rèn)值為1000