高并發 Nginx + lua是如何抗住的

提到高并發或者抗壓力,有這種高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的內部實現。

image.png

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的流程:

image.png

然后監聽字ngx_event_accept 的處理流程:

image.png

第二階段:

所提到的異步 非阻塞IO 及常見幾種IO的差別:

兩階段式IO:

image.jpeg

【阻塞 blocking IO】:

recvfrom -> [syscall -> wait -> copy ->] return OK!

image.jpeg

【非阻塞 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!

image.jpeg

【多路復用IO multiplexingIO】

其中每個IO都是非阻塞IO,先使用poll/select 輪訓IO句柄,如果有準備好的,開始第二階段IO(阻塞讀數據)

select/poll -> [syscall -> wait -> ] return readable

recvfrom -> [syscall -> copy -> ] return OK!

image.jpeg

【信號驅動IO signal driven IO】

首先構建一個信號處理器,然后第二階段阻塞讀數據

signal handle -> [syscal -> wait -> ] return

[syscall -> ] signal handle-> recvfrom -> [syscall -> copy -> ] return OK!

image.jpeg

【異步IO asynchronous IO】

兩個階段都是非阻塞的

aio_read -> [syscall -> wait -> ] return

[syscall -> copy -> ] aio_readCallback

【對比】

image.jpeg
image.jpeg

第三階段:

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句柄數輕松過百萬。

詳細的可以看一下這篇文章:

https://zhuanlan.zhihu.com/p/39970630

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容