tcp too many orphaned sockets 問題引發的思考

起因:服務器上部署了單個golang編寫的HTTPDNS實例,一個固定的端口9981對外提供服務。當QPS達到幾十萬以上時,該實例崩潰,且無法再次啓動

dmesg 查看系統日誌,發現大量日誌,如下:

TCP:too many orphaned sockets

看到這個日誌想到了可能是sockets資源耗盡了。下面來分析下如何解決吧

1.端口與socket

端口

Linux下端口號範圍0~65535,其中0~1023是系統保留端口號,1024~65535是用戶可使用端口號,Linux下默認用戶可用端口號範圍如下:

cat /proc/sys/net/ipv4/ip_local_port_range 
32768	60999

可用端口號爲28232個,可以通過參照https://charlescui.iteye.com/blog/341713這個鏈接中的方式修改

socket

要明確兩點:

  1. 端口並不等同於socket ,socket是(source_ip;source_port;destination_ip;destination_port)組成的四元組,套接字對唯一標識一個網絡上的每個TCP連接。其中任意1個不同,都是1個新的socket。參考:《TCP-IP詳解》卷一
  2. 在Linux中,一切都可以看成文件,包括磁盤,外設等,內核(kernel)利用文件描述符(file descriptor)來訪問文件。文件描述符是非負整數,打開現存文件或新建文件時,內核會返回一個文件描述符。讀寫文件也需要使用文件描述符來指定待讀寫的文件。socket是一個抽象出來的概念,本質上也是一個文件描述符。
    參考:《unix網絡編程》·卷1

2.端口耗盡

上面已經寫了,端口號是有範圍的,因此可能存在被耗盡的風險。
這種情況一般是出現在客戶端機器上的。因爲一般服務端都是固定端口的,由客戶端動態的使用一個端口號,與目標服務器連接。
比如下面的客戶端代碼:

# 創建 socket 對象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = '10.32.4.95'
port = 9999
# 連接服務,指定主機和端口
s.connect((host, port))

客戶端都是動態的使用一個端口號,來連接服務端(10.32.4.95:9999),如果在客戶端上不斷的同服務端建立連接,那麼客戶端端口號可能被耗盡。壓測的時候就算是使用短連接也可能導致客戶端端口被耗盡。
因爲TCP四次揮手最後過程,主動關閉連接的一端都會處於TIME_WAIT等待2MSL,一般是60s

修改內核參數主要目標就是加快TIME_WAIT狀態套接字的回收(快速回收無效連接)
參考這個優化吧https://woodding2008.iteye.com/blog/2336704
客戶端:

#開啓TCP連接中TIME-WAIT套接字的快速回收  
net.ipv4.tcp_tw_recycle=1     
  
#開啓重用,表示是否允許將處於TIME-WAIT狀態的socket(TIME-WAIT的端口)用於新的TCP連接 。  
net.ipv4.tcp_tw_reuse=1       
  
#對於本端斷開的socket連接,TCP保持在FIN-WAIT-2狀態的時間(秒)。  
#對方可能會斷開連接或一直不結束連接或不可預料的進程死亡。  
net.ipv4.tcp_fin_timeout=5      
  
#TCP時間戳(會在TCP包頭增加12個字節),以一種比重發超時更精確的方法(參考RFC 1323)  
#來啓用對RTT 的計算,爲實現更好的性能應該啓用這個選項。  
net.ipv4.tcp_timestamps=1    
  
#收縮TIME_WAIT狀態socket的回收時間窗口   
net.ipv4.tcp_tw_timeout=3     

3.socket資源耗盡

注意,是資源耗盡,並不是socket耗盡,資源耗盡是指什麼呢?嚴格來講,socket是一個四元組,一抽象出來的東西因此我覺得不存在socket耗盡的說法,但由於socket本質是一個文件描述符,因此存在文件描述符被耗盡的可能,下面再說
引用下這個總結

linux系統單機支持的tcp連接數主要受三個方面的限制:
1.文件描述符的限制 (socket本質上也是一個文件描述符)
2.系統內存限制
3.Linux內核的限制

參考:https://blog.csdn.net/ybxuwei/article/details/77969032

對於上面三點,一一說明一下
文件描述符的限制:在64位的系統上,文件描述符單機可以達到20億+,因此這個限制基本不用考慮,絕對夠用
系統內存限制:一個socket連接大概佔用4~10Kb內存,要想單機1百萬連接,那麼就至少需要大概4G內存。這個用量對於現在的服務器來說還是很輕鬆就滿足的
因此最關鍵的在於 Linux內核的限制,如果突破了這個限制就會出問題。
Linux對TCP、UPD連接是做了內存限制的,通過以下命令查看

cat /proc/sys/net/ipv4/tcp_mem 
186135	248180	372270

cat /proc/sys/net/ipv4/udp_mem
372270	496361	744540

上面的數據 (參考連接)
第一個:low:當TCP使用了低於該值的內存頁面數時,TCP不會考慮釋放內存。
第二個:pressure:當TCP使用了超過該值的內存頁面數量時,TCP試圖穩定其內存使用,進入pressure模式,當內存消耗低於low值時則退出pressure狀態。
第三個:high:允許所有tcp sockets用於排隊緩衝數據報的頁面量,當內存佔用超過此值,系統拒絕分配socket,後臺日誌輸出“TCP: too many of orphaned sockets” (應該是Out of Socket memory)。
如果超過最大限制,dmesg中會報Out of Socket memory錯誤,且程序會被殺死
上面的數據單位是頁,頁大小通過以下命令查看

 getconf PAGESIZE
4096

1 page =4096 byte
計算下得到,一個TCP可使用的內存上限是:1.4GB左右,當然了這個是我筆記本上的參數,服務器上的會更大
可以通過如下圖所示命令查看tcp socket使用情況

爲了增大服務器的TCP連接數目,可以通過以下命令來增大TCP socket可使用內存

echo "net.ipv4.tcp_mem = 786432 2097152 3145728">> /etc/sysctl.conf
echo "net.ipv4.tcp_rmem = 4096 4096 16777216">> /etc/sysctl.conf
echo "net.ipv4.tcp_wmem = 4096 4096 16777216">> /etc/sysctl.conf

對於長連接來說,上面的操作就已經足夠了

4.回到正題來

dmesg中出現tcp too many orphaned sockets信息是爲什麼呢?HTTPDNS服務器中,一般都是一些短連接,因此會產生大量orphan socket
什麼是orphan socket?

First of all: what’s an orphan socket? It’s simply a socket that isn’t associated to a file descriptor. For instance, after you close() a socket, you no longer hold a file descriptor to reference it, but it still exists because the kernel has to keep it around for a bit more until TCP is done with it. Because orphan sockets aren’t very useful to applications (since applications can’t interact with them), the kernel is trying to limit the amount of memory consumed by orphans, and it does so by limiting the number of orphans that stick around. If you’re running a frontend web server (or an HTTP load balancer), then you’ll most likely have a sizeable number of orphans, and that’s perfectly normal.

意思就是說:orphan sockets是沒有與任何文件描述符關聯的socket,當你調用close()關閉一個socket後,你就不再擁有這個socket的引用了,但是它仍然存在與操作系統中,直到TCP完成揮手流程。因爲orphan sockets對程序來說沒有什麼用,因此內核會限制其數量

Linux內核對orphaned sockets也做出了限制

cat /proc/sys/net/ipv4/tcp_max_orphans
65536

要避免這種情況,可以將tcp_max_orphans的值也設大一點,並且建議同時也參照上述2(快速回收無效連接),3(增加TCP socket可使用內存)進行優化
也看到一些建議修改net.ipv4.tcp_orphan_retries參數的

參考:
https://blog.tsunanet.net/2011/03/out-of-socket-memory.html
https://yq.aliyun.com/articles/91966
https://serverfault.com/questions/274212/what-does-tcp-orphan-retries-set-to-0-mean
http://jaseywang.me/2012/05/09/關於-out-of-socket-memory-的解釋-2/
https://blog.csdn.net/antony1776/article/details/73717666

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章