提到高并發或者抗壓力,有這種高qps經驗的同學第一反應大都是Nginx + lua + Redis,網上也滿天非那種高并發架構方案大都是這種,但是Nginx + lua 來做接入層到底是怎么抗住壓力的呢?
本篇順序:
1、Nginx 如何抗住的高并發,工作模式是怎樣的,利用了哪些技術
2、常見的IO模型及 異步非阻塞IO的優勢
3、epoll相對于其他模型為何這么強大
第一階段:
Nginx 不同于 Apache 的一點就是,Nginx 采用單線程,非阻塞,異步 IO 的工作模型,并不會每一個新進程都會起一個新的進程或者線程來處請求,Nginx利用的是epoll模型。然后來看一下Nginx的工作模型:
nginx 工作模型有兩種實現方式:
單工作進程是指,一個工作進程中有多個工作線程,只有一個工作進程 + master進程
多工作進程是指,一個工作進程中只有一個工作線程,然后有多個工作進程 + master進程
就是下面這種機遇epoll 模型構建的單線程,非阻塞,異步 IO的 單線程處理多鏈接工作模型來抗住的壓力,下面來仔細的看看Nginx的內部實現。
master 主要負責,接受管理員信號向worker發送指令,負責worker進程的生命周期,通知的類型有:worker 不再接受新請求、worker 退出&銷毀等,可以把master 看作一個worker 的管理器,我們和master交互來間接管理worker。nginx一些無損重啟或者reload配置文件等就是這么來實現的。
worker 顧名思義是處理具體網絡事件的,從初始化nginx開始講:
首先被創建的是master,在創建時先建立好需要listen的socket(listenfd),然后從master從fork出多個worker ,這樣相當于master listenfd就被繼承過來了,所以說所有的worker會在新連接到來時變得可讀,為保證只有一個進程處理鏈接,所有的worder進程在注冊listenfd讀事件發生時會先去搶占accept_mutex,搶到互斥鎖的那個worker進程才會注冊listenfd讀事件,在讀事件里面調用accept接受連接,然后就開始讀取請求、解析請求、處理、產生響應、斷開連接。處理過程中如果碰到了IO操作,就開始使用基于epoll的非阻塞,異步 IO工作模式,發生IO時work會先把這個socket夯在哪里 去處理別的請求,等IO完成后再處理剩下的邏輯。nginx 就這么抗住了大量的連接并且充分利用cpu進行處理的。
然后從網上盜兩張圖來看一下nginx 創建監聽到accept的流程:
然后監聽字ngx_event_accept 的處理流程:
第二階段:
所提到的異步 非阻塞IO 及常見幾種IO的差別:
兩階段式IO:
【阻塞 blocking IO】:
recvfrom -> [syscall -> wait -> copy ->] return OK!
【非阻塞 nonblocking IO】:
recvfrom ->[syscall -> wait -> ] return no data ready
recvfrom ->[syscall -> wait -> ] return no data ready
recvfrom ->[syscall -> wait -> ] return data ready
recvfrom ->[syscall -> copy -> ] return OK!
【多路復用IO multiplexingIO】
其中每個IO都是非阻塞IO,先使用poll/select 輪訓IO句柄,如果有準備好的,開始第二階段IO(阻塞讀數據)
select/poll -> [syscall -> wait -> ] return readable
recvfrom -> [syscall -> copy -> ] return OK!
【信號驅動IO signal driven IO】
首先構建一個信號處理器,然后第二階段阻塞讀數據
signal handle -> [syscal -> wait -> ] return
[syscall -> ] signal handle-> recvfrom -> [syscall -> copy -> ] return OK!
【異步IO asynchronous IO】
兩個階段都是非阻塞的
aio_read -> [syscall -> wait -> ] return
[syscall -> copy -> ] aio_readCallback
【對比】
第三階段:
select /poll/ epoll 對比
單個進程能夠監視的文件描述符的數量存在最大限制,通常是1024,
select不足的地方:
1 每次select都要把全部IO句柄復制到內核
2 內核每次都要遍歷全部IO句柄,以判斷是否數據準備好
3 select模式最大IO句柄數是1024,太多了性能下降明顯
poll:
poll使用鏈表保存文件描述符,因此沒有了監視文件數量的限制,但其他三個缺點依然存在。
拿select模型為例,假設我們的服務器需要支持100萬的并發連接,則在_FD_SETSIZE為1024的情況下,則我們至少需要開辟1k個進程才能實現100萬的并發連接。除了進程間上下文切換的時間消耗外,從內核/用戶空間大量的無腦內存拷貝、數組輪詢等,是系統難以承受的。因此,基于select模型的服務器程序,要達到10萬級別的并發訪問,是一個很難完成的任務。
epoll的特點
1 每次新建IO句柄(epoll_create)才復制并注冊(epoll_register)到內核
2 內核根據IO事件,把準備好的IO句柄放到就緒隊列
3 應用只要輪詢(epoll_wait)就緒隊列,然后去讀取數據
只需要輪詢就緒隊列(數量少),不存在select的輪詢,也沒有內核的輪詢,不需要多次復制所有的IO句柄。因此,可以同時支持的IO句柄數輕松過百萬。
詳細的可以看一下這篇文章: