<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                在說nginx前,先來看看什么是“驚群”?簡單說來,多線程/多進程(linux下線程進程也沒多大區別)等待同一個socket事件,當這個事件發生時,這些線程/進程被同時喚醒,就是驚群。可以想見,效率很低下,許多進程被內核重新調度喚醒,同時去響應這一個事件,當然只有一個進程能處理事件成功,其他的進程在處理該事件失敗后重新休眠(也有其他選擇)。這種性能浪費現象就是驚群。 驚群通常發生在server 上,當父進程綁定一個端口監聽socket,然后fork出多個子進程,子進程們開始循環處理(比如accept)這個socket。每當用戶發起一個TCP連接時,多個子進程同時被喚醒,然后其中一個子進程accept新連接成功,余者皆失敗,重新休眠。 那么,我們不能只用一個進程去accept新連接么?然后通過消息隊列等同步方式使其他子進程處理這些新建的連接,這樣驚群不就避免了?沒錯,驚群是避免了,但是效率低下,因為這個進程只能用來accept連接。對多核機器來說,僅有一個進程去accept,這也是程序員在自己創造accept瓶頸。所以,我仍然堅持需要多進程處理accept事件。 其實,在linux2.6內核上,accept系統調用已經不存在驚群了(至少我在2.6.18內核版本上已經不存在)。大家可以寫個簡單的程序試下,在父進程中bind,listen,然后fork出子進程,所有的子進程都accept這個監聽句柄。這樣,當新連接過來時,大家會發現,僅有一個子進程返回新建的連接,其他子進程繼續休眠在accept調用上,沒有被喚醒。 但是很不幸,通常我們的程序沒那么簡單,不會愿意阻塞在accept調用上,我們還有許多其他網絡讀寫事件要處理,linux下我們愛用epoll解決非阻塞socket。所以,即使accept調用沒有驚群了,我們也還得處理驚群這事,因為epoll有這問題。上面說的測試程序,如果我們在子進程內不是阻塞調用accept,而是用epoll_wait,就會發現,新連接過來時,多個子進程都會在epoll_wait后被喚醒! nginx就是這樣,master進程監聽端口號(例如80),所有的nginx worker進程開始用epoll_wait來處理新事件(linux下),如果不加任何保護,一個新連接來臨時,會有多個worker進程在epoll_wait后被喚醒,然后發現自己accept失敗。現在,我們可以看看nginx是怎么處理這個驚群問題了。 nginx的每個worker進程在函數ngx_process_events_and_timers中處理事件,(void) ngx_process_events(cycle, timer, flags);封裝了不同的事件處理機制,在linux上默認就封裝了epoll_wait調用。我們來看看ngx_process_events_and_timers為解決驚群做了什么: ~~~ void ngx_process_events_and_timers(ngx_cycle_t *cycle) { 。。。 。。。 //ngx_use_accept_mutex表示是否需要通過對accept加鎖來解決驚群問題。當nginx worker進程數>1時且配置文件中打開accept_mutex時,這個標志置為1 if (ngx_use_accept_mutex) { //ngx_accept_disabled表示此時滿負荷,沒必要再處理新連接了,我們在nginx.conf曾經配置了每一個nginx worker進程能夠處理的最大連接數,當達到最大數的7/8時,ngx_accept_disabled為正,說明本nginx worker進程非常繁忙,將不再去處理新連接,這也是個簡單的負載均衡 if (ngx_accept_disabled > 0) { ngx_accept_disabled--; } else { //獲得accept鎖,多個worker僅有一個可以得到這把鎖。獲得鎖不是阻塞過程,都是立刻返回,獲取成功的話ngx_accept_mutex_held被置為1。拿到鎖,意味著監聽句柄被放到本進程的epoll中了,如果沒有拿到鎖,則監聽句柄會被從epoll中取出。 if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) { return; } //拿到鎖的話,置flag為NGX_POST_EVENTS,這意味著ngx_process_events函數中,任何事件都將延后處理,會把accept事件都放到ngx_posted_accept_events鏈表中,epollin|epollout事件都放到ngx_posted_events鏈表中 if (ngx_accept_mutex_held) { flags |= NGX_POST_EVENTS; } else { //拿不到鎖,也就不會處理監聽的句柄,這個timer實際是傳給epoll_wait的超時時間,修改為最大ngx_accept_mutex_delay意味著epoll_wait更短的超時返回,以免新連接長時間沒有得到處理 if (timer == NGX_TIMER_INFINITE || timer > ngx_accept_mutex_delay) { timer = ngx_accept_mutex_delay; } } } } 。。。 。。。 //linux下,調用ngx_epoll_process_events函數開始處理 (void) ngx_process_events(cycle, timer, flags); 。。。 。。。 //如果ngx_posted_accept_events鏈表有數據,就開始accept建立新連接 if (ngx_posted_accept_events) { ngx_event_process_posted(cycle, &ngx_posted_accept_events); } //釋放鎖后再處理下面的EPOLLIN EPOLLOUT請求 if (ngx_accept_mutex_held) { ngx_shmtx_unlock(&ngx_accept_mutex); } if (delta) { ngx_event_expire_timers(); } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "posted events %p", ngx_posted_events); //然后再處理正常的數據讀寫請求。因為這些請求耗時久,所以在ngx_process_events里NGX_POST_EVENTS標志將事件都放入ngx_posted_events鏈表中,延遲到鎖釋放了再處理。 if (ngx_posted_events) { if (ngx_threaded) { ngx_wakeup_worker_thread(cycle); } else { ngx_event_process_posted(cycle, &ngx_posted_events); } } } ~~~ 從上面的注釋可以看到,無論有多少個nginx worker進程,同一時刻只能有一個worker進程在自己的epoll中加入監聽的句柄。這個處理accept的nginx worker進程置flag為NGX_POST_EVENTS,這樣它在接下來的ngx_process_events函數(在linux中就是ngx_epoll_process_events函數)中不會立刻處理事件,延后,先處理完所有的accept事件后,釋放鎖,然后再處理正常的讀寫socket事件。我們來看下ngx_epoll_process_events是怎么做的: ~~~ static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags) { 。。。 。。。 events = epoll_wait(ep, event_list, (int) nevents, timer); 。。。 。。。 ngx_mutex_lock(ngx_posted_events_mutex); for (i = 0; i < events; i++) { c = event_list[i].data.ptr; 。。。 。。。 rev = c->read; if ((revents & EPOLLIN) && rev->active) { 。。。 。。。 //有NGX_POST_EVENTS標志的話,就把accept事件放到ngx_posted_accept_events隊列中,把正常的事件放到ngx_posted_events隊列中延遲處理 if (flags & NGX_POST_EVENTS) { queue = (ngx_event_t **) (rev->accept ? &ngx_posted_accept_events : &ngx_posted_events); ngx_locked_post_event(rev, queue); } else { rev->handler(rev); } } wev = c->write; if ((revents & EPOLLOUT) && wev->active) { 。。。 。。。 //同理,有NGX_POST_EVENTS標志的話,寫事件延遲處理,放到ngx_posted_events隊列中 if (flags & NGX_POST_EVENTS) { ngx_locked_post_event(wev, &ngx_posted_events); } else { wev->handler(wev); } } } ngx_mutex_unlock(ngx_posted_events_mutex); return NGX_OK; } ~~~ 看看ngx_use_accept_mutex在何種情況下會被打開: ~~~ if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) { ngx_use_accept_mutex = 1; ngx_accept_mutex_held = 0; ngx_accept_mutex_delay = ecf->accept_mutex_delay; } else { ngx_use_accept_mutex = 0; } ~~~ 當nginx worker數量大于1時,也就是多個進程可能accept同一個監聽的句柄,這時如果配置文件中accept_mutex開關打開了,就將ngx_use_accept_mutex置為1。 再看看有些負載均衡作用的ngx_accept_disabled是怎么維護的,在ngx_event_accept函數中: ~~~ ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n; ~~~ 表明,當已使用的連接數占到在nginx.conf里配置的worker_connections總數的7/8以上時,ngx_accept_disabled為正,這時本worker將ngx_accept_disabled減1,而且本次不再處理新連接。 最后,我們看下ngx_trylock_accept_mutex函數是怎么玩的: ~~~ ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle) { //ngx_shmtx_trylock是非阻塞取鎖的,返回1表示成功,0表示沒取到鎖 if (ngx_shmtx_trylock(&ngx_accept_mutex)) { //ngx_enable_accept_events會把監聽的句柄都塞入到本worker進程的epoll中 if (ngx_enable_accept_events(cycle) == NGX_ERROR) { ngx_shmtx_unlock(&ngx_accept_mutex); return NGX_ERROR; } //ngx_accept_mutex_held置為1,表示拿到鎖了,返回 ngx_accept_events = 0; ngx_accept_mutex_held = 1; return NGX_OK; } //處理沒有拿到鎖的邏輯,ngx_disable_accept_events會把監聽句柄從epoll中取出 if (ngx_accept_mutex_held) { if (ngx_disable_accept_events(cycle) == NGX_ERROR) { return NGX_ERROR; } ngx_accept_mutex_held = 0; } return NGX_OK; } ~~~ OK,關于鎖的細節是如何實現的,這篇限于篇幅就不說了,下篇帖子再來講。現在大家清楚nginx是怎么處理驚群了吧?簡單了說,就是同一時刻只允許一個nginx worker在自己的epoll中處理監聽句柄。它的負載均衡也很簡單,當達到最大connection的7/8時,本worker不會去試圖拿accept鎖,也不會去處理新連接,這樣其他nginx worker進程就更有機會去處理監聽句柄,建立新連接了。而且,由于timeout的設定,使得沒有拿到鎖的worker進程,去拿鎖的頻繁更高。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看