大戰C100K之4-Linux核心調優篇
http://joyexpr.com/2013/11/22/c100k-4-kernel-tuning/
早期的系統,系統資源包括CPU、記憶體等都是非常有限的,系統為了保持公平,預設要限制程序對資源的使用情況。由於Linux的預設核心配置無法滿足C100K的要求,因此需要對其進行適當的調優。
我們可以通過 ulimit
檢視一下典型的機器預設的限制情況:
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 204800
max locked memory (kbytes, -l) 32
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 204800
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
比如其中的 open
files
,預設一個程序能開啟的檔案控制代碼數量為1024,對於一些需要大量檔案控制代碼的程式,如web伺服器、資料庫程式等,1024往往是不夠用的,在控制代碼使用完畢的時候,系統就會頻繁出現emfile錯誤。
俗話說:一個巴掌拍不響,要完成 C100K
的目標,需要伺服器端與客戶端的緊密配合,下面將分別對這二者的調優進行介紹。
客戶端
1:檔案控制代碼數量受限
在Linux平臺上,無論是編寫客戶端程式還是服務端程式,在進行高併發TCP連線處理時,由於每個TCP連線都要建立一個socket控制代碼,而每個socket控制代碼同時也是一個檔案控制代碼,所以其最高併發數量要受到系統對使用者單一程序同時可開啟檔案數量的限制以及整個系統可同時開啟的檔案數量限制。
1.1:單一程序的檔案控制代碼數量受限
我們可以ulimit命令檢視當前使用者程序可開啟的檔案控制代碼數限制:
[root@localhost ~]# ulimit -n
1024
這表示當前使用者的每個程序最多允許同時開啟1024個檔案,除去每個程序必然開啟的標準輸入、標準輸出、標準錯誤、伺服器監聽socket、程序間通訊的unix域socket等檔案,剩下的可用於客戶端socket連線的檔案數就只有大概1024-10=1014個左右。也就是說,在預設情況下,基於Linux的通訊程式最多允許同時1014個TCP併發連線。
對於想支援更高數量的TCP併發連線的通訊處理程式,就必須修改Linux對當前使用者的程序可同時開啟的檔案數量的軟限制(soft limit)和硬限制(hardlimit)。其中:
- 軟限制是指Linux在當前系統能夠承受的範圍內進一步限制使用者能同時開啟的檔案數。
- 硬限制是指根據系統硬體資源狀況(主要是系統記憶體)計算出來的系統最多可同時開啟的檔案數量。
通常軟限制小於或等於硬限制,可通過ulimit命令檢視軟限制和硬限制:
[root@localhost ~]# ulimit -Sn
1024
[root@localhost ~]# ulimit -Hn
4096
修改單一程序能同時開啟的檔案控制代碼數有2種方法:
1、直接使用ulimit命令,如:
[root@localhost ~]# ulimit -n 1048576
執行成功之後,ulimit n、Sn、Hn的值均會變為1048576。但該方法設定的值只會在當前終端有效,且設定的值不能高於方法2中設定的值。
2、對 /etc/security/limits.conf
檔案,新增或修改:
* soft nofile 1048576
* hard nofile 1048576
其中,
-
*
代表對所有使用者有效,若僅想針對某個使用者,可替換星號。 - soft即軟限制,它只是一個警告值。
- hard代表硬限制,是一個真正意義的閾值,超過就會報錯。
- nofile表示開啟檔案的最大數量。
- 1048576 = 1024 * 1024,為什麼要取這個值呢?因為
在linux kernel 2.6.25之前通過ulimit -n(setrlimit(RLIMIT_NOFILE))設定每個程序的最大開啟檔案控制代碼數不能超過NR_OPEN(1024*1024),也就是100多w(除非重新編譯核心),而在25之後,核心匯出了一個sys介面可以修改這個最大值(/proc/sys/fs /nr_open).具體的changelog在https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=9cfe015aa424b3c003baba3841a60dd9b5ad319b
注意檔案儲存之後,需要登出或重啟系統方能生效。
1.2:整個系統的檔案控制代碼數量受限
解決完單一程序的檔案控制代碼數量受限問題後,還要解決整個系統的檔案控制代碼數量受限問題。我們可通過以下命令檢視Linux系統級的最大開啟檔案數限制:
[root@localhost ~]# cat /proc/sys/fs/file-max
98957
file-max表示系統所有程序最多允許同時開啟的檔案控制代碼數,是Linux系統級硬限制。通常,這個系統硬限制是Linux系統在啟動時根據系統硬體資源狀況計算出來的最佳的最大同時開啟檔案數限制,如果沒有特殊需要,不應該修改此限制。
要修改它,需要對 /etc/sysctl.conf
檔案,增加一行內容:
fs.file-max = 1048576
儲存成功後,需執行下面命令使之生效:
[root@localhost ~]# sysctl -p
2:埠數量受限
解決完檔案控制代碼數量受限的問題後,就要解決IP埠數量受限的問題了。一般來說,對外提供請求的服務端不用考慮埠數量問題,只要監聽某一個埠即可。可客戶端要模擬大量的使用者對服務端發起TCP請求,而每一個請求都需要一個埠,為了使一個客戶端儘可能地模擬更多的使用者,也就要使客戶端擁有更多可使用的埠。
由於埠為16進位制,即最大埠數為2的16次方65536(0-65535)。在Linux系統裡,1024以下埠只有超級管理員使用者(如root)才可以使用,普通使用者只能使用大於等於1024的埠值。
我們可以通過以下命令檢視系統提供的預設的埠範圍:
[root@localhost ~]# cat /proc/sys/net/ipv4/ip_local_port_range
32768 61000
即只有61000-32768=28232個埠可以使用,即單個IP對外只能同時傳送28232個TCP請求。
修改方法有以下2種:
1、執行以下命令:
echo "1024 65535"> /proc/sys/net/ipv4/ip_local_port_range
該方法立即生效,但重啟後會失效。
2、修改 /etc/sysctl.conf
檔案,增加一行內容:
net.ipv4.ip_local_port_range = 1024 65535
儲存成功後,需執行下面命令使之生效:
[root@localhost ~]# sysctl -p
修改成功後,可用埠即增加到65535-1024=64511個,即單個客戶端機器只能同時模擬64511個使用者。要想突破這個限制,只能給該客戶端增加IP地址,這樣即可相應成倍地增加可用IP:PORT數。具體可參考yongboy的這篇文章。
服務端
1:檔案描述符數量受限
同客戶端的問題1。
2:TCP引數調優
要想提高服務端的效能,以達到我們高併發的目的,需要對系統的TCP引數進行適當的修改優化。
方法同樣是修改 /etc/sysctl.conf
檔案,增加以下內容:
net.ipv4.tcp_tw_reuse = 1
當伺服器需要在大量TCP連線之間切換時,會產生大量處於TIME_WAIT狀態的連線。TIME_WAIT意味著連線本身是關閉的,但資源還沒有釋放。將net_ipv4_tcp_tw_reuse設定為1是讓核心在安全時儘量回收連線,這比重新建立新連線要便宜得多。
net.ipv4.tcp_fin_timeout = 15
這是處於TIME_WAIT狀態的連線在回收前必須等待的最小時間。改小它可以加快回收。
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
提高TCP的最大緩衝區大小,其中:
net.core.rmem_max
:表示接收套接字緩衝區大小的最大值(以位元組為單位)。
net.core.wmem_max
:表示傳送套接字緩衝區大小的最大值(以位元組為單位)。
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
提高Linux核心自動對socket緩衝區進行優化的能力,其中:
net.ipv4.tcp_rmem
:用來配置讀緩衝的大小,第1個值為最小值,第2個值為預設值,第3個值為最大值。
net.ipv4.tcp_wmem
:用來配置寫緩衝的大小,第1個值為最小值,第2個值為預設值,第3個值為最大值。
net.core.netdev_max_backlog = 4096
每個網路介面接收資料包的速率比核心處理這些包的速率快時,允許送到佇列的資料包的最大數目。預設為1000。
net.core.somaxconn = 4096
表示socket監聽(listen)的backlog上限。什麼是backlog呢?backlog就是socket的監聽佇列,當一個請求(request)尚未被處理或建立時,他會進入backlog。而socket server可以一次性處理backlog中的所有請求,處理後的請求不再位於監聽佇列中。當server處理請求較慢,以至於監聽佇列被填滿後,新來的請求會被拒絕。預設為128。
net.ipv4.tcp_max_syn_backlog = 20480
表示SYN佇列的長度,預設為1024,加大佇列長度為8192,可以容納更多等待連線的網路連線數。
net.ipv4.tcp_syncookies = 1
表示開啟SYN Cookies。當出現SYN等待佇列溢位時,啟用cookies來處理,可防範少量SYN攻擊,預設為0,表示關閉。
net.ipv4.tcp_max_tw_buckets = 360000
表示系統同時保持TIME_WAIT套接字的最大數量,如果超過這個數字,TIME_WAIT套接字將立刻被清除並列印警告資訊。預設為180000。
net.ipv4.tcp_no_metrics_save = 1
一個tcp連線關閉後,把這個連線曾經有的引數比如慢啟動門限snd_sthresh、擁塞視窗snd_cwnd,還有srtt等資訊儲存到dst_entry中,只要dst_entry沒有失效,下次新建立相同連線的時候就可以使用儲存的引數來初始化這個連線。
net.ipv4.tcp_syn_retries = 2
表示在核心放棄建立連線之前傳送SYN包的數量,預設為4。
net.ipv4.tcp_synack_retries = 2
表示在核心放棄連線之前傳送SYN+ACK包的數量,預設為5。
完整的TCP引數調優配置如下所示:
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.netdev_max_backlog = 4096
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 20480
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_tw_buckets = 360000
net.ipv4.tcp_no_metrics_save = 1
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2
其它一些引數
vm.min_free_kbytes = 65536
用來確定系統開始回收記憶體的閥值,控制系統的空閒記憶體。值越高,核心越早開始回收記憶體,空閒記憶體越高。
vm.swappiness = 0
控制核心從實體記憶體移出程序,移到交換空間。該引數從0到100,當該引數=0,表示只要有可能就盡力避免交換程序移出實體記憶體;該引數=100,這告訴核心瘋狂的將資料移出實體記憶體移到swap快取中。