欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Nginx學(xué)習(xí)筆記之事件驅(qū)動框架處理流程

 更新時間:2014年07月15日 09:11:59   投稿:hebedich  
Nginx對請求的處理是通過事件觸發(fā)的,模塊作為事件消費者,只能被事件收集、分發(fā)器調(diào)用。在Nginx中,接收到一個請求時,不會產(chǎn)生一個單獨的進程來處理該請求,而是由事件收集、分發(fā)器(進程)調(diào)用某個模塊,由模塊處理請求,處理完后再返回到事件收集、分發(fā)器

ngx_event_core_module模塊的ngx_event_process_init方法對事件模塊做了一些初始化。其中包括將“請求連接”這樣一個讀事件對應(yīng)的處理方法(handler)設(shè)置為ngx_event_accept函數(shù),并將此事件添加到epoll模塊中。當(dāng)有新連接事件發(fā)生時,ngx_event_accept就會被調(diào)用。大致流程是這樣:

worker進程在ngx_worker_process_cycle方法中不斷循環(huán)調(diào)用ngx_process_events_and_timers函數(shù)處理事件,這個函數(shù)是事件處理的總?cè)肟凇?/p>

ngx_process_events_and_timers會調(diào)用ngx_process_events,這是一個宏,相當(dāng)于ngx_event_actions.process_events,ngx_event_actions是個全局的結(jié)構(gòu)體,存儲了對應(yīng)事件驅(qū)動模塊(這里是epoll模塊)的10個函數(shù)接口。所以這里就是調(diào)用了ngx_epoll_module_ctx.actions.process_events函數(shù),也就是ngx_epoll_process_events函數(shù)來處理事件。

ngx_epoll_process_events調(diào)用Linux函數(shù)接口epoll_wait獲得“有新連接”這個事件,然后調(diào)用這個事件的handler處理函數(shù)來對這個事件進行處理。

在上面已經(jīng)說過handler已經(jīng)被設(shè)置成了ngx_event_accept函數(shù),所以就調(diào)用ngx_event_accept進行實際的處理。

下面分析ngx_event_accept方法,它的流程圖如下所示:

經(jīng)過精簡的代碼如下,注釋中的序號對應(yīng)上圖的序號:

void
ngx_event_accept(ngx_event_t *ev)
{
 socklen_t  socklen;
 ngx_err_t  err;
 ngx_log_t  *log;
 ngx_uint_t  level;
 ngx_socket_t  s;
 ngx_event_t  *rev, *wev;
 ngx_listening_t  *ls;
 ngx_connection_t *c, *lc;
 ngx_event_conf_t *ecf;
 u_char  sa[NGX_SOCKADDRLEN];
 
 if (ev->timedout) {
  if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) {
   return;
  }
 
  ev->timedout = 0;
 }
 
 ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);
 
 if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
  ev->available = 1;
 
 } else if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) {
  ev->available = ecf->multi_accept;
 }
 
 lc = ev->data;
 ls = lc->listening;
 ev->ready = 0;
 
 do {
  socklen = NGX_SOCKADDRLEN;
 
  /* 1、accept方法試圖建立連接,非阻塞調(diào)用 */
  s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
 
  if (s == (ngx_socket_t) -1)
  {
   err = ngx_socket_errno;
 
   if (err == NGX_EAGAIN)
   {
    /* 沒有連接,直接返回 */
    return;
   }
 
   level = NGX_LOG_ALERT;
 
   if (err == NGX_ECONNABORTED) {
    level = NGX_LOG_ERR;
 
   } else if (err == NGX_EMFILE || err == NGX_ENFILE) {
    level = NGX_LOG_CRIT;
   }
 
   if (err == NGX_ECONNABORTED) {
    if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
     ev->available--;
    }
 
    if (ev->available) {
     continue;
    }
   }
 
   if (err == NGX_EMFILE || err == NGX_ENFILE) {
    if (ngx_disable_accept_events((ngx_cycle_t *) ngx_cycle)
     != NGX_OK)
    {
     return;
    }
 
    if (ngx_use_accept_mutex) {
     if (ngx_accept_mutex_held) {
      ngx_shmtx_unlock(&ngx_accept_mutex);
      ngx_accept_mutex_held = 0;
     }
 
     ngx_accept_disabled = 1;
 
    } else {
     ngx_add_timer(ev, ecf->accept_mutex_delay);
    }
   }
 
   return;
  }
 
  /* 2、設(shè)置負(fù)載均衡閾值 */
  ngx_accept_disabled = ngx_cycle->connection_n / 8
        - ngx_cycle->free_connection_n;
 
  /* 3、從連接池獲得一個連接對象 */
  c = ngx_get_connection(s, ev->log);
 
  /* 4、為連接創(chuàng)建內(nèi)存池 */
  c->pool = ngx_create_pool(ls->pool_size, ev->log);
 
  c->sockaddr = ngx_palloc(c->pool, socklen);
 
  ngx_memcpy(c->sockaddr, sa, socklen);
 
  log = ngx_palloc(c->pool, sizeof(ngx_log_t));
 
  /* set a blocking mode for aio and non-blocking mode for others */
  /* 5、設(shè)置套接字屬性為阻塞或非阻塞 */
  if (ngx_inherited_nonblocking) {
   if (ngx_event_flags & NGX_USE_AIO_EVENT) {
    if (ngx_blocking(s) == -1) {
     ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
         ngx_blocking_n " failed");
     ngx_close_accepted_connection(c);
     return;
    }
   }
 
  } else {
   if (!(ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT))) {
    if (ngx_nonblocking(s) == -1) {
     ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
         ngx_nonblocking_n " failed");
     ngx_close_accepted_connection(c);
     return;
    }
   }
  }
 
  *log = ls->log;
 
  c->recv = ngx_recv;
  c->send = ngx_send;
  c->recv_chain = ngx_recv_chain;
  c->send_chain = ngx_send_chain;
 
  c->log = log;
  c->pool->log = log;
 
  c->socklen = socklen;
  c->listening = ls;
  c->local_sockaddr = ls->sockaddr;
  c->local_socklen = ls->socklen;
 
  c->unexpected_eof = 1;
 
  rev = c->read;
  wev = c->write;
 
  wev->ready = 1;
 
  if (ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT)) {
   /* rtsig, aio, iocp */
   rev->ready = 1;
  }
 
  if (ev->deferred_accept) {
   rev->ready = 1;
 
  }
 
  rev->log = log;
  wev->log = log;
 
  /*
   * TODO: MT: - ngx_atomic_fetch_add()
   *  or protection by critical section or light mutex
   *
   * TODO: MP: - allocated in a shared memory
   *   - ngx_atomic_fetch_add()
   *  or protection by critical section or light mutex
   */
 
  c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
 
  if (ls->addr_ntop) {
   c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len);
   if (c->addr_text.data == NULL) {
    ngx_close_accepted_connection(c);
    return;
   }
 
   c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen,
            c->addr_text.data,
            ls->addr_text_max_len, 0);
   if (c->addr_text.len == 0) {
    ngx_close_accepted_connection(c);
    return;
   }
  }
 
  /* 6、將新連接對應(yīng)的讀寫事件添加到epoll對象中 */
  if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
   if (ngx_add_conn(c) == NGX_ERROR) {
    ngx_close_accepted_connection(c);
    return;
   }
  }
 
  log->data = NULL;
  log->handler = NULL;
 
  /* 7、TCP建立成功調(diào)用的方法,這個方法在ngx_listening_t結(jié)構(gòu)體中 */
  ls->handler(c);
 
 } while (ev->available); /* available標(biāo)志表示一次盡可能多的建立連接,由配置項multi_accept決定 */
}

Nginx中的“驚群”問題

Nginx一般會運行多個worker進程,這些進程會同時監(jiān)聽同一端口。當(dāng)有新連接到來時,內(nèi)核將這些進程全部喚醒,但只有一個進程能夠和客戶端連接成功,導(dǎo)致其它進程在喚醒時浪費了大量開銷,這被稱為“驚群”現(xiàn)象。Nginx解決“驚群”的方法是,讓進程獲得互斥鎖ngx_accept_mutex,讓進程互斥地進入某一段臨界區(qū)。在該臨界區(qū)中,進程將它所要監(jiān)聽的連接對應(yīng)的讀事件添加到epoll模塊中,使得當(dāng)有“新連接”事件發(fā)生時,該worker進程會作出反應(yīng)。這段加鎖并添加事件的過程是在函數(shù)ngx_trylock_accept_mutex中完成的。而當(dāng)其它進程也進入該函數(shù)想要添加讀事件時,發(fā)現(xiàn)互斥鎖被另外一個進程持有,所以它只能返回,它所監(jiān)聽的事件也無法添加到epoll模塊,從而無法響應(yīng)“新連接”事件。但這會出現(xiàn)一個問題:持有互斥鎖的那個進程在什么時候釋放互斥鎖呢?如果需要等待它處理完所有的事件才釋放鎖的話,那么會需要相當(dāng)長的時間。而在這段時間內(nèi),其它worker進程無法建立新連接,這顯然是不可取的。Nginx的解決辦法是:通過ngx_trylock_accept_mutex獲得了互斥鎖的進程,在獲得就緒讀/寫事件并從epoll_wait返回后,將這些事件歸類放入隊列中:

新連接事件放入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);
 
 /* 將事件添加到延后執(zhí)行隊列中 */
 ngx_locked_post_event(rev, queue);
}
else
{
 rev->handler(rev); /* 不需要延后,則立即處理事件 */
}

寫事件做類似處理。進程接下來處理ngx_posted_accept_events隊列中的事件,處理完后立即釋放互斥鎖,使該進程占用鎖的時間降到了最低。

Nginx中的負(fù)載均衡問題

Nginx中每個進程使用了一個處理負(fù)載均衡的閾值ngx_accept_disabled,它在上圖的第2步中被初始化:

ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;

它的初值為一個負(fù)數(shù),該負(fù)數(shù)的絕對值等于總連接數(shù)的7/8.當(dāng)閾值小于0時正常響應(yīng)新連接事件,當(dāng)閾值大于0時不再響應(yīng)新連接事件,并將ngx_accept_disabled減1,代碼如下:

if (ngx_accept_disabled > 0)
{
  ngx_accept_disabled--;
}
else
{
 if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR)
 {
  return;
 }
 ....
}

這說明,當(dāng)某個進程當(dāng)前的連接數(shù)達(dá)到能夠處理的總連接數(shù)的7/8時,負(fù)載均衡機制被觸發(fā),進程停止響應(yīng)新連接。

參考:

《深入理解Nginx》 P328-P334.

相關(guān)文章

  • 詳解Nginx幾種常見實現(xiàn)301重定向方法上的區(qū)別

    詳解Nginx幾種常見實現(xiàn)301重定向方法上的區(qū)別

    本篇文章主要介紹了詳解Nginx幾種常見實現(xiàn)301重定向方法上的區(qū)別,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • nginx 開啟 pathinfo的過程詳解

    nginx 開啟 pathinfo的過程詳解

    這篇文章主要介紹了nginx 開啟 pathinfo的過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-08-08
  • nginx中path模式配置示例

    nginx中path模式配置示例

    這篇文章主要介紹了nginx中path模式配置示例,nginx服務(wù)器默認(rèn)是不支持pathinfo模式的,需要修改配置才可以實現(xiàn),本文即給出了配置示例,需要的朋友可以參考下
    2014-12-12
  • Nginx中日志模塊的應(yīng)用和配置應(yīng)用示例

    Nginx中日志模塊的應(yīng)用和配置應(yīng)用示例

    Nginx是一款高性能的HTTP和反向代理服務(wù)器,廣泛應(yīng)用于互聯(lián)網(wǎng)領(lǐng)域,這篇文章主要介紹了Nginx中日志模塊的應(yīng)用和配置,下面通過一個簡單的實例來演示Nginx日志模塊的應(yīng)用和配置,需要的朋友可以參考下
    2024-02-02
  • 解讀Nginx和Apache的特點與區(qū)別

    解讀Nginx和Apache的特點與區(qū)別

    這篇文章主要介紹了解讀Nginx和Apache的特點與區(qū)別,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • nginx實現(xiàn)一個域名配置多個laravel項目的方法示例

    nginx實現(xiàn)一個域名配置多個laravel項目的方法示例

    這篇文章主要介紹了nginx實現(xiàn)一個域名配置多個laravel項目的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-01-01
  • 通過nginx實現(xiàn)方向代理過程圖解

    通過nginx實現(xiàn)方向代理過程圖解

    這篇文章主要介紹了通過nginx實現(xiàn)方向代理過程圖解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-02-02
  • 安裝OpenResty(Nginx倉庫)

    安裝OpenResty(Nginx倉庫)

    這篇文章主要介紹了安裝OpenResty(Nginx倉庫),需要的朋友可以參考下
    2023-06-06
  • Laravel的Nginx重寫規(guī)則實例代碼

    Laravel的Nginx重寫規(guī)則實例代碼

    這篇文章主要介紹了Laravel的Nginx重寫規(guī)則實例代碼,需要的朋友可以參考下
    2017-09-09
  • nginx打印請求頭日志方法(親測可用)

    nginx打印請求頭日志方法(親測可用)

    之前想用nginx打印收到的請求的請求頭,但是只找到打印請求體的,沒有打印請求頭的,本文就來介紹一下nginx打印請求頭日志方法,感興趣的可以了解一下
    2023-11-11

最新評論