運營研發團隊 李樂html
限流的目的是經過對併發訪問/請求進行限速來保護系統,一旦達到限制速率則能夠拒絕服務(定向到錯誤頁)、排隊等待(秒殺)、降級(返回兜底數據或默認數據);node
高併發系統常見的限流有:限制總併發數(數據庫鏈接池)、限制瞬時併發數(如nginx的limit_conn模塊,用來限制瞬時併發鏈接數)、限制時間窗口內的平均速率(nginx的limit_req模塊,用來限制每秒的平均速率);nginx
另外還能夠根據網絡鏈接數、網絡流量、CPU或內存負載等來限流。算法
最簡單粗暴的限流算法就是計數器法了,而比較經常使用的有漏桶算法和令牌桶算法;數據庫
計數器法是限流算法裏最簡單也是最容易實現的一種算法。好比咱們規定,對於A接口來講,咱們1分鐘的訪問次數不能超過100個。後端
那麼咱們咱們能夠設置一個計數器counter,其有效時間爲1分鐘(即每分鐘計數器會被重置爲0),每當一個請求過來的時候,counter就加1,若是counter的值大於100,就說明請求數過多;數組
這個算法雖然簡單,可是有一個十分致命的問題,那就是臨界問題。緩存
以下圖所示,在1:00前一刻到達100個請求,1:00計數器被重置,1:00後一刻又到達100個請求,顯然計數器不會超過100,全部請求都不會被攔截;服務器
然而這一時間段內請求數已經達到200,遠超100。網絡
以下圖所示,有一個固定容量的漏桶,按照常量固定速率流出水滴;若是桶是空的,則不會流出水滴;流入到漏桶的水流速度是隨意的;若是流入的水超出了桶的容量,則流入的水會溢出(被丟棄);
能夠看到漏桶算法天生就限制了請求的速度,能夠用於流量整形和限流控制;
令牌桶是一個存放固定容量令牌的桶,按照固定速率r往桶裏添加令牌;桶中最多存放b個令牌,當桶滿時,新添加的令牌被丟棄;
當一個請求達到時,會嘗試從桶中獲取令牌;若是有,則繼續處理請求;若是沒有則排隊等待或者直接丟棄;
能夠發現,漏桶算法的流出速率恆定或者爲0,而令牌桶算法的流出速率卻有可能大於r;
Nginx主要有兩種限流方式:按鏈接數限流(ngx_http_limit_conn_module)、按請求速率限流(ngx_http_limit_req_module);
學習限流模塊以前還須要瞭解nginx對HTTP請求的處理過程,nginx事件處理流程等;
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; }
假設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模塊提供一下配置指令,供用戶配置限流策略
//每一個配置指令主要包含兩個字段:名稱,解析配置的處理方法 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 }, ………… }
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隊列首部;
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;
前面說過用戶訪問記錄會同時存儲在紅黑樹與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隊列;最新訪問的節點總會被插入鏈表頭部,淘汰時從尾部刪除節點;
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最終使用如下存儲形式存儲每條記錄;
上面提到在postconfiguration過程會註冊ngx_http_limit_req_handler方法到HTTP處理的NGX_HTTP_PREACCESS_PHASE階段;
所以在處理HTTP請求時,會執行ngx_http_limit_req_handler方法判斷是否須要限流;
用戶可能同時配置若干限流,所以對於HTTP請求,nginx須要遍歷全部限流策略,判斷是否須要限流;
ngx_http_limit_req_lookup方法實現了漏桶算法,方法返回3種結果:
//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;以下圖所示
上一節叩痛算法中,會執行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); } }
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請求 }
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; } }
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"
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; } }
查看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"
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
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; } }
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"
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是針對連接數的限流,比較容易理解,在此就不作詳細介紹。