<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

        Nginx 采用的是多進(jìn)程(單線程) & 多路IO復(fù)用模型,使用了 I/O 多路復(fù)用技術(shù)的 Nginx,就成了”并發(fā)事件驅(qū)動“的服務(wù)器,同時使用sendfile等技術(shù),最終實現(xiàn)了高性能。主要從以下幾個方面講述Nginx高性能機(jī)制:

      • Nginx master-worker進(jìn)程機(jī)制。
      • IO多路復(fù)用機(jī)制。
      • Accept鎖及REUSEPORT機(jī)制。
      • sendfile零拷貝機(jī)制

      1、Nginx進(jìn)程機(jī)制

        1.1、Nginx進(jìn)程機(jī)制概述

        許多web服務(wù)器和應(yīng)用服務(wù)器使用簡單的線程的(threaded)、或基于流程的(process-based)架構(gòu), NGINX則以一種復(fù)雜的事件驅(qū)動(event-driven)的架構(gòu)脫穎而出,這種架構(gòu)能支持現(xiàn)代硬件上成千上萬的并發(fā)連接。

        NGINX有一個主進(jìn)程(master process)(執(zhí)行特定權(quán)限的操作,如讀取配置、綁定端口)和一系列工作進(jìn)程(worker process)和輔助進(jìn)程(helper process)。如下圖所示:

        

        如下所示:

      # service nginx restart
      * Restarting nginx
      # ps -ef --forest | grep nginx
      root     32475     1  0 13:36 ?        00:00:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf
      nginx    32476 32475  0 13:36 ?        00:00:00  _ nginx: worker process
      nginx    32477 32475  0 13:36 ?        00:00:00  _ nginx: worker process
      nginx    32479 32475  0 13:36 ?        00:00:00  _ nginx: worker process
      nginx    32480 32475  0 13:36 ?        00:00:00  _ nginx: worker process
      nginx    32481 32475  0 13:36 ?        00:00:00  _ nginx: cache manager process
      nginx    32482 32475  0 13:36 ?        00:00:00  _ nginx: cache loader process

        如上4核服務(wù)器所示,NGINX主進(jìn)程創(chuàng)建了4個工作進(jìn)程和2個緩存輔助進(jìn)程(cachehelper processes)來管理磁盤內(nèi)容緩存(on-disk content cache)。如果我們不配置緩存,那么就只會有master、worker兩個進(jìn)程,worker進(jìn)程的數(shù)量,通過配置文件worker_process進(jìn)行配置(一般worker_process數(shù)與服務(wù)器CPU核心數(shù)一致),如下所示:

      $ cat nginx.conf|grep process 
      worker_processes  3;
      
      $ ps -ef|grep nginx           
        501 33758     1   0 四03上午 ??         0:00.02 nginx: master process ./bin/nginx
        501 56609 33758   0 11:58下午 ??         0:00.00 nginx: worker process
        501 56610 33758   0 11:58下午 ??         0:00.00 nginx: worker process
        501 56611 33758   0 11:58下午 ??         0:00.00 nginx: worker process

        NGINX根據(jù)可用的硬件資源,使用一個可預(yù)見式的(predictable)進(jìn)程模型:

      • Master主進(jìn)程執(zhí)行特權(quán)操作,如讀取配置和綁定端口,還負(fù)責(zé)創(chuàng)建少量的子進(jìn)程(以下三種進(jìn)程)。
      • Cache Loader緩存加載進(jìn)程在啟動時運行,把基于磁盤的緩存(disk-based cache)加載到內(nèi)存中,然后退出。緩存加載進(jìn)程的調(diào)度很謹(jǐn)慎,所以其資源需求很低。
      • Cache Manager緩存管理進(jìn)程周期性運行,并削減磁盤緩存(prunes entries from the disk caches)來使緩存保持在配置的大小范圍內(nèi)。
      • Worker工作進(jìn)程才是執(zhí)行所有實際任務(wù)的進(jìn)程:處理網(wǎng)絡(luò)連接、讀取和寫入內(nèi)容到磁盤,與upstream服務(wù)器通信等。

        多數(shù)情況下,NGINX建議每1個CPU核心都運行1個工作進(jìn)程,使硬件資源得到最有效的利用。你可以在配置中設(shè)置如下指令:

      worker_processes auto;

        當(dāng)NGINX服務(wù)器運行時,只有Worker工作進(jìn)程在忙碌。每個工作進(jìn)程都以非阻塞的方式處理多個連接,以減少上下文切換的開銷。 每個工作進(jìn)程都是單線程且獨立運行的,抓取并處理新的連接。進(jìn)程間通過共享內(nèi)存的方式,來共享緩存數(shù)據(jù)、持久性會話數(shù)據(jù)(session persistence data)和其他共享資源。

        1.2、Master進(jìn)程

        nginx啟動后,系統(tǒng)中會以daemon的方式在后臺運行,后臺進(jìn)程包含一個master進(jìn)程和多個worker進(jìn)程。當(dāng)然nginx也是支持多線程的方式的,只是我們主流的方式還是多進(jìn)程的方式,也是nginx的默認(rèn)方式。我們可以手動地關(guān)掉后臺模式,讓nginx在前臺運行,并且通過配置讓nginx取消master進(jìn)程,從而可以使nginx以單進(jìn)程方式運行。生產(chǎn)環(huán)境下肯定不會這么做,所以關(guān)閉后臺模式,一般是用來調(diào)試用的。

        master進(jìn)程主要用來管理worker進(jìn)程,包含:接收來自外界的信號,向各worker進(jìn)程發(fā)送信號,監(jiān)控worker進(jìn)程的運行狀態(tài),當(dāng)worker進(jìn)程退出后(異常情況下),會自動重新啟動新的worker進(jìn)程。

        1.3、Worker進(jìn)程

        每一個Worker工作進(jìn)程都是使用NGINX配置文件初始化的,并且主節(jié)點會為其提供一套監(jiān)聽套接字(listen sockets)。 worker進(jìn)程之間是平等的,每個進(jìn)程,處理請求的機(jī)會也是一樣的。當(dāng)我們提供80端口的http服務(wù)時,一個連接請求過來,每個進(jìn)程都有可能處理這個連接,怎么做到的呢?首先,每個worker進(jìn)程都是從master進(jìn)程fork過來,在master進(jìn)程里面,先建立好需要listen的socket(listenfd)之后,然后再fork出多個worker進(jìn)程。

        所有worker進(jìn)程的listenfd會在新連接到來時變得可讀,為保證只有一個進(jìn)程處理該連接,所有worker進(jìn)程在注冊listenfd讀事件前搶accept_mutex,搶到互斥鎖的那個進(jìn)程注冊listenfd讀事件,在讀事件里調(diào)用accept接受該連接。當(dāng)一個worker進(jìn)程在accept這個連接之后,就開始讀取請求,解析請求,處理請求,產(chǎn)生數(shù)據(jù)后,再返回給客戶端,最后才斷開連接,這樣一個完整的請求就是這樣的了。所以Worker工作進(jìn)程通過等待在監(jiān)聽套接字上的事件(accept_mutex和kernel socketsharding)開始工作。事件是由新的incoming connections初始化的。這些連接被會分配給狀態(tài)機(jī)(statemachine)—— HTTP狀態(tài)機(jī)是最常用的,但NGINX還為流(原生TCP)和大量的郵件協(xié)議(SMTP,IMAP和POP3)實現(xiàn)了狀態(tài)機(jī)。

        

        狀態(tài)機(jī)本質(zhì)上是一組告知NGINX如何處理請求的指令。大多數(shù)和NGINX具有相同功能的web服務(wù)器也使用類似的狀態(tài)機(jī)——只是實現(xiàn)不同。如下圖所示,就是一個HTTP請求的生命周期:

        

        關(guān)于Nginx的處理流程,也可以如下圖所示:

        

        1.4、 Nginx信號管理

        Nginx可以通過信號的方式進(jìn)行管理,信號在Master-Worker的關(guān)系以及對應(yīng)的命令行,如下圖所示:

         

        其具體使用方式為:

      # 以下SIG有無前綴含義一樣,如SIGHUP和HUP等同
      kill  -信號  進(jìn)程號

        Nginx主要是通過信號量來控制Nginx,所以我們常用的Nginx命令也都可以通過信號的方式進(jìn)行執(zhí)行。具體含義如下所示:

      The master process of nginx can handle the following signals:
           SIGINT, SIGTERM   Shut down quickly. //nginx的進(jìn)程馬上被關(guān)閉,不能完整處理正在使用的nginx的用戶的請求,等同于 nginx -s stop
           SIGHUP           Reload configuration, start the new worker process with a new con‐figuration, and gracefully shut down 
                   old worker processes. //nginx進(jìn)程不關(guān)閉,但是重新加載配置文件。等同于nginx -s reload SIGQUIT Shut down gracefully. //優(yōu)雅的關(guān)閉nginx進(jìn)程,在處理完所有正在使用nginx用戶請求后再關(guān)閉nginx進(jìn)程,等同于nginx -s quit SIGUSR1 Reopen log files. //不用關(guān)閉nginx進(jìn)程就可以重讀日志,此命令可以用于nginx的日志定時備份,按月/日等時間間隔分割有用等同于nginx -s reopen SIGUSR2 Upgrade the nginx executable on the fly. //nginx的版本需要升級的時候,不需要停止nginx,就能對nginx升級 SIGWINCH Shut down worker processes gracefully. //配合USR2對nginx升級,優(yōu)雅的關(guān)閉nginx舊版本的進(jìn)程。 While there is no need to explicitly control worker processes normally, they support some signals too: SIGTERM Shut down quickly. SIGQUIT Shut down gracefully. SIGUSR1 Reopen log files.

        1.4.1、配置熱加載Reload

        如下圖所示,NGINX的進(jìn)程體系結(jié)構(gòu)具有少量的Worker工作進(jìn)程,因此可以非常有效地更新配置,甚至可以更新NGINX二進(jìn)制文件本身。

        

        更新NGINX配置是一個非常簡單,輕量且可靠的操作。它通常僅意味著運行nginx -s reload命令,該命令檢查磁盤上的配置并向主進(jìn)程發(fā)送SIGHUP信號。當(dāng)主進(jìn)程收到SIGHUP時,它將執(zhí)行以下兩項操作:

      • 重新加載配置并派生一組新的工作進(jìn)程。這些新的工作進(jìn)程立即開始接受連接和處理流量(使用新的配置設(shè)置)。
      • 指示舊工作進(jìn)程正常退出。工作進(jìn)程停止接受新連接。當(dāng)前的每個HTTP請求完成后,工作進(jìn)程就會干凈地關(guān)閉連接(即,沒有持久的keepalive)。一旦所有連接都關(guān)閉,工作進(jìn)程將退出。

        此重新加載過程可能會導(dǎo)致CPU和內(nèi)存使用量的小幅上升,但是與從活動連接中加載資源相比,這通常是察覺不到的。您可以每秒多次重載配置(許多NGINX用戶正是這樣做的)。即使當(dāng)有許多版本NGINX工作進(jìn)程等待連接關(guān)閉時也很少有問題出現(xiàn),而且實際上這些連接很快被處理完并關(guān)閉掉。

        詳細(xì)過程如下:

      • 向master進(jìn)程發(fā)送HUP信號(reload)命令
      • master進(jìn)程校驗配置語法是否正確
      • master進(jìn)程打開新的監(jiān)聽端口
      • master進(jìn)程用新配置啟動新的worker子進(jìn)程
      • master進(jìn)程向老worker子進(jìn)程發(fā)送QUIT信號
      • 老woker進(jìn)程關(guān)閉監(jiān)聽句柄,處理完當(dāng)前連接后結(jié)束進(jìn)程

        如下圖所示,所有的Worker進(jìn)程都是新開啟的進(jìn)程:

        

        1.4.2、Nginx平滑升級

         Nginx可以支撐無縫平滑升級,即通過信號SIGUSR2和SIGWINCH,NGINX的二進(jìn)制升級過程實現(xiàn)了高可用性:您可以動態(tài)升級軟件,而不會出現(xiàn)斷開連接,停機(jī)或服務(wù)中斷的情況。

        二進(jìn)制升級過程與正常配置熱加載的方法類似。一個新的NGINX主進(jìn)程與原始主進(jìn)程并行運行,并且它們共享偵聽套接字。這兩個進(jìn)程都是活動的,并且它們各自的工作進(jìn)程都處理流量。然后,您可以向舊的主機(jī)及其工作人員發(fā)出信號,使其優(yōu)雅地退出,如下圖所示:

        

        如下圖所示示例:

        第一階段新老Master、Worker共存,同時新Master也是老Master的一個子進(jìn)程。

        

        第二階段,新Nginx替代老Nginx提供服務(wù)。如下所示通過SIGWINCH信號停老Worker服務(wù),但是老Master還在,可以直接kill SIGQUIT掉master。

        

         也可以通過直接發(fā)送SIGQUIT信號,同時殺掉所有老Master、Worker:

        

      2、IO多路復(fù)用(NIO)

        2.1、IO模型

        Nginx是基于NIO事件驅(qū)動的,是非阻塞的,還有很多中間件也是基于NIO的IO多路復(fù)用,如redis、tomcat、netty、nginx。

        

        I/O復(fù)用模型會用到select、poll、epoll函數(shù),在這種模型中,這時候并不是進(jìn)程直接發(fā)起資源請求的系統(tǒng)調(diào)用去請求資源,進(jìn)程不會被“全程阻塞”,進(jìn)程是調(diào)用select或poll函數(shù)。進(jìn)程不是被阻塞在真正IO上了,而是阻塞在select或者poll上了。Select或者poll幫助用戶進(jìn)程去輪詢那些IO操作是否完成。

      • select:基于數(shù)組實現(xiàn),最多支持1024路IO。
      • poll:基于鏈表實現(xiàn)IO管理無限制,可以超過1024,沒有IO量的限制。
      • epoll:linux 2.6以上才支持,是基于紅黑樹+鏈表,擁有更高的IO管理性能。
      • kqueue:unix的內(nèi)核支持,如Mac。

        2.2、epoll

        2.3.1、epoll概述

        我們重點看看epoll,epoll提供了三個函數(shù),epoll_create, epoll_ctl和epoll_wait,epoll_create是創(chuàng)建一個epoll句柄(也就是一個epoll instance);epoll_ctl是注冊要監(jiān)聽的事件類型;epoll_wait則是等待事件的產(chǎn)生。

      int epoll_create(int size);//創(chuàng)建一個epoll的句柄,size用來告訴內(nèi)核這個監(jiān)聽的數(shù)目一共有多大
      int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
      int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

        ① 執(zhí)行 epoll_create
          內(nèi)核在epoll文件系統(tǒng)中建了個file結(jié)點,(使用完,必須調(diào)用close()關(guān)閉,否則導(dǎo)致fd被耗盡)
             在內(nèi)核cache里建了紅黑樹存儲epoll_ctl傳來的socket,
             在內(nèi)核cache里建了rdllist雙向鏈表存儲準(zhǔn)備就緒的事件。
       ?、?執(zhí)行 epoll_ctl
          如果增加socket句柄,檢查紅黑樹中是否存在,存在立即返回,不存在則添加到樹干上,然后向內(nèi)核注冊回調(diào)函數(shù),告訴內(nèi)核如果這個句柄的中斷到了,就把它放到準(zhǔn)備就緒list鏈表里。所有添加到epoll中的事件都會與設(shè)備(如網(wǎng)卡)驅(qū)動程序建立回調(diào)關(guān)系,相應(yīng)的事件發(fā)生時,會調(diào)用回調(diào)方法。

       ?、?執(zhí)行 epoll_wait

          立刻返回準(zhǔn)備就緒表里的數(shù)據(jù)即可(將內(nèi)核cache里雙向列表中存儲的準(zhǔn)備就緒的事件  復(fù)制到用戶態(tài)內(nèi)存),當(dāng)調(diào)用epoll_wait檢查是否有事件發(fā)生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可。如果rdlist不為空,則把發(fā)生的事件復(fù)制到用戶態(tài),同時將事件數(shù)量返回給用戶。 

        在io活躍數(shù)比較少的情況下使用epoll更有優(yōu)勢:因為鏈表時間復(fù)雜度o(n)。epoll同select、poll比較:

        

        2.3.2、epoll水平觸發(fā)與邊緣觸發(fā)

      • Level_triggered(水平觸發(fā)):當(dāng)被監(jiān)控的文件描述符上有可讀寫事件發(fā)生時,epoll_wait()會通知處理程序去讀寫。如果這次沒有把數(shù)據(jù)一次性全部讀寫完(如讀寫緩沖區(qū)太小),那么下次調(diào)用 epoll_wait()時,它還會通知你在上沒讀寫完的文件描述符上繼續(xù)讀寫,當(dāng)然如果你一直不去讀寫,它會一直通知你?。。∪绻到y(tǒng)中有大量你不需要讀寫的就緒文件描述符,而它們每次都會返回,這樣會大大降低處理程序檢索自己關(guān)心的就緒文件描述符的效率!!!
      • Edge_triggered(邊緣觸發(fā)):當(dāng)被監(jiān)控的文件描述符上有可讀寫事件發(fā)生時,epoll_wait()會通知處理程序去讀寫。如果這次沒有把數(shù)據(jù)全部讀寫完(如讀寫緩沖區(qū)太小),那么下次調(diào)用epoll_wait()時,它不會通知你,也就是它只會通知你一次,直到該文件描述符上出現(xiàn)第二次可讀寫事件才會通知你?。。∵@種模式比水平觸發(fā)效率高,系統(tǒng)不會充斥大量你不關(guān)心的就緒文件描述符??!

        select(),poll()模型都是水平觸發(fā)模式,信號驅(qū)動IO是邊緣觸發(fā)模式,epoll()模型即支持水平觸發(fā),也支持邊緣觸發(fā),默認(rèn)是水平觸發(fā)。

        具體可以參考博文:7層網(wǎng)絡(luò)以及5種Linux IO模型以及相應(yīng)IO基礎(chǔ) 

      3、Accept鎖及REUSEPORT機(jī)制

        3.1、Accept鎖

        Nginx這種多進(jìn)程的服務(wù)器,在fork后同時監(jiān)聽同一個端口時,如果有一個外部連接進(jìn)來,會導(dǎo)致所有休眠的子進(jìn)程被喚醒,而最終只有一個子進(jìn)程能夠成功處理accept事件,其他進(jìn)程都會重新進(jìn)入休眠中。這就導(dǎo)致出現(xiàn)了很多不必要的schedule和上下文切換,而這些開銷是完全不必要的。

        在Linux內(nèi)核的較新版本中,accept調(diào)用本身所引起的驚群問題已經(jīng)得到了解決,但是在Nginx中,accept是交給epoll機(jī)制來處理的,epoll的accept帶來的驚群問題并沒有得到解決(應(yīng)該是epoll_wait本身并沒有區(qū)別讀事件是否來自于一個Listen套接字的能力,所以所有監(jiān)聽這個事件的進(jìn)程會被這個epoll_wait喚醒。),所以Nginx的accept驚群問題仍然需要定制一個自己的解決方案。

        accept鎖就是nginx的解決方案,本質(zhì)上這是一個跨進(jìn)程的互斥鎖,以這個互斥鎖來保證只有一個進(jìn)程具備監(jiān)聽accept事件的能力。

        3.2、Accept鎖實現(xiàn)(accept_mutex)

        實現(xiàn)上accept鎖是一個跨進(jìn)程鎖,其在Nginx中是一個全局變量,聲明如下: 

      ngx_shmtx_t   ngx_accept_mutex;

        nginx是一個 1(master)+N(worker) 多進(jìn)程模型:master在啟動過程中負(fù)責(zé)讀取nginx.conf中配置的監(jiān)聽端口,然后加入到一個cycle->listening數(shù)組中。init_cycle函數(shù)中會調(diào)用init_module函數(shù),init_module函數(shù)會調(diào)用所有注冊模塊的module_init函數(shù)完成相關(guān)模塊所需資源的申請以及其他一些工作;其中event模塊的module_init函數(shù)申請一塊共享內(nèi)存用于存儲accept_mutex鎖信息以及連接數(shù)信息。

        因此這是一個在event模塊初始化時就分配好的鎖,放在一塊進(jìn)程間共享的內(nèi)存中,以保證所有進(jìn)程都能訪問這一個實例,其加鎖解鎖是借由linux的原子變量來做CAS,如果加鎖失敗則立即返回,是一種非阻塞的鎖。加解鎖代碼如下:

      ngx_uint_t             
      ngx_shmtx_trylock(ngx_shmtx_t *mtx)           
      {                    
        return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));  
      }                    
                          
      #define ngx_shmtx_lock(mtx) ngx_spinlock((mtx)->lock, ngx_pid, 1024)   
                          
      #define ngx_shmtx_unlock(mtx) (void) ngx_atomic_cmp_set((mtx)->lock, ngx_pid, 0)

        可以看出,調(diào)用ngx_shmtx_trylock失敗后會立刻返回而不會阻塞。那么accept鎖如何保證只有一個進(jìn)程能夠處理新連接呢?要解決epoll帶來的accept鎖的問題也很簡單,只需要保證同一時間只有一個進(jìn)程注冊了accept的epoll事件即可。Nginx采用的處理模式也沒什么特別的,大概就是如下的邏輯:

      嘗試獲取accept鎖
      if 獲取成功:
          在epoll中注冊accept事件
      else:
          在epoll中注銷accept事件
      處理所有事件
      釋放accept鎖

        我們知道,所有的worker進(jìn)程均是由master進(jìn)程通過fork() 函數(shù)啟動的,所以所有的worker進(jìn)程也就繼承了master進(jìn)程所有打開的文件描述符(包括之前創(chuàng)建的共享內(nèi)存的fd)以及變量數(shù)據(jù)(這其中就包括之前創(chuàng)建的accept_mutex鎖)。worker啟動的過程中會調(diào)用各個模塊的process_init函數(shù),其中event模塊的process_init函數(shù)中就會將master配置好的listening數(shù)組加入到epoll監(jiān)聽的events中,這樣初始階段所有的worker的epoll監(jiān)聽列表中都包含listening數(shù)組中的fd。

        當(dāng)各個worker實際運行時,對于accept鎖的處理和epoll中注冊注銷accept事件的的處理都是在ngx_trylock_accept_mutex中進(jìn)行的。而這一系列過程則是在nginx主體循環(huán)中反復(fù)調(diào)用的void ngx_process_events_and_timers(ngx_cycle_t *cycle)中進(jìn)行。

        也就是說,每輪事件的處理都會首先競爭accept鎖,競爭成功則在epoll中注冊accept事件,失敗則注銷accept事件,然后處理完事件之后,釋放accept鎖。由此只有一個進(jìn)程監(jiān)聽一個listen套接字,從而避免了驚群問題。

        那么如果某個獲取accept_mutex鎖的worker非常忙,有非常多事件要處理,一直沒輪到釋放鎖,那么某一個進(jìn)程長時間占用accept鎖,而又無暇處理新連接;其他進(jìn)程又沒有占用accept鎖,同樣無法處理新連接,這是怎么處理的呢?為了解決這個問題,Nginx采用了將事件處理延后的方式。即在ngx_process_events的處理中,僅僅將事件放入兩個隊列中:

      ngx_thread_volatile ngx_event_t *ngx_posted_accept_events;        
      ngx_thread_volatile ngx_event_t *ngx_posted_events;

        處理的網(wǎng)絡(luò)事件主要牽扯到2個隊列,一個是ngx_posted_accept_events,另一個是ngx_posted_events。其中,一個隊列用于放accept的事件,另一個則是普通的讀寫事件;
      ngx_event_process_posted會處理事件隊列,其實就是調(diào)用每個事件的回調(diào)函數(shù),然后再讓這個事件出隊。

        那么具體是怎么實現(xiàn)的呢?其實就是在static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)的flags參數(shù)中傳入一個NGX_POST_EVENTS的標(biāo)志位,處理事件時檢查這個標(biāo)志位即可。

        這里只是避免了事件的消費對于accept鎖的長期占用,那么萬一epoll_wait本身占用的時間很長呢?這方面的處理也很簡單,epoll_wait本身是有超時時間的,限制住它的值就可以了,這個參數(shù)保存在ngx_accept_mutex_delay這個全局變量中。

        核心代碼如下:

      ngx_process_events_and_timers(ngx_cycle_t *cycle)
      {
          ngx_uint_t  flags;
          ngx_msec_t  timer, delta;
      
          /* 省略一些處理時間事件的代碼 */                   
          // 這里是處理負(fù)載均衡鎖和accept鎖的時機(jī)
          if (ngx_use_accept_mutex) {
              // 如果負(fù)載均衡token的值大于0, 則說明負(fù)載已滿,此時不再處理accept, 同時把這個值減一
              if (ngx_accept_disabled > 0) {
                  ngx_accept_disabled--;
      
              } else {
                  // 嘗試拿到accept鎖
                  if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                      return;
                  }
      
                  // 拿到鎖之后把flag加上post標(biāo)志,讓所有事件的處理都延后   
                  // 以免太長時間占用accept鎖
                  if (ngx_accept_mutex_held) {
                      flags |= NGX_POST_EVENTS;
      
                  } else {
                      if (timer == NGX_TIMER_INFINITE
                          || timer > ngx_accept_mutex_delay)
                      {
                          // 最多等ngx_accept_mutex_delay個毫秒,防止占用太久accept鎖
                          timer = ngx_accept_mutex_delay;
                      }
                  }
              }
          }
      
          delta = ngx_current_msec;
          // 調(diào)用事件處理模塊的process_events,處理一個epoll_wait的方法
          (void) ngx_process_events(cycle, timer, flags);
      
          // 計算處理events事件所消耗的時間
          delta = ngx_current_msec - delta;
      
          ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                         "timer delta: %M", delta);
      
          // 如果有延后處理的accept事件,那么延后處理這個事件
          ngx_event_process_posted(cycle, &ngx_posted_accept_events);
      
          // 釋放accept鎖
          if (ngx_accept_mutex_held) {
              ngx_shmtx_unlock(&ngx_accept_mutex);
          }
          // 處理所有的超時事件
          if (delta) {
              ngx_event_expire_timers();
          }
      
          // 處理所有的延后事件
          ngx_event_process_posted(cycle, &ngx_posted_events);
      }

        整個流程如下所示:

         

         3.3、Accept鎖開啟是否一定性能高

        上述分析的主要是accept_mutex打開的情況。對于不打開的情況,比較簡單,所有worker的epoll都會監(jiān)聽listening數(shù)組中的所有fd,所以一旦有新連接過來,就會出現(xiàn)worker“搶奪資源“的情況。對于分布式的大量短鏈接來講,打開accept_mutex選項較好,避免了worker爭奪資源造成的上下文切換以及try_lock的鎖開銷。但是對于傳輸大量數(shù)據(jù)的tcp長鏈接來講,打開accept_mutex就會導(dǎo)致壓力集中在某幾個worker上,特別是將worker_connection值設(shè)置過大的時候,影響更加明顯。因此對于accept_mutex開關(guān)的使用,根據(jù)實際情況考慮,不可一概而論。

        一般來說,如果采用的是長tcp連接的方式,而且worker_connection也比較大,這樣就出現(xiàn)了accept_mutex打開worker負(fù)載不均造成QPS下降的問題。

        目前新版的Linux內(nèi)核中增加了EPOLLEXCLUSIVE選項,nginx從1.11.3版本之后也增加了對NGX_EXCLUSIVE_EVENT選項的支持,這樣就可以避免多worker的epoll出現(xiàn)的驚群效應(yīng),從此之后accept_mutex從默認(rèn)的on變成了默認(rèn)off。

        3.4、reuseport機(jī)制

        3.4.1、reuseport機(jī)制描述

        NGINX發(fā)布的1.9.1版本引入了一個新的特性:允許使用SO_REUSEPORT套接字選項,該選項在許多操作系統(tǒng)的新版本中是可用的,包括Bsd和Linux(內(nèi)核版本3.9及以后)。該套接字選項允許多個套接字監(jiān)聽同一IP和端口的組合。內(nèi)核能夠在這些套接字中對傳入的連接進(jìn)行負(fù)載均衡。對于NGINX而言,啟用該選項可以減少在某些場景下的鎖競爭而改善性能。

        如下圖描述,當(dāng)SO_REUSEPORT未開啟時,一個單獨的監(jiān)聽socket通知工作進(jìn)程接入的連接,并且每個工作線程都試圖獲得連接。

        

        當(dāng)SO_REUSEPORT選項啟用時,存在對每一個IP地址和端口綁定連接的多個socket監(jiān)聽器,每一個工作進(jìn)程都可以分配一個。系統(tǒng)內(nèi)核決定哪一個有效的socket監(jiān)聽器(通過隱式的方式,給哪一個工作進(jìn)程)獲得連接。這可以減少工作進(jìn)程之間獲得新連接時的封鎖競爭(譯者注:工作進(jìn)程請求獲得互斥資源加鎖之間的競爭),同時在多核系統(tǒng)可以提高性能。然而,這也意味著當(dāng)一個工作進(jìn)程陷入阻塞操作時,阻塞影響的不僅是已經(jīng)接受連接的工作進(jìn)程,也同時讓內(nèi)核發(fā)送連接請求計劃分配的工作進(jìn)程因此變?yōu)樽枞?nbsp;

        

         3.4.2、開啟reuseport

        要開啟SO_REUSEPORT,需要為HTTP或TCP(流模式)通信選項內(nèi)的listen項直接添加reuseport參數(shù),就像下例這樣:

      http {
           server {
                listen 80 reuseport;
                server_name  localhost;
                # ...
           }
      }
      
      stream {
           server {
                listen 12345 reuseport;
                # ...
           }
      }

        引用reuseport參數(shù)后,accept_mutex參數(shù)將會無效,因為互斥鎖對reuseport來說是多余的。如果沒有開啟reuseport,設(shè)置accept_mutex仍然是有效的。accept_mutex默認(rèn)是開啟的。

        3.4.3、reuseport的性能測試

        我在一個36核的AWS實例運行wrk基準(zhǔn)測試工具,測試4個NGINX工作進(jìn)程。為了減少網(wǎng)絡(luò)的影響,客戶端和NGINX都運行在本地,并且讓NGINX返回OK字符串而不是一個文件。我比較三種NGINX配置:默認(rèn)(等同于accept_mutex on),accept_mutex off和reuseport。如圖所示,reuseport的每秒請求是其余的兩到三倍,同時延遲和延遲標(biāo)準(zhǔn)差也是減少的。

        

        也運行了另一個相關(guān)的性能測試——客戶端和NGINX分別在不同的機(jī)器上且NGINX返回一個HTML文件。如下表所示,用reuseport減少的延遲和之前的性能測試相似,延遲的標(biāo)準(zhǔn)差減少的更為顯著(接近十分之一)。其他結(jié)果(沒有顯示在表格中)同樣令人振奮。使用reuseport ,負(fù)載被均勻分離到了worker進(jìn)程。在默認(rèn)條件下(等同于 accept_mutex on),一些worker分到了較高百分比的負(fù)載,而用accept_mutex off所有worker都受到了較高的負(fù)載。

        

        在這些性能測試中,連接請求的速度是很高的,但是請求不需要大量的處理。其他的基本的測試應(yīng)該指出——當(dāng)應(yīng)用流量符合這種場景時 reuseport 也能大幅提高性能。(reuseport 參數(shù)在 mail 上下文環(huán)境下不能用在 listen 指令下,例如email,因為email流量一定不會匹配這種場景。)我們鼓勵你先測試而不是直接大規(guī)模應(yīng)用。關(guān)于測試NGNIX性能的一些技巧,看看Konstantin Pavlov在nginx2014大會上的演講。

        原文地址:Socket Sharding in NGINX Release 1.9.1

        Nginx reuseport的原理參考另一篇博文:nginx源碼分析—reuseport的使用

      4、Sendfile機(jī)制

        在Nginx作為WEB服務(wù)器使用的時候,會訪問大量的本地磁盤文件,在以往,訪問磁盤文件會經(jīng)歷多次內(nèi)核態(tài)、用戶態(tài)切換,造成大量的資源浪費(如下圖右邊部分),而Nginx支持sendfile(也就是零拷貝),實現(xiàn)文件fd到網(wǎng)卡fd的直接映射(如下圖左側(cè)部分),跳過了大量的用戶態(tài)、內(nèi)核態(tài)切換。如下圖所示:

        

        注:用戶態(tài)、內(nèi)核態(tài)切換一次大約耗費是5ms,而一次CPU時間片大約才10ms-100ms,因此在大量并發(fā)的情況下不斷進(jìn)行內(nèi)核切換相當(dāng)浪費CPU資源,建議配置打開sendfile。

        配置文件如截圖所示:

        

      附錄、模塊化體系結(jié)構(gòu)

        如下圖所示,就是Nginx的模塊體系化結(jié)構(gòu):

         

        nginx的模塊根據(jù)其功能基本上可以分為以下幾種類型:

      • event module: 搭建了獨立于操作系統(tǒng)的事件處理機(jī)制的框架,及提供了各具體事件的處理。包括ngx_events_module, ngx_event_core_module和ngx_epoll_module等。nginx具體使用何種事件處理模塊,這依賴于具體的操作系統(tǒng)和編譯選項。
      • phase handler: 此類型的模塊也被直接稱為handler模塊。主要負(fù)責(zé)處理客戶端請求并產(chǎn)生待響應(yīng)內(nèi)容,比如ngx_http_static_module模塊,負(fù)責(zé)客戶端的靜態(tài)頁面請求處理并將對應(yīng)的磁盤文件準(zhǔn)備為響應(yīng)內(nèi)容輸出。
      • output filter: 也稱為filter模塊,主要是負(fù)責(zé)對輸出的內(nèi)容進(jìn)行處理,可以對輸出進(jìn)行修改。例如,可以實現(xiàn)對輸出的所有html頁面增加預(yù)定義的footbar一類的工作,或者對輸出的圖片的URL進(jìn)行替換之類的工作。
      • upstream: upstream模塊實現(xiàn)反向代理的功能,將真正的請求轉(zhuǎn)發(fā)到后端服務(wù)器上,并從后端服務(wù)器上讀取響應(yīng),發(fā)回客戶端。upstream模塊是一種特殊的handler,只不過響應(yīng)內(nèi)容不是真正由自己產(chǎn)生的,而是從后端服務(wù)器上讀取的。
      • load-balancer: 負(fù)載均衡模塊,實現(xiàn)特定的算法,在眾多的后端服務(wù)器中,選擇一個服務(wù)器出來作為某個請求的轉(zhuǎn)發(fā)服務(wù)器。
      posted on 2021-04-23 22:29  kosamino  閱讀(2458)  評論(0)    收藏  舉報

      主站蜘蛛池模板: 亚洲av不卡电影在线网址最新| 精品一区二区三区四区激情| 久久先锋男人AV资源网站| 永久免费无码av网站在线观看| 99热久久这里只有精品| 色婷婷欧美在线播放内射| 亚洲欧美日韩综合在线丁香| 亚洲乱码日产精品bd在线看| 亚洲www永久成人网站| 亚洲第四色在线中文字幕| 欧美成人精品三级网站| 亚洲岛国成人免费av| 国产精品va无码一区二区| 熟女系列丰满熟妇AV| 国产亚洲精品久久久久蜜臀| 欧洲亚洲国内老熟女超碰| 欧美人与禽2o2o性论交| 男女xx00xx的视频免费观看| 乱人伦人妻中文字幕不卡| 神马久久亚洲一区 二区| 少妇爽到爆视频网站免费| 武平县| 亚洲人成网站在线在线观看| 最新偷拍一区二区三区| 亚洲精品日韩中文字幕| 一日本道伊人久久综合影| 中文字幕亚洲综合小综合| 产综合无码一区| 人妻少妇精品系列一区二区| 91亚洲国产成人精品性色| ww污污污网站在线看com| 爆乳女仆高潮在线观看| 国产区精品福利在线熟女| 国产精品久久久久乳精品爆| 国产免费一区二区不卡| 少妇熟女视频一区二区三区| 国产午夜影视大全免费观看| 欧美成人影院亚洲综合图| 日韩精品一区二区三区久| 亚洲中文字幕日韩精品| 午夜福利啪啪片|