二、master管理多個worker進程
在Nginx的配置文件中有個條目worker_processes,其用于指定master進程可以產(chǎn)生幾個worker進程,默認情況下是CPU執(zhí)行單元的數(shù)目。在Linux下實驗發(fā)現(xiàn),當kill掉worker進程的時候,master進程會自動再次啟動worker進程,但是當kill掉master進程的時候,worker進程仍然活著并向外提供服務(wù),這種方式或許是對于常駐服務(wù)最好的處理語義:master進程存在的時候會保證設(shè)定數(shù)目的工作進程存在,而master進程掛掉的時候worker進程仍然繼續(xù)服務(wù),不會存在單點故障導(dǎo)致服務(wù)立即停止的情況。
其基本原理也很簡單,這源于在Linux平臺下,當子進程退出的時候,內(nèi)核會向父進程發(fā)送SIGCHLD信號,父進程可以捕獲這個信號,并通過wait系統(tǒng)調(diào)用搜集子進程退出的相關(guān)信息,此后子進程的資源會被相應(yīng)的釋放掉。因此,父進程可以通過接收信號的方式異步得到子進程退出的消息,并且適當安排創(chuàng)建工作者進程。
當然,這僅僅是一個小trick,探究一下,發(fā)現(xiàn)Nginx的設(shè)計中,尤其是多進程服務(wù)端程序的開發(fā)維護中,大有學(xué)問可以借鑒!同時還有一個跟Nginx關(guān)系十分密切,估計也是使用相同master-worker方式構(gòu)建的多進程的構(gòu)架的,那就是php-fpm。之所以說關(guān)系密切,就是因為Apache本身支持php的解析,而Nginx只能通過外掛的方式,而掛件最常見的恰巧就是php-fpm了,通過ps查看,其也像是master-worker的結(jié)構(gòu),不過沒看代碼尚且不敢斷定。

2.1 跟蹤環(huán)境的配置
不知道啥時候,自己都快成了代碼控了,GitHub上面一些感興趣的項目代碼都會clone下來并不斷pull跟蹤,nginx就是其中之一啊。調(diào)試環(huán)境設(shè)置很簡單,只是有些點需要額外注意一下
[email protected]:~/nginx# apt-get install libpcre3-dev zlib1g-dev
[email protected]:~/nginx# auto/configure --with-debug
[email protected]:~/nginx# make
上面configure的時候一定要添加–with-debug參數(shù),這個時候可以讓可執(zhí)行程序支持生成debug的log信息,同時如果是MacOS的系統(tǒng)的話,還需要事先用homebrew安裝gcc,然后添加–with-cc=/usr/local/bin/gcc-5指定使用gcc編譯器(后面有時間說是要折騰一下Clang的,而蘋果xcode默認就是用的這貨),不過MacOS底層用的是kqueue而不是epoll,你應(yīng)該知道我要說什么;make編譯之后會在objs目錄下面生成nginx可執(zhí)行程序
[email protected]:~/nginx# mkdir logs
[email protected]:~/nginx# objs/nginx -p .
通過-p參數(shù),可以避免使用默認系統(tǒng)路徑的權(quán)限問題,以及對現(xiàn)有環(huán)境的干擾。此時進程全部轉(zhuǎn)到后臺執(zhí)行了,更要命的是IDE的調(diào)試環(huán)境此處被斷開失連了,所以需要在nginx.c中將系統(tǒng)初始化過程的ngx_daemon()注釋起來,就可以正常斷點跟蹤了。
到此,Nginx的調(diào)試跟蹤環(huán)境設(shè)置完成,設(shè)置conf/nginx.conf中l(wèi)og級別error_log logs/error.log debug;然后通過tail -f logs/error.log所有運行調(diào)試日志盡收眼底。
2.2 多進程服務(wù)端程序設(shè)計
通過官網(wǎng)Nginx文檔大致了解了一下他的構(gòu)架,看的真是讓人拍案叫絕大快人心,請待我慢慢道來。
2.2.1 多進程下的套接字
傳統(tǒng)上Nginx在啟動開始的時候就bind一個地址進行l(wèi)isten,后續(xù)在fork()創(chuàng)建worker process的時候,這些進程是共享這個偵聽套接字的,這個在linux fork()的手冊中明確地被表示出了
The child inherits copies of the parent’s set of open file descriptors. Each file descriptor in the child refers to the same open file description (see open(2)) as the corresponding file descriptor in the parent. The child inherits copies of the parent’s set of open message queue descriptors, open directory streams.
所以master process創(chuàng)建出來的所有worker process都是可以accept()客戶端請求的,當多個進程對同一個socket調(diào)用accept()接收連接的時候,他們都會把自己放到這個套接字的等待隊列上面去,然后一旦有客戶發(fā)起連接請求,這個隊列上面等待的進程就會被喚醒,這個過程在之前分析epoll的時候就介紹過了,但是在較早的epoll版本中,上面的喚醒過程會產(chǎn)生驚群(Thundering Herd)的問題:即使只有一個連接請求到來,也會喚醒在這個共享偵聽套接字上所有等待的進程,而所有進程爭搶這個連接只有一個能獲得連接,其他所有進程都無功而返,所以新版的epoll添加了EPOLLEXCLUSIVE這么一個新的flag,通過在EPOLL_CTL_ADD的時候使用,保證在事件就緒的時候不會產(chǎn)生驚群的問題。