Nginx服務器web請求處理機制
從設計架構來說,Nginx服務器是與眾不同的。不同之處一方面體現在它的模塊化設計,另一方面,也是最重要的一方面,體現在它對客戶端請求的處理機制上。
Web服務器和客戶端是一對多的關系,Web服務器必須有能力同時為多個客戶端提供服務。一般來說,完成並發處理請求工作有三種方式可供選擇、多進程、多線程、異步方式。
多進程方式
多進程方式是指,服務器每當接收到一個客戶端時,就由服務器主進程生成一個子進程出來和該客戶端建立連接進行交互,直到連接斷開,改子進程就結束了。
多進程方式的優點在於,設計和實現相對簡單,各個子進程之間相互獨立,處理客戶端請求的過程彼此不受到干擾,並且當一個子進程產生問題時,不容易將影響蔓延到其他進程中,這保證了提供服務的穩定性。當子線程退出時,其占用資源會被操作系統回收,也不會留下任何垃圾。而其缺點也很明顯。操作系統中生成一個子進程需要進行內存復制等操作,在資源和時間上回產生一定的額外開銷,因此,如果Web服務器接收大量並發請求,就會對系統資源造成壓力,導致系統性能下降。
初期的Apache服務器就是采用這種方式對外提供服務的。為了應對大量並發請求,Apache服務器采用“預生成進程”的機制對多進程方式進行改進,“預生成進程”的工作方式很好理解。它將生成子進程的時機提前,在客戶端請求還沒有到來之前就預先生成好,當請求到來時,主進程分配一個子進程和該客戶端進行交互,交互完成之后,該進程也不結束,而被主進程管理起來等待下一個客戶端請求的到來,改進的多進程方式在一定程度上緩解了大量並發請求情形下Web服務器對系統資源造成的壓力。但是優化Apache服務器在最初的構架設計上采用了多進程方式,因此這不能從根本上解決問題。
多線程方式
多線程方式和多進程方式相似,他是指,服務器每接收到一個客戶端時,會有服務器主進程派生一個線程出來和該客戶端進行交互。
由於操作系統產生一個線程的開銷遠遠小於產生一個進程的開銷,所以多線程方式在很大程度上減輕了Web服務器對系統資源的要求。該方式使用線程進行任務調度,開發方面可以遵循一定的標准,這相對來說比較規范和有利於協作。但在線程管理方面,改方式有一定的不足。多個線程位於同一個進程內,可以訪問同樣的內存空間,彼此之間相互影響;同時,在開發過程中不過避免地要開發者自己對內存進行管理,其增加了出錯的風險。服務器系統需要長時間連續不停地運轉,錯誤的逐漸積累可能最終對整個服務器產生重大影響。
IIS服務,器使用了多線程方式對外提供服務,它的穩定性相對來說還是不錯的,但對於經驗豐富的Web服務器管理人員而言,他們通常還是會定期檢查和重啟服務器,以預防不可預料的故障發生。
異步方式
異步方式是和多進程方式及多線程方式完全不同的一種處理客戶端請求的方式。在介紹改方式之前 ,我們先復習下同步、異步以及阻塞、非阻塞的概念。
網絡通信中的同步機制和異步機制是描述通信模塊的概念。同步機制,是指發送方發送請求后,需要等待接收到接收方發回的響應后,才接着發送下一個請求;異步機制,和同步機制正好相反,在異步機制中,發送方發出一個請求后,不等待接收方響應這個請求,就繼續發送下個請求。在同步機制中,所有的請求在服務器端得到同步,發送方和接收方對請求的處理步調是一致的;在異步機制中,所有來着發送方的請求形成一個隊列,接收方處理完成后通知發送方。
阻塞和非阻塞用來描述進程處理調用的方式,在網絡通信中,主要指網絡套接字Socket的阻塞和非阻塞方式,而Socket的實質也是IO操作,Socket的阻塞調用方式為,調用結果返回之前,當前線程從運行狀態被掛起,一直等到調用結果返回之后,才進行就緒狀態,獲取CPU后繼續執行;Socket的非阻塞調用方式和阻塞方式調用方式正好相反,在非阻塞方式中,如果調用結果不能馬上返回,當前線程也不會被掛起,而是立即返回執行下一個調用。
在網絡通信中,經常可以看到有人講同步和阻塞等同、異步和非阻塞等同。事實上,這兩對概念有一定的區別,不能混淆。兩對概念的組合,就會產生四個新的概念,同步阻塞、異步阻塞、同步非阻塞、異步非阻塞。
- 同步阻塞方式,發送方向接收方發送請求后,一直等待響應;接收方處理請求是進行的IO操作如果不能馬上得到結果,就一直等到返回結果后,才響應發送方,期間不能進行其他工作。比如、在超時排隊付賬時,客戶(發送方)想收款員(接收方)付款(發送請求)后需要等待收款員找零,期間不能做其他的事情;而收款員要等待收款機返回結果(IO操作)后才能把零錢取出來交給客戶(響應請求),期間也只能等待,不能做其他的事情。這種方式實現簡單,但是效率不高。
- 同步非阻塞方式,發送方向接收方發送請求后,一直等待響應;接收方處理請求時進行的IO操作如果不能馬上得到結果,就立即返回,去做其他事情,但由於沒有得到請求處理結果,不響應發送方,發送方一直在等待,一直等IO操作完成后,接收方獲得結果響應發送方后,接收方才進入下一次請求過程。在實際中不使用這種方式。
- 異步阻塞方法,發送方向接收方發送請求后,不用等待響應,可以繼續其他工作;接收方處理請求是進行的IO操作如果不能馬上得到結果,就一直等到返回結果后,才響應發送方,期間不能進行其他工作。這種方式在實際中也不使用。
- 異步非阻塞方式,發送方向接收方發送請求后,不用等待響應,可以繼續其他工作;接收方處理請求時進行的IO操作富國不能馬上得到結果,也不等待,而是馬上返回去做其他事情。當IO操作完成以后,將完成狀態和結果通知接收方,接收方再響應發送方。繼續使用在超市付賬排隊的例子。客戶(發送方)想收款員(接收方)付款(發送請求)后在等待收款員找零的過程中,還可以做其他事情,比如打電話、聊天等;而收款員在等待收款機處理交易(IO操作)的過程中可以幫助客戶將商品打包,當收款機產生結果后,收款員給客戶結賬(響應請求)。在四種方式中,這種方式是發送方和接收方通信效率最高的一種。
Nginx如何處理請求
Nginx服務器的一個顯著優勢是能夠同時處理大量並發請求。它結合多進程機制和異步機制對外提供服務,異步機制使用的是異步非阻塞方式
Nginx服務器啟動后,可以產生一個主進程(master process)和多個工作進程(worker processes),其中可以在配置文件中指定產生的工作進程數量。Nginx服務器的所有工作進程都用於接收和處理客戶端的請求。這類似於Apache使用的改進的多進程機制,預先生成多個工作進程,等待處理客戶端請求。
每個工作進程使用了異步非阻塞方式,可以處理多個客戶端請。當某個工作進程接收到客戶端的請求以后,調用IO進行處理,如果不能立即得到結果,就去處理其他的請求;兒客戶端在此期間業務需等待響應,可以去處理其他的事情;當IO調用返回結果時,就會通知此工作進程;該進程的到通知,暫時掛起當前處理的事務,去響應客戶端請求。
客戶端請求數量增長、網絡負載繁重時,Nginx服務器使用多進程機制能夠保證不增長對系統資源的壓力;同時使用異步非阻塞方式減少了工作進程在I/O調用上的阻塞延遲,保證了不降低對請求的處理能力。
Nginx事件驅動處理模型
在上面我們提到,Nginx服務器的工作進程調用IO后,就去進行其他工作了;當IO調用返回后,會通知工作進程。這里有一個問題,IO調用時如何把自己的狀態通知給工作進程的呢?
一般解決這個問題的方案有兩種。一是,讓工作進程在進行其他工作的過程中間隔一段時間就去檢查一下IO 的運行狀態,如果完成,就去響應客戶端,如果未完成,就繼續在進行的工作;二是,IO調用在完成后能主動通知工作進程。對於前者,雖然工作進程在IO調用過程中沒有等待,但不斷的檢察仍然在時間和資源上導致了不小的開銷,最理想的解決方案就是第二種。
具體來說,select/pool/epool/kqueue等這樣的系統調用就是用來支持第二種解決方案的。這些系統調用,也常被稱為事件驅動模型,他們提供了一種機制,讓進程可以同時處理多個並發請求,不用關心IO調用的具體狀態,IO調用完全由事件驅動模型來管理,事件准備好之后就通知工作進程事件已經就緒。
事件驅動處理庫有被稱為多路IO復用方法,最常見的包括以下三種:select模型、poll模型和epoll模型。
select庫
select庫,是各個版本的Linux和Windows平台都支持的基本事件驅動模型庫,並且在接口的定義上也基本相同,只是部分參數的含義略有差異。使用select庫的步驟一般是:
首先,創建所關注事件的描述符集合。對於一個描述符,可以關注其上面的讀(Read)事件、寫(Write)事件以及異常發生(Exception)事件,所以要創建三類事件描述集合,分別用來收集讀事件的描述符、寫事件的描述符和異常事件的描述符。
其次,調用底層提供的select()函數,等待事件發生。這里需要注意的一點是,select的阻塞與是否設置非阻塞I/O是沒有關系的。
然后,輪詢所有事件描述符集合中的每個事件描述符,檢查是否有相應的事件發生,如果有,就進行處理。
Nginx服務器在編譯過程中如果沒有為其他指定其他高性能事件驅動模型庫,它將自動編譯該庫。我們可以使用--with-select_module和--without-select_module兩個參數強制Nginx是否編譯該庫。
poll庫
poll庫,作為Linux平台上的基本事件驅動模型,是在Linux2.1.23中引入。Windwos平台不支持poll庫。
poll與select的基本工作方式是相同的,都是先創建一個關注事件的描述符集合,再去等待這些事件的發生,然后再輪詢描述符集合,檢查有沒有事件發生,如果有,就進行處理。
poll庫與select庫的主要區別在於,select庫需要為讀事件、寫事件和異常事件分別創建一個描述符集合,因此在最后輪詢的時候,需要分別輪詢這三個集合。而poll庫只需要創建一個集合,在每個描述符對應的結構上分別設置讀事件、寫事件或者異常事件,最后輪詢的時候,可以同時檢查者三種事件是否發生。可以說,poll庫是select庫的優化實現。
Nginx服務器在編譯過程中如果沒有為其制定其他高性能事件驅動模型庫,它將自動編譯該庫。我們可以使用--with-poll_module和--without-poll_module兩個參數強制Nginx是否編譯該庫
epoll庫
epoll庫是Nginx服務器支持的是高性能事件驅動庫之一,它是公認的非常優秀的事件驅動模塊,和poll庫及select庫有很大的不同。epoll屬於poll庫的一個變種,是在Linux2.5.44中引入的,在Linux2.6及以上的版本都可以使用它。poll庫和select庫在實際工作中,最大的區別在於效率。
從前面的介紹我們知道,它們的處理方式都是創建一個待處理事件列表,然后把這個列表發給內核,返回的時候,再去輪詢檢查這個列表,以判斷事件是否發生。這樣在描述符比較多的應用中,效率就顯得比較低下了。一種比較好的做法是,把描述符列表的管理交由內核負責,一旦有某種事件發生,內核把發生時間的描述符列表通知給進程,這樣就避免了輪詢整個描述符列表。epoll庫就是這樣一種模型。
首先,epoll庫通過相關調用通知內核創建一個有N個描述符的事件列表;然后,給這些描述符設置鎖關注的事件,並把它添加到內核的事件列表中去,在具體的編碼過程中也可通過相關調用對時間列表中描述符進行修改和刪除。
完成設置之后,epoll庫就開始等待內核通知事件發生了。某一事件發生后,內核講發生事件的描述符列表上報給epoll庫。得到時間列表的epoll庫,就可以進行事件處理了。
epoll庫在Linux平台上是搞笑的。它支持一個進程打開大數目的時間描述符,上限是系統可以打開文件的最大數目;同時,epoll庫的IO效率不隨描述符數目增加而線性下降,因為它只會對內核上報的“活躍”的描述符進行操作
上面的全部內容均出自於《Nginx高性能Web服務器詳情》中第三章Nginx服務器架構初探(我純手動打上去的/(ㄒoㄒ)/~~ ┭┮﹏┭┮),對於理解Python中三者的關系真的特別有幫助;
Python開發【第九章】:線程、進程和協程---》》http://www.cnblogs.com/lianzhilei/p/5881434.html
Python開發【第十章】:I/O多路復用、異步I/O(綜合篇)---》》http://www.cnblogs.com/lianzhilei/p/5955526.html