nginx異步非阻塞 解析

原文鏈接:https://blog.csdn.net/dutsoft/article/details/55224755

同步與異步的理解

同步與異步的重點在消息通知的方式上,也就是調用結果通知的方式。
同步:當一個同步調用發出去後,調用者要一直等待調用結果的通知後,才能進行後續的執行。
異步:當一個異步調用發出去後,調用者不能立即得到調用結果的返回。
異步調用,要想獲得結果,一般有兩種方式:
1、主動輪詢異步調用的結果;
2、被調用方通過callback來通知調用方調用結果。

生活實例

同步取快遞:小明收到快遞將送達的短信,在樓下一直等到快遞送達。
異步取快遞:小明收到快遞將送達的短信,快遞到樓下後,小明再下樓去取。
異步取快遞,小明知道快遞到達樓下有兩種方式:1、不停的電話問快遞小哥到了沒有,即主動輪詢;2、快遞小哥到樓下後,打電話通知小明,然後小明下樓取快遞,即回調通知。

阻塞和非阻塞

阻塞與非阻塞的理解

阻塞與非阻塞的重點在於進/線程等待消息時候的行爲,也就是在等待消息的時候,當前進/線程是掛起狀態,還是非掛起狀態。
- 阻塞阻塞調用在發出去後,在消息返回之前,當前進/線程會被掛起,直到有消息返回,當前進/線程纔會被激活
- 非阻塞非阻塞調用在發出去後,不會阻塞當前進/線程,而會立即返回。

生活實例

阻塞取快遞:小明收到快遞即將送達的信息後,什麼事都不做,一直專門等快遞。
非阻塞取快遞:小明收到快遞即將送達的信息後,等快遞的時候,還一邊敲代碼、一邊刷微信。

同步與異步,重點在於消息通知的方式;阻塞與非阻塞,重點在於等消息時候的行爲。
所以,就有了下面4種組合方式

1. 同步阻塞:小明收到信息後,啥都不幹,等快遞;
2. 同步非阻塞:小明收到信息後,邊刷微博,邊等着取快遞;
3. 異步阻塞:小明收到信息後,啥都不幹,一直等着快遞員通知他取快遞;
4. 異步非阻塞:小明收到信息後,邊刷着微博,邊等快遞員通知他取快遞。
  • 1
  • 2
  • 3
  • 4

大部分程序的I/O模型都是同步阻塞的,單個進程每次只在一個文件描述符上執行I/O操作,每次I/O系統調用都會阻塞,直到完成數據傳輸。
傳統的服務器採用的就是同步阻塞的多進程模型。一個server採用一個進程負責一個request的方式,一個進程負責一個request,直到會話結束。進程數就是併發數,而操作系統支持的進程數是有限的,且進程數越多,調度的開銷也越大,因此無法面對高併發。

Nginx採用了異步非阻塞的方式工作。那麼Nginx是如何實現異步非阻塞的呢?那得先了解一下I/O多路複用。

I/O多路複用

所謂的I/O複用,就是多個I/O可以複用一個進程。I/O多路複用允許進程同時檢查多個fd,以找出其中可執行I/O操作的fd。
系統調用select()和poll()來執行I/O多路複用。在Linux2.6中引入的epoll()是select()的升級版,提供了更高的性能。通過I/O複用,我們可以在一個進程處理大量的併發I/O。

初級版I/O複用

比如一個進程接受了10000個連接,這個進程每次從頭到尾的問一遍這10000個連接:“有I/O事件沒?有的話就交給我處理,沒有的話我一會再來問一遍。”然後進程就一直從頭到尾問這10000個連接,如果這10000個連接都沒有I/O事件,就會造成CPU的空轉,並且效率也很低,不好不好。

那麼,如果發明一個代理,每次能夠知道哪個連接有了I/O流事件,不就可以避免無意義的空轉了嗎?爲了避免CPU空轉,可以引進了一個代理(一開始有一位叫做select的代理,後來又有一位叫做poll的代理,不過兩者的本質是一樣的)。

升級版I/O複用

select()

select可以同時觀察許多流的I/O事件,在空閒的時候,會把當前線程阻塞掉,當有一個或多個流有I/O事件時,就從阻塞態中醒來,於是我們的程序就會輪詢一遍所有的流(於是我們可以把“忙”字去掉了)。

while true {
  select(streams[])
  for i in streams[] {
            if i has data
                  read until unavailable
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

select()採用輪詢的方式來檢查fd是否就緒,當fd數量較多時,性能欠佳。因爲從select那裏僅僅知道了,有I/O事件發生了,但卻並不知道是那幾個流(可能有一個,多個,甚至全部),我們只能無差別輪詢所有流,找出能讀出數據,或者寫入數據的流,對他們進行操作。–from 知乎

生活實例

小明家樓下有一個收發室,每次有快遞到了就先代收,但收發室也不知道那個是小明的快遞;但小明去取的時候,要查詢所有代收的快遞。

高級版I/O複用

epoll()

epoll能更高效的檢查大量fd,UNIX中提供了類似功能的kqueue調用。
epoll可以理解爲event poll,不同於忙輪詢和無差別輪詢,當連接有I/O流事件產生的時候,epoll就會去告訴進程哪個連接有I/O流事件產生,然後進程就去處理這個事件。此時我們對這些流的操作都是有意義的。(複雜度降低到了O(k),k爲產生I/O事件的流的個數,也有認爲O(1)的)

生活實例

小明家樓下有一個收發室,每次有快遞到了,就先代收並做了標記;然後通知小明去取送給小明的快遞。

Nginx的異步非阻塞

Nginx配置use epoll後,以異步非阻塞方式工作,能夠輕鬆處理百萬級的併發連接。

events {
    worker_connections  1024;
    use kqueue;  # 在Linux中配置:use epoll;
}
  • 1
  • 2
  • 3
  • 4

在一個Web服務中,延遲最多的就是等待網絡傳輸。nginx在啓動後,會有一個master進程和多個worker進程。master進程主要用來管理worker進程,包含:接收來自外界的信號,向各worker進程發送信號,監控worker進程的運行狀態,當worker進程退出後(異常情況下),會自動重新啓動新的worker進程。而基本的網絡事件,則是放在worker進程中來處理了。在一個請求需要等待的時候,worker可以空閒出來處理其他的請求,少數幾個worker進程就能夠處理大量的併發。

舉例來說:同樣的4個進程,如果採用一個進程負責一個request的方式;那麼,同時進來4個request之後,每個進程就負責其中一個,直至會話關閉。期間,如果有第5個request進來了。就無法及時反應了,因爲4個進程都沒幹完活呢,因此,一般有個調度進程,每當新進來了一個request,就新開個進程來處理。

nginx不這樣,每進來一個request,會有一個worker進程去處理。但不是全程的處理,處理到什麼程度呢?處理到可能發生阻塞的地方。比如向後端服務器轉發request,並等待請求返回。那麼,這個處理的worker不會這麼傻等着,他會在發送完請求後,註冊一個事件:“如果upstream返回了,告訴我一聲,我再接着幹”。於是他就休息去了。此時,如果再有request 進來,他就可以很快再按這種方式處理。而一旦上游服務器返回了,就會觸發這個事件,worker纔會來接手,這個request纔會接着往下走。

總結

web server剛好屬於網絡io密集型應用,不算是計算密集型。web server的這種性質決定了每個request的大部份時間都消耗在網絡傳輸中,實際上花費在server機器上的時間片不多。異步非阻塞,使用epoll,和大量細節處的優化,這就是Nginx幾個進程就解決高併發的祕密所在。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章