Nginx對于共享accept套接字驚群問題的處理,有三個方法:
(1). accept_mutex = on
當這個選項打開的時候,worker process在其任務循環(huán)的時候,會首先通過ngx_trylock_accept_mutex去獲得一個進程間的ngx_accept_mutex互斥鎖,而該鎖通常是使用文件鎖來實現(xiàn)的。在持有這個鎖的時候,首先收集底層就緒的事件,同時執(zhí)行accept的所有回調(diào),然后釋放該鎖,處理一般的非accept事件。
(2). accept_mutex = off
這個設置在較新版本的Nginx已經(jīng)是默認關閉的,主要考慮到的是:一來通過EPOLLEXCLUSIVE、下面的SO_REUSEPORT等新技術可以避免accept的時候驚群的問題;另一方面Nginx采用基于事件的處理方式,worker process只有很少的幾個,而不像Apache的技術Prefork很多的子進程,所以即使發(fā)生驚群對系統(tǒng)造成的影響也極為有限。
(3). reuseport
在Linux內(nèi)核3.9的時候,內(nèi)核Socket支持了SO_REUSEPORT選項,而Nginx在1.9版本中引入了這個選項,這樣每個worker process都可以同時偵聽同一個IP:Port地址,內(nèi)核會發(fā)現(xiàn)哪些listener可用,從而自動將連接請求分配給給定的worker process,消除了Nginx傳統(tǒng)上通過用戶態(tài)采用accept_mutex互斥鎖而帶來的性能損耗問題。
上面三種方式的性能對比在官方也給出了 測試結果 。
2.2.2 基于事件的異步模型
異步模型是新一代http服務器Nginx和老牌Apache最大的不同之處:
Apache采用的是Prefork技術,服務啟動之后預先啟動一定數(shù)目的子進程,當服務器壓力增大的時候不斷增加子進程的數(shù)目,而當服務器空閑后自動關閉一些子進程,雖然這種彈性常駐子進程比One Child per Client的模型要進步很多,但是經(jīng)過這么久的多進程、協(xié)程開發(fā)技術的熏陶可知,子進程的增加只在一定范圍內(nèi)可以增加服務能力,同時子進程在進程切換、內(nèi)存等方面會對服務器帶來很大的壓力,如果當連接客戶達到C10K的時候其占用的資源是不可估量的。
Nginx采用的是基于事件驅動的模型來解決C10K問題,所以通常Nginx只需要啟動很少(通常CPU執(zhí)行單元個數(shù))的worker process就可以同時服務大量連接,以至于越來越多的http服務器遷移到Nginx平臺上面。其工作流程主要是:
當master process通過fork()創(chuàng)建出幾個worker process的時候,worker process進程主執(zhí)行函數(shù)為ngx_worker_process_cycle(),這里面除了檢查各種狀態(tài)標識(比如接受到父進程發(fā)送的信號后,設置ngx_terminate、ngx_quit、ngx_reopen等標識)作出特定行為外,其正事主要是通過ngx_process_events_and_timers處理事件:
此時如果accept_mutex==on,而當ngx_trylock_accept_mutex搶鎖失敗則直接返回,否則就會設置NGX_POST_EVENTS這個標識,表示事件的回調(diào)延后執(zhí)行。因為我們要把持鎖的臨界區(qū)降低,所以在持鎖的過程中,通過ngx_process_events(實質(zhì)乃是ngx_epoll_module_ctx.actions)檢查底層偵聽套接字就緒的事件,根據(jù)epoll特性可以快速的收集就緒事件并添加到ngx_posted_accept_events和ngx_posted_events隊列上去,執(zhí)行ngx_posted_accept_events隊列回調(diào)后釋放鎖,最后執(zhí)行一般的事件回調(diào)操作。
如果accept_mutex==off,那么在ngx_process_events的過程中,事件的回調(diào)將會在搜集就緒事件的過程中同步執(zhí)行。
2.3 Nginx配置文件和二進制程序平滑升級
Nginx中多進程之間將信號運用的活靈活現(xiàn)(Windows平臺下沒用借用信號的方式,而是用其特有的Event事件進行的通信),使得Nginx可以在不間斷服務的情況下進行配置文件,甚至是二進制文件的平滑升級操作,信號的含義可以參見ngx_config.h,信號處理參見ngx_process.c:ngx_signal_handler,在信號處理文件中其實也只是設置一些狀態(tài)變量,然后在進程的時間循環(huán)中去執(zhí)行相應的操作,比如向worker process發(fā)送特定信號、啟動worker process等。
2.3.1 Nginx配置文件平滑升級
通過nginx –s reload或者直接kill -SIGHUP向Nginx master process發(fā)送信號,當master process接受到SIGHUP信號的時候:
a. 檢查配置文件,然后打開新的listen socket和日志文件,如果失敗則讓old nginx繼續(xù)執(zhí)行,否則
b. 創(chuàng)建新的worker process,同時向old worker process發(fā)送信息,讓他們graceful關閉,old worker process會關閉偵聽套接字,服務已經(jīng)連接的客戶,當所有連接客戶服務完了之后退出
2.3.2 Nginx二進制程序平滑升級
將新的二進制文件拷貝覆蓋原二進制執(zhí)行文件,然后向master process發(fā)送SIGUSR2信號,當master process接收到該信號的時候: