
Nginx現(xiàn)在是非?;鸨膚eb服務(wù)器,她使用更少的資源,支持更多的并發(fā)連接數(shù),實現(xiàn)了linux的epoll模型。
Nginx采用的是多進(jìn)程單線程和多路IO復(fù)用模型。使用了I/O多路復(fù)用技術(shù)的Nginx,就成了”并發(fā)事件驅(qū)動“的服務(wù)器。這里再強(qiáng)調(diào)下重點,
- 多進(jìn)程單線程
- 多路IO復(fù)用模型
一、多進(jìn)程單線程
Nginx 自己實現(xiàn)了對epoll的封裝,是多進(jìn)程單線程的典型代表。使用多進(jìn)程模式,不僅能提高并發(fā)率,而且進(jìn)程之間是相互獨立的,一 個worker進(jìn)程掛了不會影響到其他worker進(jìn)程。
master進(jìn)程管理worker進(jìn)程:
- 接收來自外界的信號。
- 向各worker進(jìn)程發(fā)送信號。
- 監(jiān)控woker進(jìn)程的運行狀態(tài)。
- 當(dāng)woker進(jìn)程退出后(異常情況下),會自動重新啟動新的woker進(jìn)程。
注意worker進(jìn)程數(shù),一般會設(shè)置成機(jī)器cpu核數(shù)。因為更多的worker只會導(dǎo)致進(jìn)程之間相互競爭cpu,從而帶來不必要的上下文切換。
二、IO 多路復(fù)用模型 epoll
多路復(fù)用,允許我們只在事件發(fā)生時才將控制返回給程序,而其他時候內(nèi)核都掛起進(jìn)程,隨時待命。
epoll通過在Linux內(nèi)核中申請一個簡易的文件系統(tǒng)(文件系統(tǒng)一般用B+樹數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)),其工作流程分為三部分:
- 調(diào)用 int epoll_create(int size)建立一個epoll對象,內(nèi)核會創(chuàng)建一個eventpoll結(jié)構(gòu)體,用于存放通過epoll_ctl()向epoll對象中添加進(jìn)來的事件,這些事件都會掛載在紅黑樹中。
- 調(diào)用 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 在 epoll 對象中為 fd 注冊事件,所有添加到epoll中的事件都會與設(shè)備驅(qū)動程序建立回調(diào)關(guān)系,也就是說,當(dāng)相應(yīng)的事件發(fā)生時會調(diào)用這個sockfd的回調(diào)方法,將sockfd添加到eventpoll 中的雙鏈表。
- 調(diào)用 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout) 來等待事件的發(fā)生,timeout 為 -1 時,該調(diào)用會阻塞知道有事件發(fā)生。
注冊好事件之后,只要有fd上事件發(fā)生,epoll_wait()就能檢測到并返回給用戶,用戶執(zhí)行阻塞函數(shù)時就不會發(fā)生阻塞了。
epoll()在中內(nèi)核維護(hù)一個鏈表,epoll_wait直接檢查鏈表是不是空就知道是否有文件描述符準(zhǔn)備好了。順便提一提,epoll與select、poll相比最大的優(yōu)點是不會隨著sockfd數(shù)目增長而降低效率,使用select()時,內(nèi)核采用輪訓(xùn)的方法來查看是否有fd準(zhǔn)備好,其中的保存sockfd的是類似數(shù)組的數(shù)據(jù)結(jié)構(gòu)fd_set,key 為 fd,value為0或者1(發(fā)生時間)。
能達(dá)到這種效果,是因為在內(nèi)核實現(xiàn)中epoll是根據(jù)每 sockfd 上面的與設(shè)備驅(qū)動程序建立起來的回調(diào)函數(shù)實現(xiàn)的。那么,某個sockfd上的事件發(fā)生時,與它對應(yīng)的回調(diào)函數(shù)就會被調(diào)用,將這個sockfd加入鏈表,其他處于“空閑的”狀態(tài)的則不會。在這點上,epoll 實現(xiàn)了一個"偽"AIO。
可以看出,因為一個進(jìn)程里只有一個線程,所以一個進(jìn)程同時只能做一件事,但是可以通過不斷地切換來“同時”處理多個請求。
例子:Nginx 會注冊一個事件:“如果來自一個新客戶端的連接請求到來了,再通知我”,此后只有連接請求到來,服務(wù)器才會執(zhí)行 accept() 來接收請求。又比如向上游服務(wù)器(比如 php-FPM)轉(zhuǎn)發(fā)請求,并等待請求返回時,這個處理的 worker 不會在這阻塞,它會在發(fā)送完請求后,注冊一個事件:“如果緩沖區(qū)接收到數(shù)據(jù)了,告訴我一聲,我再將它讀進(jìn)來”,于是進(jìn)程就空閑下來等待事件發(fā)生。
這樣,基于 多進(jìn)程+epoll, Nginx 便能實現(xiàn)高并發(fā)。
三、worker進(jìn)程工作流程
當(dāng)一個 worker 進(jìn)程在 accept() 這個連接之后,就開始讀取請求,解析請求,處理請求,產(chǎn)生數(shù)據(jù)后,再返回給客戶端,最后才斷開連接,一個完整的請求。一個請求,完全由worker進(jìn)程來處理,而且只會在一個worker進(jìn)程中處理。優(yōu)點:
- 節(jié)省鎖帶來的開銷。每個worker進(jìn)程都彼此獨立地工作,不共享任何資源,因此不需要鎖。同時在編程以及問題排查上時,也會方便很多。
- 獨立進(jìn)程,減少風(fēng)險。采用獨立的進(jìn)程,可以讓互相之間不會影響,一個進(jìn)程退出后,其它進(jìn)程還在工作,服務(wù)不會中斷,master進(jìn)程則很快重新啟動新的worker進(jìn)程。當(dāng)然,worker進(jìn)程自己也能發(fā)生意外退出。
四、對驚群效應(yīng)的處理
Nginx提供了一個accept_mutex這個東西,這是一個加在accept上的一把互斥鎖。即每個worker進(jìn)程在執(zhí)行accept()之前都需要先獲取鎖,accept()成功之后再解鎖。有了這把鎖,同一時刻,只會有一個進(jìn)程執(zhí)行accpet(),這樣就不會有驚群問題了。accept_mutex是一個可控選項,我們可以顯示地關(guān)掉,默認(rèn)是打開的。






