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;如下圖所示

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 max
Connect:  41 44 1.7  44  46
Processing: 46 1566 1916.6 1093 5084
Waiting:  46 1565 1916.7 1092 5084
Total:   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 max
Connect:  42 43 0.5  43  43
Processing: 43 46 2.4  47  49
Waiting:  42 45 2.5  46  49
Total:   85 88 2.8  90  92

總結

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持三五互聯

更多關于云服務器域名注冊,虛擬主機的問題,請訪問三五互聯官網:m.shinetop.cn

贊(0)
聲明:本網站發布的內容(圖片、視頻和文字)以原創、轉載和分享網絡內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。郵箱:3140448839@qq.com。本站原創內容未經允許不得轉載,或轉載時需注明出處:三五互聯知識庫 » Nginx源碼研究之nginx限流模塊詳解

登錄

找回密碼

注冊

主站蜘蛛池模板: 天堂www在线中文| 99久久er热在这里只有精品99| 99热精品毛片全部国产无缓冲| 国产乱码一区二区三区免费| 日本真人做爰免费视频120秒| 久久国产精品老女人| 五月天国产成人AV免费观看| 亚洲精品久久久久国色天香| 亚洲日本精品一区二区| 亚洲av久久精品狠狠爱av| 中文日产幕无线码一区中文| 欧美福利在线| 亚洲综合国产激情另类一区| 亚洲国产精品一区在线看| 亚洲成a人片在线视频| 国产三级精品福利久久| 色综合视频一区二区三区| 日韩成人无码影院| 日本一区二区精品色超碰| 日本做受高潮好舒服视频| 宝贝腿开大点我添添公口述视频 | 强伦姧人妻免费无码电影| 午夜在线不卡| av无码av无码专区| 美女裸体黄网站18禁止免费下载| 国产精品黄色片| 国产精品自拍视频免费看| 国产一区二区在线有码| 国产午夜无码视频在线观看| 国产微拍一区二区三区四区| 国产午精品午夜福利757视频播放| 亚欧美闷骚院| 欧美成人看片黄A免费看| 张家口市| 日韩成av在线免费观看| 欧美和黑人xxxx猛交视频| 国产精品美女网站| 成年女人片免费视频播放A| 起碰免费公开97在线视频| 中文字幕无线码中文字幕免费| 泾源县|