并發量和承載的概念
并發量:一個服務器能同時承載客戶端的數量
承載:客戶端發送給服務器的請求(http或tcp等)在200ms內可以回傳正確的結果
影響并發量的主要因素
- 資料庫回應速度
- 網路帶寬回應
- 記憶體操作
- 日志性能
服務器能同時建立的連接數量只是并發的基礎,服務器處理客戶端的請求包括請求、回應、處理和關閉,
網路五元組
- 源ip
- 目的ip
- 源埠號
- 目的埠號
- 協議
? 由于只有一臺客戶端不斷建立連接,所以源ip是一樣的;目的ip也只有一臺服務器;源埠號是不確定的,由作業系統或自己指定,目的埠號則是指定的8080埠;協議使用tcp協議,此時只有源埠號是不唯一的,這五個元素只要有一個是不同的,就對應一個不同的fd,
第一次并發量測驗
使用之前的epoll_reactor.c的服務器代碼進行測驗,使用網路除錯助手進行連接測驗,成功連接,發生兩次資料,然后斷開,
wang@ubuntu:~/Desktop/0vicevip/5.1$ gcc epoll_reactor.c -o epoll_reactor
wang@ubuntu:~/Desktop/0vicevip/5.1$ ./epoll_reactor 8080
recv from 192.168.211.1 at port 57216
Recv: http://www.cmsoft.cn, 20 Bytes
Recv: http://www.cmsoft.cn, 20 Bytes
disconnect 5
再將客戶端連接代碼mulport_client_epoll.c中的最大連接埠宏定義先修改為1
編譯之后,先啟動服務端再啟動客戶端
wang@ubuntu:~/Desktop/0vicevip/5.1$ ./mulport 192.168.211.128 8080
connections: 999, sockfd:1002, time_used:1915
socket: Too many open files
error : Too many open files
查看單行程可操作性檔案描述符數
根據錯誤提示可知,是由于打開太多檔案導致的,那么我們先查看一下當前系統允許操作的檔案描述符數量,
wang@ubuntu:~/Desktop/0vicevip/5.1$ 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) 15391
max locked memory (kbytes, -l) 65536
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) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 15391
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
由open files (-n) 1024可得,最多打開的檔案描述符個數為1024
修改單行程可操作性檔案描述符數
有兩種方法
- 使用
ulimit -n 數字可以進行臨時修改,重啟當前終端后,修改效果消失 - 修改
/etc/security/limits.conf組態檔中的內容
在檔案尾部加入中間兩行配置
#<domain> <type> <item> <value>
* hard nofile 1048576 ===》Hard limit限制,下面的設定不能超過該數
* soft nofile 1048576 ===》
#說明domain:域 記錄那個用戶組的程式有效 * 所有用戶 root:root用戶
type:soft(軟限制) hard(硬限制) 軟:能超過value,但是超過后作業系統回回收,硬不能超過設定的數量
value:對應數量
但是有個bug,切換后沒有效果,重啟也沒有辦法,切換到root用戶,再切換回來就生效了,太奇怪了,每次這么搞好麻煩,還不如直接方法1修改,或者一直用root,
修改系統能打開的最大檔案描述符數
同時還有個地方需要修改,就是在/etc/sysctl.conf中,需要設定系統可以打開的檔案描述符最大值,
# 系統最大檔案打開數
fs.file-max = 20000000
# 單個用戶行程最大檔案打開數
fs.nr_open = 20000000
指向sysctl -p /etc/sysctl.conf后生效,可通過cat /proc/sys/fs/file-max查看,
第二次并發量測驗
再次運行服務端和客戶端
root@ubuntu:/home/wang/Desktop/0vicevip/5.1# ./mulport 192.168.211.128 8080
connections: 999, sockfd:1002, time_used:1857
connections: 1999, sockfd:2002, time_used:1898
connections: 2999, sockfd:3002, time_used:1908
connections: 3999, sockfd:4002, time_used:1881
connections: 4999, sockfd:5002, time_used:1886
connections: 5999, sockfd:6002, time_used:1924
connections: 6999, sockfd:7002, time_used:1950
connections: 7999, sockfd:8002, time_used:1940
connections: 8999, sockfd:9002, time_used:1931
connections: 9999, sockfd:10002, time_used:1956
connections: 10999, sockfd:11002, time_used:3026
connections: 11999, sockfd:12002, time_used:1987
connections: 12999, sockfd:13002, time_used:1992
connections: 13999, sockfd:14002, time_used:1940
connections: 14999, sockfd:15002, time_used:3805
connections: 15999, sockfd:16002, time_used:4060
connections: 16999, sockfd:17002, time_used:4240
connections: 17999, sockfd:18002, time_used:4179
connections: 18999, sockfd:19002, time_used:4220
connections: 19999, sockfd:20002, time_used:5522
connections: 20999, sockfd:21002, time_used:4605
connections: 21999, sockfd:22002, time_used:5540
connections: 22999, sockfd:23002, time_used:4621
connections: 23999, sockfd:24002, time_used:4711
connections: 24999, sockfd:25002, time_used:5049
connections: 25999, sockfd:26002, time_used:4931
connections: 26999, sockfd:27002, time_used:5046
connections: 27999, sockfd:28002, time_used:6185
connect: Cannot assign requested address
error : Cannot assign requested address
觀察最后三行,并發量到達2.8W,連接出錯,客戶端提示無法分配地址,這是由于客戶端埠限制導致的,系統分配給程式的埠已經用完了,linux系統默認0-1023為常用埠,1024-49151為注冊服務的埠,49152-65535為自定義埠,
修改可用埠范圍
此埠的范圍可以通過range_from來修改,
#方法1 cat /proc/sys/net/ipv4/ip_local_port_range
root@ubuntu:/home/wang/Desktop/0vicevip/5.1# cat /proc/sys/net/ipv4/ip_local_port_range
32768 60999
#方法2 在/etc/sysctl.conf組態檔中直接修改
root@ubuntu:/home/wang/Desktop/0vicevip/5.1# sysctl -p /etc/sysctl.conf
net.ipv4.ip_local_port_range = 32768 65000
root@ubuntu:/home/wang/Desktop/0vicevip/5.1# cat /proc/sys/net/ipv4/ip_local_port_range
32768 65000
root@ubuntu:/home/wang/Desktop/0vicevip/5.1#
再次測驗
connections: 31999, sockfd:32002, time_used:7004
connect: Cannot assign requested address
error : Cannot assign requested address
由于客戶端可用埠號增加了4000,所以連接數也增加了4000,但是依然遠遠不夠,我們可以考慮讓服務端監聽100個埠,我們通過客戶端來訪問這一百個埠,服務端端的代碼同時也做出修改,(實際開發中不會采用這種方式提高并發量,一般使用多行程fork的方式來實作(只能使用行程不能使用執行緒,因為每個行程都有自己的檔案系統空間,但是執行緒是共享的))
第三次并發量測驗
connections: 102999, sockfd:103002, time_used:2009
connections: 103999, sockfd:104002, time_used:2133
RecvBuffer:Hello Server: client --> 103998
data from 4
data from 5
data from 6
data from 7
data from 8
data from 9
data from 10
,,,
data fro
Error clientfd:141002, errno:11
connect: Connection refused
error : Connection refused
連接數到達十萬,由于網路協議堆疊中的netfilter組件配置限制的原因,
netfilter
客戶端發送的data,由網卡通過sk_buffer組件傳輸到網路協議堆疊,到達網路協議堆疊時,會經過一個netfilter組件(作業系統為防止大量無效的資料攻擊,在資料進入網路協議堆疊之前加一個層過濾器netfilter規則,對網路資料包進行過濾)

Linux系統中的iptables防火墻就是基于netfilter實作的,iptables一共兩部分,一部分是內核實作的netfilter的介面,有一部分是提供出來應用程式可以用的東西,即通過iptables命令操作的一些內容(轉發、拒絕某個介面資料等功能),
在/etc/sysctl.conf中添加如下配置,然后sysctl -p /etc/sysctl.conf生效即可,
cat /proc/sys/net/netfilter/nf_conntrack_max查看允許連接數,
//最大跟蹤連接數,默認是65536個
net.nf_conntrack_max = 1048576
//穩定的連接狀態最多保留多久,單位是秒,默認是432000秒,就是5天
net.netfilter.nf_conntrack_tcp_timeout_established = 1200
性能調優
修改了系統對fd的限制,單個行程對fd的限制,接下來就是記憶體的限制,tcp相關收發緩沖區,tcp記憶體設定,以及tcp相關引數設定,
# 全連接佇列長度,默認128
net.core.somaxconn = 10240
# 半連接佇列長度,當使用sysncookies無效,默認128
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.tcp_syncookies = 0
# 網卡資料包佇列長度
net.core.netdev_max_backlog = 41960
# time-wait 最大佇列長度
net.ipv4.tcp_max_tw_buckets = 300000
# time-wait 是否重新用于新鏈接以及快速回收
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
# tcp報文探測時間間隔, 單位s
net.ipv4.tcp_keepalive_intvl = 30
# tcp連接多少秒后沒有資料報文時啟動探測報文
net.ipv4.tcp_keepalive_time = 900
# 探測次數
net.ipv4.tcp_keepalive_probes = 3
# 保持fin-wait-2 狀態多少秒
net.ipv4.tcp_fin_timeout = 15
# 最大孤兒socket數量,一個孤兒socket占用64KB,當socket主動close掉,處于fin-wait1, last-ack
net.ipv4.tcp_max_orphans = 131072
# 每個套接字所允許得最大快取區大小
net.core.optmem_max = 819200
# 默認tcp資料接受視窗大小
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
# tcp堆疊記憶體使用第一個值記憶體下限, 第二個值快取區應用壓力上限, 第三個值記憶體上限, 單位為page,通常為4kb
net.ipv4.tcp_mem = 786432 4194304 8388608
# 讀, 第一個值為socket快取區分配最小位元組, 第二個,第三個分別被rmem_default, rmem_max覆寫
net.ipv4.tcp_rmem = 4096 4096 4206592
# 寫, 第一個值為socket快取區分配最小位元組, 第二個,第三個分別被wmem_default, wmem_max覆寫
net.ipv4.tcp_wmem = 4096 4096 4206592
tcp_mem
- low:當TCP使用了低于該值的記憶體頁面數時,TCP不會考慮釋放記憶體,
- pressure:當TCP使用了超過該值的記憶體頁面數量時,TCP試圖穩定其記憶體使用,進入壓力模式,當記憶體消耗低于low值時則退出壓力狀態,
- high:允許所有tcp sockets用于排隊緩沖資料報的頁面量,當記憶體占用超過此值,系統拒絕分配socket,后臺日志輸出“TCP: too many orphaned sockets”或“Out of socket memory”
每個tcp連接對應一個讀緩沖區recvbuffer和寫緩沖區sendbuffer,可以使用修改組態檔,也可以通過在程式中使用setsockopt函式,
tcp_rmem 讀緩沖區
- low:默認是4096,緩沖區的最小大小
- pressure:默認是87380,接識訓沖區的初始大小
- high:允許接識訓沖區的最大大小
tcp_wmem 寫緩沖區
- low:默認是4096,緩沖區的最小大小
- pressure:默認是16384,發送緩沖區的初始大小
- high:允許發送緩沖區的最大大小
高并發設計原則
自己的服務器程式,除錯網路協議堆疊引數的原則:
- 傳輸檔案資料量大時,將buffer調大,
- 資料量較小時,可將buff調小,當buffer為512時,ssh無法連接,且日志也無法列印,調小的目的,就是為了增加并發量,可以把fd的數量做到更多,占用的記憶體更少,
第四次并發量測驗
因為只有一臺設備,也懶得程式裝額外的虛擬機,所以所有的測驗都是在同一臺上,最終的測驗結果達到了70萬連接,(當時虛擬機已經卡住了,我一看情況不對,趕緊先把圖截下來,哈哈哈)偽c1000k并發完成!

抖動
每k連接建立,所用的時長差距較大,原因是只有一個執行緒在處理accept,處理不過來,半連接佇列已滿,只能等待,
解決方法
- accept和recv/send分開,將網路連接丟給一個執行緒去處理,分配fd后,交給執行緒池處理,參考redis
- 開多個執行緒accept,參考nginx
c1000k服務器客戶端流程

C10M應該如何實作
C1000k,只需使用一個epoll的reactor模型就可以實作,那么如何實作千萬級的并發呢?要解決這個問題,最重要就是跳過內核協議堆疊的冗長路徑,把網路包直接送到要處理的應用程式那里去,有兩種常見的機制,DPDK 和 XDP,
(1) DPDK:是用戶態網路的標準,它跳過內核協議堆疊,直接由用戶態行程通過輪詢的方式,來處理網路接收,每時每刻都有新的網路包需要處理,輪詢的優勢就很明顯,
(2) XDP:是 Linux 內核提供的一種高性能網路資料路徑,它允許網路包,在進入內核協議堆疊之前,就進行處理,也可以帶來更高的性能,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/289229.html
標籤:其他
