亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 網站 > Nginx > 正文

Nginx源碼研究之nginx限流模塊詳解

2024-08-30 12:29:54
字體:
來源:轉載
供稿:網友

高并發系統有三把利器:緩存、降級和限流;

限流的目的是通過對并發訪問/請求進行限速來保護系統,一旦達到限制速率則可以拒絕服務(定向到錯誤頁)、排隊等待(秒殺)、降級(返回兜底數據或默認數據);

高并發系統常見的限流有:限制總并發數(數據庫連接池)、限制瞬時并發數(如nginx的limit_conn模塊,用來限制瞬時并發連接數)、限制時間窗口內的平均速率(nginx的limit_req模塊,用來限制每秒的平均速率);

另外還可以根據網絡連接數、網絡流量、CPU或內存負載等來限流。

1.限流算法

最簡單粗暴的限流算法就是計數器法了,而比較常用的有漏桶算法和令牌桶算法;

1.1計數器

計數器法是限流算法里最簡單也是最容易實現的一種算法。比如我們規定,對于A接口來說,我們1分鐘的訪問次數不能超過100個。

那么我們我們可以設置一個計數器counter,其有效時間為1分鐘(即每分鐘計數器會被重置為0),每當一個請求過來的時候,counter就加1,如果counter的值大于100,就說明請求數過多;

這個算法雖然簡單,但是有一個十分致命的問題,那就是臨界問題。

如下圖所示,在1:00前一刻到達100個請求,1:00計數器被重置,1:00后一刻又到達100個請求,顯然計數器不會超過100,所有請求都不會被攔截;

然而這一時間段內請求數已經達到200,遠超100。

Nginx源碼,nginx,限流模塊

1.2 漏桶算法

如下圖所示,有一個固定容量的漏桶,按照常量固定速率流出水滴;如果桶是空的,則不會流出水滴;流入到漏桶的水流速度是隨意的;如果流入的水超出了桶的容量,則流入的水會溢出(被丟棄);

可以看到漏桶算法天生就限制了請求的速度,可以用于流量整形和限流控制;

Nginx源碼,nginx,限流模塊

1.3 令牌桶算法

令牌桶是一個存放固定容量令牌的桶,按照固定速率r往桶里添加令牌;桶中最多存放b個令牌,當桶滿時,新添加的令牌被丟棄;

當一個請求達到時,會嘗試從桶中獲取令牌;如果有,則繼續處理請求;如果沒有則排隊等待或者直接丟棄;

可以發現,漏桶算法的流出速率恒定或者為0,而令牌桶算法的流出速率卻有可能大于r;

Nginx源碼,nginx,限流模塊

2.nginx基礎知識

Nginx主要有兩種限流方式:按連接數限流(ngx_http_limit_conn_module)、按請求速率限流(ngx_http_limit_req_module);

學習限流模塊之前還需要了解nginx對HTTP請求的處理過程,nginx事件處理流程等;

2.1HTTP請求處理過程

nginx將HTTP請求處理流程分為11個階段,絕大多數HTTP模塊都會將自己的handler添加到某個階段(其中有4個階段不能添加自定義handler),nginx處理HTTP請求時會挨個調用所有的handler;

typedef enum { NGX_HTTP_POST_READ_PHASE = 0, //目前只有realip模塊會注冊handler(nginx作為代理服務器時有用,后端以此獲取客戶端原始ip)  NGX_HTTP_SERVER_REWRITE_PHASE, //server塊中配置了rewrite指令,重寫url  NGX_HTTP_FIND_CONFIG_PHASE, //查找匹配location;不能自定義handler; NGX_HTTP_REWRITE_PHASE,  //location塊中配置了rewrite指令,重寫url NGX_HTTP_POST_REWRITE_PHASE, //檢查是否發生了url重寫,如果有,重新回到FIND_CONFIG階段;不能自定義handler;  NGX_HTTP_PREACCESS_PHASE,  //訪問控制,限流模塊會注冊handler到此階段  NGX_HTTP_ACCESS_PHASE,  //訪問權限控制 NGX_HTTP_POST_ACCESS_PHASE, //根據訪問權限控制階段做相應處理;不能自定義handler;  NGX_HTTP_TRY_FILES_PHASE,  //只有配置了try_files指令,才會有此階段;不能自定義handler; NGX_HTTP_CONTENT_PHASE,  //內容產生階段,返回響應給客戶端  NGX_HTTP_LOG_PHASE   //日志記錄} ngx_http_phases;

nginx使用結構體ngx_module_s表示一個模塊,其中字段ctx,是一個指向模塊上下文結構體的指針;nginx的HTTP模塊上下文結構體如下所示(上下文結構體的字段都是一些函數指針):

typedef struct { ngx_int_t (*preconfiguration)(ngx_conf_t *cf); ngx_int_t (*postconfiguration)(ngx_conf_t *cf); //此方法注冊handler到相應階段  void  *(*create_main_conf)(ngx_conf_t *cf); //http塊中的主配置 char  *(*init_main_conf)(ngx_conf_t *cf, void *conf);  void  *(*create_srv_conf)(ngx_conf_t *cf); //server配置 char  *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);  void  *(*create_loc_conf)(ngx_conf_t *cf); //location配置 char  *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);} ngx_http_module_t;

以ngx_http_limit_req_module模塊為例,postconfiguration方法簡單實現如下:

static ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf){ h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);  *h = ngx_http_limit_req_handler; //ngx_http_limit_req_module模塊的限流方法;nginx處理HTTP請求時,都會調用此方法判斷應該繼續執行還是拒絕請求  return NGX_OK;}

2.2 nginx事件處理簡單介紹

假設nginx使用的是epoll。

nginx需要將所有關心的fd注冊到epoll,添加方法生命如下:

static ngx_int_t ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

方法第一個參數是ngx_event_t結構體指針,代表關心的一個讀或者寫事件;nginx為事件可能會設置一個超時定時器,從而能夠處理事件超時情況;定義如下:

struct ngx_event_s {  ngx_event_handler_pt handler; //函數指針:事件的處理函數  ngx_rbtree_node_t timer;  //超時定時器,存儲在紅黑樹中(節點的key即為事件的超時時間)  unsigned   timedout:1; //記錄事件是否超時 };

一般都會循環調用epoll_wait監聽所有fd,處理發生的讀寫事件;epoll_wait是阻塞調用,最后一個參數timeout是超時時間,即最多阻塞timeout時間如果還是沒有事件發生,方法會返回;

nginx在設置超時時間timeout時,會從上面說的記錄超時定時器的紅黑樹中查找最近要到時的節點,以此作為epoll_wait的超時時間,如下面代碼所示;

ngx_msec_t ngx_event_find_timer(void){ node = ngx_rbtree_min(root, sentinel); timer = (ngx_msec_int_t) (node->key - ngx_current_msec);  return (ngx_msec_t) (timer > 0 ? timer : 0);}

同時nginx在每次循環的最后,會從紅黑樹中查看是否有事件已經過期,如果過期,標記timeout=1,并調用事件的handler;

void ngx_event_expire_timers(void){ for ( ;; ) {  node = ngx_rbtree_min(root, sentinel);   if ((ngx_msec_int_t) (node->key - ngx_current_msec) <= 0) { //當前事件已經超時   ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));    ev->timedout = 1;    ev->handler(ev);    continue;  }   break; }}

nginx就是通過上面的方法實現了socket事件的處理,定時事件的處理;

ngx_http_limit_req_module模塊解析

ngx_http_limit_req_module模塊是對請求進行限流,即限制某一時間段內用戶的請求速率;且使用的是令牌桶算法;

3.1配置指令

ngx_http_limit_req_module模塊提供一下配置指令,供用戶配置限流策略

//每個配置指令主要包含兩個字段:名稱,解析配置的處理方法static ngx_command_t ngx_http_limit_req_commands[] = {  //一般用法:limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; //$binary_remote_addr表示遠程客戶端IP; //zone配置一個存儲空間(需要分配空間記錄每個客戶端的訪問速率,超時空間限制使用lru算法淘汰;注意此空間是在共享內存分配的,所有worker進程都能訪問) //rate表示限制速率,此例為1qps { ngx_string("limit_req_zone"),  ngx_http_limit_req_zone,  },  //用法:limit_req zone=one burst=5 nodelay; //zone指定使用哪一個共享空間 //超出此速率的請求是直接丟棄嗎?burst配置用于處理突發流量,表示最大排隊請求數目,當客戶端請求速率超過限流速率時,請求會排隊等待;而超出burst的才會被直接拒絕; //nodelay必須與burst一起使用;此時排隊等待的請求會被優先處理;否則假如這些請求依然按照限流速度處理,可能等到服務器處理完成后,客戶端早已超時 { ngx_string("limit_req"),  ngx_http_limit_req,  },  //當請求被限流時,日志記錄級別;用法:limit_req_log_level info | notice | warn | error; { ngx_string("limit_req_log_level"),  ngx_conf_set_enum_slot,  },  //當請求被限流時,給客戶端返回的狀態碼;用法:limit_req_status 503 { ngx_string("limit_req_status"),  ngx_conf_set_num_slot, },};

注意:$binary_remote_addr是nginx提供的變量,用戶在配置文件中可以直接使用;nginx還提供了許多變量,在ngx_http_variable.c文件中查找ngx_http_core_variables數組即可:

static ngx_http_variable_t ngx_http_core_variables[] = {  { ngx_string("http_host"), NULL, ngx_http_variable_header,  offsetof(ngx_http_request_t, headers_in.host), 0, 0 },  { ngx_string("http_user_agent"), NULL, ngx_http_variable_header,  offsetof(ngx_http_request_t, headers_in.user_agent), 0, 0 }, …………}

3.2源碼解析

ngx_http_limit_req_module在postconfiguration過程會注冊ngx_http_limit_req_handler方法到HTTP處理的NGX_HTTP_PREACCESS_PHASE階段;

ngx_http_limit_req_handler會執行漏桶算法,判斷是否超出配置的限流速率,從而進行丟棄或者排隊或者通過;

當用戶第一次請求時,會新增一條記錄(主要記錄訪問計數、訪問時間),以客戶端IP地址(配置$binary_remote_addr)的hash值作為key存儲在紅黑樹中(快速查找),同時存儲在LRU隊列中(存儲空間不夠時,淘汰記錄,每次都是從尾部刪除);當用戶再次請求時,會從紅黑樹中查找這條記錄并更新,同時移動記錄到LRU隊列首部;

3.2.1數據結構

limit_req_zone配置限流算法所需的存儲空間(名稱及大?。?,限流速度,限流變量(客戶端IP等),結構如下:

typedef struct { ngx_http_limit_req_shctx_t *sh; ngx_slab_pool_t    *shpool;//內存池 ngx_uint_t     rate; //限流速度(qps乘以1000存儲) ngx_int_t     index; //變量索引(nginx提供了一系列變量,用戶配置的限流變量索引) ngx_str_t     var; //限流變量名稱 ngx_http_limit_req_node_t *node;} ngx_http_limit_req_ctx_t; //同時會初始化共享存儲空間struct ngx_shm_zone_s { void      *data; //data指向ngx_http_limit_req_ctx_t結構 ngx_shm_t     shm; //共享空間 ngx_shm_zone_init_pt  init; //初始化方法函數指針 void      *tag; //指向ngx_http_limit_req_module結構體};

limit_req配置限流使用的存儲空間,排隊隊列大小,是否緊急處理,結構如下:

typedef struct { ngx_shm_zone_t    *shm_zone; //共享存儲空間   ngx_uint_t     burst;  //隊列大小 ngx_uint_t     nodelay; //有請求排隊時是否緊急處理,與burst配合使用(如果配置,則會緊急處理排隊請求,否則依然按照限流速度處理)} ngx_http_limit_req_limit_t;

Nginx源碼,nginx,限流模塊

前面說過用戶訪問記錄會同時存儲在紅黑樹與LRU隊列中,結構如下:

//記錄結構體typedef struct { u_char      color; u_char      dummy; u_short      len; //數據長度 ngx_queue_t     queue;  ngx_msec_t     last; //上次訪問時間   ngx_uint_t     excess; //當前剩余待處理的請求數(nginx用此實現令牌桶限流算法) ngx_uint_t     count; //此類記錄請求的總數 u_char      data[1];//數據內容(先按照key(hash值)查找,再比較數據內容是否相等)} ngx_http_limit_req_node_t; //紅黑樹節點,key為用戶配置限流變量的hash值;struct ngx_rbtree_node_s { ngx_rbtree_key_t  key; ngx_rbtree_node_t  *left; ngx_rbtree_node_t  *right; ngx_rbtree_node_t  *parent; u_char     color; u_char     data;};  typedef struct { ngx_rbtree_t     rbtree; //紅黑樹 ngx_rbtree_node_t    sentinel; //NIL節點 ngx_queue_t     queue; //LRU隊列} ngx_http_limit_req_shctx_t; //隊列只有prev和next指針struct ngx_queue_s { ngx_queue_t *prev; ngx_queue_t *next;};

思考1:ngx_http_limit_req_node_t記錄通過prev和next指針形成雙向鏈表,實現LRU隊列;最新訪問的節點總會被插入鏈表頭部,淘汰時從尾部刪除節點;

Nginx源碼,nginx,限流模塊

ngx_http_limit_req_ctx_t *ctx;ngx_queue_t    *q; q = ngx_queue_last(&ctx->sh->queue); lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);//此方法由ngx_queue_t獲取ngx_http_limit_req_node_t結構首地址,實現如下: #define ngx_queue_data(q, type, link) (type *) ((u_char *) q - offsetof(type, link)) //queue字段地址減去其在結構體中偏移,為結構體首地址

思考2:限流算法首先使用key查找紅黑樹節點,從而找到對應的記錄,紅黑樹節點如何與記錄ngx_http_limit_req_node_t結構關聯起來呢?在ngx_http_limit_req_module模塊可以找到以代碼:

size = offsetof(ngx_rbtree_node_t, color) //新建記錄分配內存,計算所需空間大小  + offsetof(ngx_http_limit_req_node_t, data)  + len; node = ngx_slab_alloc_locked(ctx->shpool, size); node->key = hash; lr = (ngx_http_limit_req_node_t *) &node->color; //color為u_char類型,為什么能強制轉換為ngx_http_limit_req_node_t指針類型呢? lr->len = (u_char) len;lr->excess = 0; ngx_memcpy(lr->data, data, len); ngx_rbtree_insert(&ctx->sh->rbtree, node); ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);

通過分析上面代碼,ngx_rbtree_node_s結構體的color與data字段其實是無意義的,結構體的生命形式與最終存儲形式是不同的,nginx最終使用以下存儲形式存儲每條記錄;

Nginx源碼,nginx,限流模塊

3.2.2限流算法

上面提到在postconfiguration過程會注冊ngx_http_limit_req_handler方法到HTTP處理的NGX_HTTP_PREACCESS_PHASE階段;

因此在處理HTTP請求時,會執行ngx_http_limit_req_handler方法判斷是否需要限流;

3.2.2.1漏桶算法實現

用戶可能同時配置若干限流,因此對于HTTP請求,nginx需要遍歷所有限流策略,判斷是否需要限流;

ngx_http_limit_req_lookup方法實現了漏桶算法,方法返回3種結果:

  • NGX_BUSY:請求速率超出限流配置,拒絕請求;
  • NGX_AGAIN:請求通過了當前限流策略校驗,繼續校驗下一個限流策略;
  • NGX_OK:請求已經通過了所有限流策略的校驗,可以執行下一階段;
  • NGX_ERROR:出錯
//limit,限流策略;hash,記錄key的hash值;data,記錄key的數據內容;len,記錄key的數據長度;ep,待處理請求數目;account,是否是最后一條限流策略static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, ngx_uint_t hash, u_char *data, size_t len, ngx_uint_t *ep, ngx_uint_t account){ //紅黑樹查找指定界定 while (node != sentinel) {   if (hash < node->key) {   node = node->left;   continue;  }   if (hash > node->key) {   node = node->right;   continue;  }   //hash值相等,比較數據是否相等  lr = (ngx_http_limit_req_node_t *) &node->color;   rc = ngx_memn2cmp(data, lr->data, len, (size_t) lr->len);  //查找到  if (rc == 0) {   ngx_queue_remove(&lr->queue);   ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); //將記錄移動到LRU隊列頭部     ms = (ngx_msec_int_t) (now - lr->last); //當前時間減去上次訪問時間    excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000; //待處理請求書-限流速率*時間段+1個請求(速率,請求數等都乘以1000了)    if (excess < 0) {    excess = 0;   }    *ep = excess;    //待處理數目超過burst(等待隊列大?。?,返回NGX_BUSY拒絕請求(沒有配置burst時,值為0)   if ((ngx_uint_t) excess > limit->burst) {    return NGX_BUSY;   }    if (account) { //如果是最后一條限流策略,則更新上次訪問時間,待處理請求數目,返回NGX_OK    lr->excess = excess;    lr->last = now;    return NGX_OK;   }   //訪問次數遞增   lr->count++;    ctx->node = lr;    return NGX_AGAIN; //非最后一條限流策略,返回NGX_AGAIN,繼續校驗下一條限流策略  }   node = (rc < 0) ? node->left : node->right; }  //假如沒有查找到節點,需要新建一條記錄 *ep = 0; //存儲空間大小計算方法參照3.2.1節數據結構 size = offsetof(ngx_rbtree_node_t, color)   + offsetof(ngx_http_limit_req_node_t, data)   + len; //嘗試淘汰記錄(LRU) ngx_http_limit_req_expire(ctx, 1);    node = ngx_slab_alloc_locked(ctx->shpool, size);//分配空間 if (node == NULL) { //空間不足,分配失敗  ngx_http_limit_req_expire(ctx, 0); //強制淘汰記錄   node = ngx_slab_alloc_locked(ctx->shpool, size); //分配空間  if (node == NULL) { //分配失敗,返回NGX_ERROR   return NGX_ERROR;  } }  node->key = hash; //賦值 lr = (ngx_http_limit_req_node_t *) &node->color; lr->len = (u_char) len; lr->excess = 0; ngx_memcpy(lr->data, data, len);  ngx_rbtree_insert(&ctx->sh->rbtree, node); //插入記錄到紅黑樹與LRU隊列 ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);  if (account) { //如果是最后一條限流策略,則更新上次訪問時間,待處理請求數目,返回NGX_OK  lr->last = now;  lr->count = 0;  return NGX_OK; }  lr->last = 0; lr->count = 1;  ctx->node = lr;  return NGX_AGAIN; //非最后一條限流策略,返回NGX_AGAIN,繼續校驗下一條限流策略  }

舉個例子,假如burst配置為0,待處理請求數初始為excess;令牌產生周期為T;如下圖所示

Nginx源碼,nginx,限流模塊

3.2.2.2LRU淘汰策略

上一節叩痛算法中,會執行ngx_http_limit_req_expire淘汰一條記錄,每次都是從LRU隊列末尾刪除;

第二個參數n,當n==0時,強制刪除末尾一條記錄,之后再嘗試刪除一條或兩條記錄;n==1時,會嘗試刪除一條或兩條記錄;代碼實現如下:

static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n){ //最多刪除3條記錄 while (n < 3) {  //尾部節點  q = ngx_queue_last(&ctx->sh->queue);  //獲取記錄  lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);     //注意:當為0時,無法進入if代碼塊,因此一定會刪除尾部節點;當n不為0時,進入if代碼塊,校驗是否可以刪除  if (n++ != 0) {    ms = (ngx_msec_int_t) (now - lr->last);   ms = ngx_abs(ms);   //短時間內被訪問,不能刪除,直接返回   if (ms < 60000) {    return;   }       //有待處理請求,不能刪除,直接返回   excess = lr->excess - ctx->rate * ms / 1000;   if (excess > 0) {    return;   }  }   //刪除  ngx_queue_remove(q);   node = (ngx_rbtree_node_t *)     ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));   ngx_rbtree_delete(&ctx->sh->rbtree, node);   ngx_slab_free_locked(ctx->shpool, node); }}

3.2.2.3 burst實現

burst是為了應對突發流量的,偶然間的突發流量到達時,應該允許服務端多處理一些請求才行;

當burst為0時,請求只要超出限流速率就會被拒絕;當burst大于0時,超出限流速率的請求會被排隊等待 處理,而不是直接拒絕;

排隊過程如何實現?而且nginx還需要定時去處理排隊中的請求;

2.2小節提到事件都有一個定時器,nginx是通過事件與定時器配合實現請求的排隊與定時處理;

ngx_http_limit_req_handler方法有下面的代碼:

//計算當前請求還需要排隊多久才能處理delay = ngx_http_limit_req_account(limits, n, &excess, &limit);//添加可讀事件if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR;}r->read_event_handler = ngx_http_test_reading;r->write_event_handler = ngx_http_limit_req_delay; //可寫事件處理函數ngx_add_timer(r->connection->write, delay); //可寫事件添加定時器(超時之前是不能往客戶端返回的)

計算delay的方法很簡單,就是遍歷所有的限流策略,計算處理完所有待處理請求需要的時間,返回最大值;

if (limits[n].nodelay) { //配置了nodelay時,請求不會被延時處理,delay為0 continue;} delay = excess * 1000 / ctx->rate; if (delay > max_delay) { max_delay = delay; *ep = excess; *limit = &limits[n];}

簡單看看可寫事件處理函數ngx_http_limit_req_delay的實現

static void ngx_http_limit_req_delay(ngx_http_request_t *r){  wev = r->connection->write;  if (!wev->timedout) { //沒有超時不會處理   if (ngx_handle_write_event(wev, 0) != NGX_OK) {   ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);  }   return; }  wev->timedout = 0;  r->read_event_handler = ngx_http_block_reading; r->write_event_handler = ngx_http_core_run_phases;  ngx_http_core_run_phases(r); //超時了,繼續處理HTTP請求}

4.實戰

4.1測試普通限流

1)配置nginx限流速率為1qps,針對客戶端IP地址限流(返回狀態碼默認為503),如下:

http{ limit_req_zone $binary_remote_addr zone=test:10m rate=1r/s;  server {  listen  80;  server_name localhost;  location / {   limit_req zone=test;   root html;   index index.html index.htm;  }}

2)連續并發發起若干請求;3)查看服務端access日志,可以看到22秒連續到達3個請求,只處理1個請求;23秒到達兩個請求,第一個請求處理,第二個請求被拒絕

xx.xx.xx.xxx - - [22/Sep/2018:23:33:22 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:33:22 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:33:22 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:33:23 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:33:23 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"

4.2測試burst

1)限速1qps時,超過請求會被直接拒絕,為了應對突發流量,應該允許請求被排隊處理;因此配置burst=5,即最多允許5個請求排隊等待處理;

http{ limit_req_zone $binary_remote_addr zone=test:10m rate=1r/s;  server {  listen  80;  server_name localhost;  location / {   limit_req zone=test burst=5;   root html;   index index.html index.htm;  }}

2)使用ab并發發起10個請求,ab -n 10 -c 10 http://xxxxx;

3)查看服務端access日志;根據日志顯示第一個請求被處理,2到5四個請求拒絕,6到10五個請求被處理;為什么會是這樣的結果呢?

查看ngx_http_log_module,注冊handler到NGX_HTTP_LOG_PHASE階段(HTTP請求處理最后一個階段);

因此實際情況應該是這樣的:10個請求同時到達,第一個請求到達直接被處理,第2到6個請求到達,排隊延遲處理(每秒處理一個);第7到10個請求被直接拒絕,因此先打印access日志;

第2到6個請求米誒秒處理一個,處理完成打印access日志,即49到53秒每秒處理一個;

xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:49 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:50 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:51 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:52 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:53 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"

4)ab統計的響應時間見下面,最小響應時間87ms,最大響應時間5128ms,平均響應時間為1609ms:

    min mean[+/-sd] median maxConnect:  41 44 1.7  44  46Processing: 46 1566 1916.6 1093 5084Waiting:  46 1565 1916.7 1092 5084Total:   87 1609 1916.2 1135 5128

4.3測試nodelay

1)4.2顯示,配置burst后,雖然突發請求會被排隊處理,但是響應時間過長,客戶端可能早已超時;因此添加配置nodelay,使得nginx緊急處理等待請求,以減小響應時間:

http{ limit_req_zone $binary_remote_addr zone=test:10m rate=1r/s;  server {  listen  80;  server_name localhost;  location / {   limit_req zone=test burst=5 nodelay;   root html;   index index.html index.htm;  }}

2)使用ab并發發起10個請求,ab -n 10 -c 10 http://xxxx/;

3)查看服務端access日志;第一個請求直接處理,第2到6個五個請求排隊處理(配置nodelay,nginx緊急處理),第7到10四個請求被拒絕

xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"

4)ab統計的響應時間見下面,最小響應時間85ms,最大響應時間92ms,平均響應時間為88ms:

    min mean[+/-sd] median maxConnect:  42 43 0.5  43  43Processing: 43 46 2.4  47  49Waiting:  42 45 2.5  46  49Total:   85 88 2.8  90  92

總結

本文首先分析常用限流算法(漏桶算法與令牌桶算法),并簡單介紹nginx處理HTTP請求的過程,nginx定時事件實現;然后詳細分析ngx_http_limit_req_module模塊的基本數據結構,及其限流過程;并以實例幫助讀者體會nginx限流的配置及結果。至于另一個模塊ngx_http_limit_conn_module是針對鏈接數的限流,比較容易理解,在此就不做詳細介紹。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到服務器教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美精品一本久久男人的天堂| 欧美成人精品在线观看| 亚洲深夜福利在线| 97香蕉久久夜色精品国产| 在线观看欧美日韩| 懂色av一区二区三区| 久久久天堂国产精品女人| 九九热这里只有精品免费看| 国模叶桐国产精品一区| 欧美一级大片视频| 欧美一区三区三区高中清蜜桃| 91亚洲精品久久久久久久久久久久| 国产精品人成电影在线观看| 欧美激情国产日韩精品一区18| 欧美精品久久久久久久免费观看| 色无极亚洲影院| 久久久久久国产三级电影| 亚洲欧美日韩另类| 亚洲综合社区网| 国产91露脸中文字幕在线| 最近2019年好看中文字幕视频| 亚洲女人天堂成人av在线| 亚洲精品久久久久久久久久久| 精品国产一区二区在线| 欧美精品免费播放| 久久久久久网址| 在线精品视频视频中文字幕| 亚洲日本aⅴ片在线观看香蕉| 在线观看91久久久久久| 色综合天天综合网国产成人网| 久久久亚洲精品视频| 国产成人精品在线播放| 久久理论片午夜琪琪电影网| 精品国产欧美一区二区五十路| 97精品国产91久久久久久| 欧美在线观看一区二区三区| 69久久夜色精品国产7777| 色小说视频一区| 色偷偷888欧美精品久久久| 性色av一区二区三区红粉影视| 日韩国产在线看| 亚洲精品久久久久中文字幕二区| 91精品国产91久久久久久| 国产精品美女999| 欧美一级大片视频| 亚洲欧美国产视频| 国产精品视频一区国模私拍| 国内精品久久久久久久| 亚洲开心激情网| 亚洲xxxx视频| 怡红院精品视频| 久久99视频精品| 亚洲精品日产aⅴ| 久久久国产成人精品| 国产精品欧美日韩久久| 久久久精品国产一区二区| 久久久国产精品一区| 最近2019中文字幕大全第二页| 中文字幕久热精品视频在线| 欧美激情日韩图片| 国产一区二区在线免费视频| 中文字幕日韩免费视频| 久久精品成人欧美大片| 91中文精品字幕在线视频| 欧美激情精品久久久久久| 欧美激情一区二区三级高清视频| 欧美激情视频网址| 美女福利视频一区| 2018国产精品视频| 97**国产露脸精品国产| 自拍视频国产精品| 欧美wwwwww| 亚洲人成网在线播放| 久久精品成人欧美大片| 91久久国产精品91久久性色| 久久久久久香蕉网| 亚洲欧美国产另类| 国产精品一区二区三区在线播放| 国产精品亚洲综合天堂夜夜| 麻豆成人在线看| 日本精品性网站在线观看| 日韩激情在线视频| 亚洲美女喷白浆| 日本a级片电影一区二区| 日韩视频精品在线| 欧美激情视频网| 久久久久久久久久婷婷| 日韩精品电影网| 亚洲影影院av| 亚洲图片在线综合| 亚洲v日韩v综合v精品v| 国产一区二区三区精品久久久| 成人午夜在线影院| 日韩电影网在线| 亚洲最新av在线网站| 欧美性猛交xxxx久久久| 日韩在线一区二区三区免费视频| 色妞在线综合亚洲欧美| 性色av一区二区三区在线观看| 国产精品国产福利国产秒拍| 日韩黄色在线免费观看| 亚洲欧洲一区二区三区久久| 国产精品午夜一区二区欲梦| 久久夜色精品亚洲噜噜国产mv| 777精品视频| 久久久亚洲国产天美传媒修理工| 中文一区二区视频| 日韩av在线资源| 国产精品三级在线| 欧美裸体xxxx极品少妇| 国产日韩在线精品av| 亚洲欧美另类国产| 国产91成人在在线播放| 日韩大陆欧美高清视频区| 伊人久久五月天| 精品亚洲一区二区三区四区五区| 亚洲欧美在线免费观看| 国产99久久精品一区二区| 色无极影院亚洲| 国产一区二区欧美日韩| 欧美最猛性xxxxx免费| 亚洲精品成人久久电影| 一区二区三区回区在观看免费视频| 成人黄色片在线| 久久99视频精品| 伊人久久综合97精品| 日韩欧美aⅴ综合网站发布| 国产美女精彩久久| 亚洲天堂av在线播放| 欧洲成人性视频| 国产精品美女在线观看| 日韩欧美中文字幕在线播放| 亚洲欧美国产另类| 国产精品扒开腿做| 亚洲黄色在线观看| 在线日韩中文字幕| 奇米四色中文综合久久| 在线电影av不卡网址| 国产精品视频成人| 日韩精品在线免费观看视频| 国产成人精品一区二区在线| 久久男人av资源网站| 91视频免费在线| 亚洲国产古装精品网站| 中文一区二区视频| 91av国产在线| 久久这里只有精品99| 亚洲成人激情在线观看| 欧美日韩国产丝袜美女| 一个人看的www欧美| 欧美精品一区在线播放| 亚洲视频在线观看免费| 日本一本a高清免费不卡| 久久亚洲精品一区二区| 亚洲精品久久久久中文字幕二区| 午夜精品久久久久久久男人的天堂| 精品欧美一区二区三区| 日本不卡高字幕在线2019| 亚洲精品成人久久电影| 日韩女优在线播放| 欧美高清视频在线观看| 久久91精品国产91久久跳| 久久香蕉精品香蕉| 亚洲国产日韩欧美综合久久|