初探nginx架構(gòu)###
淘寶團隊的nginx教材
nginx版本1.12
- nginx與外界,nginx的master與worker之間都是通過信號相連接的。
例子:從容的重啟
master接受到信號后,重新加載配置,用新配置fork新的worker出來。
master向老的worker發(fā)送信號,老的worker收到信號后,不再接受處理新的連接。
老的worker處理完現(xiàn)有連接后,從容退出。
這樣,利用信號就達到了不中斷服務(wù)的重新加載配置。
相關(guān)代碼:[20170512更新]
1)ngx_process.c:signals全局數(shù)組,line 39,定義了所有的信號名(nginx自定名和系統(tǒng)信號名的對應(yīng))
2)ngx_config.h:NGX_RECONFIGURE_SIGNAL 被定義為SIGHUP
3)ngx_process.c:ngx_signal_handler,所有的信號處理函數(shù)都被歸到一個信號處理函數(shù)中,用switch處理,對于worker進程,SIGHUP被忽略了,對于主進程,它把一個全局的變量(類型sig_atomic_t,待擴展)ngx_reconfigure設(shè)置成了1。
4)ngx_process_cycle.c(os/unix):3)提到的ngx_reconfigure被設(shè)置成1之后,主循環(huán)有一個判斷,調(diào)用了ngx_start_worker_processes。重新建立了幾個類型為JUST_RESPAWN的worker,這些是新worker。然后向所有進程發(fā)送NGX_SHUTDOWN_SIGNAL(定義為SIGQUIT)
5)3中ngx_signal_handler中worker部分,對NGX_SHUTDOWN_SIGNAL的處理是置全局變量ngx_quit = 1;前面提到的worker_process_cycle中對這個進行了判斷,關(guān)閉監(jiān)聽套接字,關(guān)閉空閑連接,然后退出進程。(關(guān)于SIGCHLD的跟蹤待擴展)
6)子進程退出會有SIGCHLD信號觸發(fā),ngx_signal_handler會設(shè)置ngx_reap = 1并且waitpid等到退出子進程的pid,線性遍歷數(shù)組找到子進程的控制塊,設(shè)置.exited = 1;主進程會去reap .exited=1的子進程。
結(jié)論:SIGHUP觸發(fā)主進程的RECONFIGURE動作==>主進程建立新worker==>向所有老worker發(fā)送SIGQUIT信號==>收到>SIGQUIT的老worker掃尾退出==>主進程去收割老worker的尸體。熱重啟完成。
- nginx保證每個worker等概率接受處理請求的方法
nginx沒有用一個線程接收然后分發(fā)到各個線程中去的做法。master進程初始化listenfd,然后fork出worker進程,worker進程持有l(wèi)istenfd,在注冊讀事件前搶一個全局的accept_mutex互斥鎖,搶到互斥鎖的那個進程,向epoll注冊讀事件,所以接受連接這件事,都是worker在做。
但是這樣做肯定會有不公平的事情發(fā)生:ngx_accept_disabled變量,它定義為worker所能承受的最大連接數(shù) * 1/8 - 空閑連接數(shù),當(dāng)空閑連接數(shù)小于最大連接數(shù)的1/8時,ngx_accept_disabled大于零,此時的worker就不會去搶鎖了,轉(zhuǎn)而變?yōu)槊看螕屾i的時候,會將ngx_accept_disabled減1,直至該變量小于0。
相關(guān)代碼:
ngx_event.c:try_lock_accept_mutex()去獲得鎖,然后獲得鎖的進程去注冊accept事件。
美團面試問到:如果你自己設(shè)計服務(wù)器,設(shè)計為多進程還是多線程?
答案幾乎一定是多進程,就是因為進程CRASH了,其他的進程還不受影響,只影響一部分服務(wù),線程如果CRASH了
整個進程就都沒了。