提問:什么是高并發服務器,高并發指的是什么?
回答:單位時間服務器能夠同時承載的客戶端最大數量稱為并發,包括同時最大連接客戶端數量以及同時處理客戶端請求數量,即對應業務的承載能力,
很多朋友認為服務器能同時建立連接客戶端的最大數量就是并發量,這是片面的,無論什么業務,客戶端與服務器三次握手建立起連接,這只是完成了第一步,每一個客戶端都代表了一個用戶,會有不同的請求,會產生對應的資料,會進行前端展示,這些都有對應的瓶頸,比如大量客戶端都連接上了我們的服務器,但是如果資料庫落盤速度跟不上,有可能會導致用戶資料丟失,前面建立的連接就沒有任何意義了,
根據上面的闡述,由于不同場景不同業務的后臺設計不同,用的組件也千差萬別,所以本文只解決前半部分的問題,即:如何實作一個能同時承載百萬連接數的服務器,
---------------------------------------------------------------------------------------------------------------------------------------------------------------
實驗server原始碼:https://github.com/huohunri/linux_info/blob/master/reactor.c
實驗client原始碼:https://github.com/huohunri/linux_info/blob/master/mul_port_client_epoll.c
先實作兩個程式,一個是reactor.c,原理是用epoll實作reactor模型充當服務器,reactor模型的好處是在某一個時間點,可以把對應的網路連接映射成一個事件,一個是mul_port_client_epoll.c 回圈創建fd,不停嘗試連接reactor,充當客戶端,
注:個人的實驗環境:win10下搭建Vmware,系統版本Ubuntu 16.04.6 LTS 官方下載,進行本次實驗需要準備四臺虛擬機,一臺充當server(192.168.158.129),其它三臺運行client(192.168.158.140、192.168.158.139、192.168.158.136),
將原始碼從git上clone到本地,運行server如圖,因為開始寫本文博客的時間是7月3號,所以埠就用0703吧:
![]()
在客戶端分別運行mul_port_client_epoll.c

server端在建立1023個連接的時候,遇到了第一個問題:

這個錯誤一看就是Linux單行程能開最大檔案句柄限制導致的,是一個用戶級別的系統限制,
ulimit -a之后發現open max file 上限是1024

我們可以通過以下方式對其進行修改:
我們先打開vim /etc/security/limits.conf檔案,在下面加上
* soft nofile 1048576
* soft nofile 1048576
部分ubuntu用戶需要加上root級的限制,有些ubuntu內核實作的比較特別,*不包括root,
root soft nofile 1048576
root hard nofile 1048576
保存修改并退出當前用戶再重新鏈接,重新ulimit -a 查看open files大小:

發現open files修改已經生效,變成1048576,在這里我們根據經驗可以聯想到另一個內核級的fd限制,Linux系統下,所有行程允許打開的最大fd數量,我們把1048576加入系統組態檔/etc/sysctl.conf里面然后運行sysctl -p使其生效,

![]()
生效完之后系統會自動把對應的閾值重繪到/proc/sys/fs/file-max,我們cat一下發現內容已經變成1048576,
到此第一個問題解決,再次運行reactor.c代碼,發現這次報錯跟上次不同,遇到第二個問題:

segmentation fault,根據經驗,都是由于記憶體訪問錯誤造成的,一般是代碼問題,通過分析原始碼發現,#define MAX_EPOLL_EVENTS 1024

寫代碼的時候,只開辟了1024個epoll事件管理單元,當超過1024個事件到來的時候,程式就宕掉了,修改代碼#define MAX_EPOLL_EVENTS 1024*512,分配記憶體采用大塊記憶體分配函式

再次運行代碼,server端:

client端:

發現在并發量28000附近的時候出現了新的情況,也就是第三個問題:
我們發現,client程式已經直接退出了,服務器程式報Connection reset by peer錯誤,當前時刻并沒有把連接斷開,
根據現象,我們猜測,是五元組的問題,我們這里涉及的五元組是:fd----->(server 的ip地址,server的埠,客戶端得ip地址,客戶端得埠,協議)------>(1個,1個,1個,1個,tcp protobuf)
我們猜測 應該是客戶端或者服務器的埠少導致的Connection reset by peer,我們先將服務器的監聽埠數從1改成5:

客戶端的連接埠數從1同樣改成5

再次運行:


發現客戶端和服務器都卡在了65535附近,情形如同死鎖,也就是第四個問題:
由于我們用的一臺虛擬機在while(1)里不斷創建fd去回圈connect 5個服務器埠來模擬現網情況,跟真實的現網情況不同,真實的現網情況一般都是一個客戶端一個ip地址,所以客戶端不會觸發什么限制,但是我們不同,Linux對單臺機器創建三次握手連接是有限制的,限制的是第三次握手的閾值,其值正好是65535,這也是個系統限制,需要修改/etc/sysctl.conf,在里面添加net.nf_conntrack_max = 1048576
光添加還不行,還需要運行sudo modprobe ip_conntrack來對這個值進行加載,一般Linux內核默認是不會給用戶這個模塊權限,否則直接運行sysctl -p會報錯:

再次運行代碼:
Server:

![]()
Client:

到目前為止,連接數已經跑到了單臺客戶端代碼里的閾值,只要再開兩臺相同配置的客戶端去連接服務器,就可以湊足百萬連接數,

打開另外兩臺虛擬機,繼續進行實驗,結果如下:
Server端:

Client端:

三個端就是1024*512附近,再次觸及了reactor.c里面的#define MAX_EPOLL_EVENTS 1024*512宏定義上限
我們將其再次改大,改成#define MAX_EPOLL_EVENTS 1024*1024,再次運行程式,

改大之后再次運行,Server直接宕了,這里遇到了第五個問題,也就是最后的問題:
我們嘗試將1024*1024改成1024*6400一樣會宕機,網上很多人說是虛擬機的記憶體不夠,但我們已經給了16個G,所以說這個問題理論是不存在的,
再次出現Segmentation faul,通過反復注釋代碼實驗,發現問題出現在struct epoll_event events[MAX_EPOLL_EVENTS+1]

這里結構體過大了,即單執行緒堆疊空間大小不夠用了,
所以,我們執行 ulimit -s 設定大小值 臨時改變堆疊空間大小:ulimit -s 102400, 即修改為100M,再次實驗:
Server:

Client:

連接數最終做到了1024*1024,達到了C10M并發量,
拓展發散:
一、業內對高并發服務器的要求都有哪些:
1.服務器能夠同時承載的客戶端數量,
2.服務器處理客戶端連接請求數量,
3.能處理指定數量以上的回應請求(一般5w以上的回應請求)
4.對資料庫的操作,
5.對磁盤的操作,
6.cpu占用率60%,(應對峰值)
7.記憶體占有率80%,(應對峰值)
二、C10K->C1000k->C10M的思考,https://zhuanlan.zhihu.com/p/50984245
當年C10k是由于沒有epoll,之前使用select或poll,select的前三個引數都是集合,每次操作都是把應用程式里面的集合拷貝到內核里,內核里面通過遍歷的方式再帶出來,epoll的原理是將就緒事件直接從紅黑樹里直接加入到就緒隊里額,不需要對整個集合進行操作,epoll里面每次活躍IO數一般不到10%,
三、最近關注的開源組件/語言特性:
1.單執行緒同步:NTP
2.多線陳同步:Natty
3.純異步:Redis,Haproxy
4.半同步半異步:Natty
5.多行程同步:fastcgi
6.多行程異步:memcached,nginx
7.每請求每行程(執行緒):Apache/CGI
8.微行程框架:erlang/go/lua
9.協程框架:libco/ntyco
...
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/234940.html
標籤:其他
上一篇:CSU2020期中考試試題題解
