天天看點

Nginx 限流配置-令牌桶算法

Nginx 限流配置-令牌桶算法

算法思想是:

  • 令牌以固定速率産生,并緩存到令牌桶中;
  • 令牌桶放滿時,多餘的令牌被丢棄;
  • 請求要消耗等比例的令牌才能被處理;
  • 令牌不夠時,請求被緩存。

漏桶算法

Nginx 限流配置-令牌桶算法

算法思想是:

  • 水(請求)從上方倒入水桶,從水桶下方流出(被處理);
  • 來不及流出的水存在水桶中(緩沖),以固定速率流出;
  • 水桶滿後水溢出(丢棄)。
  • 這個算法的核心是:緩存請求、勻速處理、多餘的請求直接丢棄。

    相比漏桶算法,令牌桶算法不同之處在于它不但有一隻“桶”,還有個隊列,這個桶是用來存放令牌的,隊列才是用來存放請求的。

從作用上來說,漏桶和令牌桶算法最明顯的差別就是是否允許突發流量(burst)的處理,漏桶算法能夠強行限制資料的實時傳輸(處理)速率,對突發流量不做額外處理;而令牌桶算法能夠在限制資料的平均傳輸速率的同時允許某種程度的突發傳輸。

Nginx按請求速率限速子產品使用的是漏桶算法,即能夠強行保證請求的實時處理速度不會超過設定的門檻值。

Nginx官方版本限制IP的連接配接和并發分别有兩個子產品:

  • limit_req_zone

     用來限制機關時間内的請求數,即速率限制,采用的漏桶算法 "leaky bucket"。
  • limit_req_conn

     用來限制同一時間連接配接數,即并發限制。

limit_req_zone 參數配置

Syntax:	limit_req zone=name [burst=number] [nodelay];
Default:	—
Context:	http, server, location
           

limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

  • 第一個參數:$binary_remote_addr 表示通過remote_addr這個辨別來做限制,“binary_”的目的是縮寫記憶體占用量,是限制同一用戶端ip位址。
  • 第二個參數:zone=one:10m表示生成一個大小為10M,名字為one的記憶體區域,用來存儲通路的頻次資訊。
  • 第三個參數:rate=1r/s表示允許相同辨別的用戶端的通路頻次,這裡限制的是每秒1次,還可以有比如30r/m的。

limit_req zone=one burst=5 nodelay;

  • 第一個參數:zone=one 設定使用哪個配置區域來做限制,與上面limit_req_zone 裡的name對應。
  • 第二個參數:burst=5,重點說明一下這個配置,burst爆發的意思,這個配置的意思是設定一個大小為5的緩沖區當有大量請求(爆發)過來時,超過了通路頻次限制的請求可以先放到這個緩沖區内。
  • 第三個參數:nodelay,如果設定,超過通路頻次而且緩沖區也滿了的時候就會直接傳回503,如果沒有設定,則所有請求會等待排隊。

例子:

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
    server {
        location /search/ {
            limit_req zone=one burst=5 nodelay;
        }
}        
           

下面配置可以限制特定UA(比如搜尋引擎)的通路:

limit_req_zone  $anti_spider  zone=one:10m   rate=10r/s;
limit_req zone=one burst=100 nodelay;
if ($http_user_agent ~* "googlebot|bingbot|Feedfetcher-Google") {
    set $anti_spider $http_user_agent;
}
           

其他參數

Syntax:	limit_req_log_level info | notice | warn | error;
Default:	
limit_req_log_level error;
Context:	http, server, location
           

當伺服器由于limit被限速或緩存時,配置寫入日志。延遲的記錄比拒絕的記錄低一個級别。例子:

limit_req_log_level notice

延遲的的基本是info。

Syntax:	limit_req_status code;
Default:	
limit_req_status 503;
Context:	http, server, location
           

設定拒絕請求的傳回值。值隻能設定 400 到 599 之間。

ngx_http_limit_conn_module 參數配置

這個子產品用來限制單個IP的請求數。并非所有的連接配接都被計數。隻有在伺服器處理了請求并且已經讀取了整個請求頭時,連接配接才被計數。

Syntax:	limit_conn zone number;
Default:	—
Context:	http, server, location
           
limit_conn_zone $binary_remote_addr zone=addr:10m;

server {
    location /download/ {
        limit_conn addr 1;
    }
           

一次隻允許每個IP位址一個連接配接。

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;

server {
    ...
    limit_conn perip 10;
    limit_conn perserver 100;
}
           

可以配置多個limit_conn指令。例如,以上配置将限制每個用戶端IP連接配接到伺服器的數量,同時限制連接配接到虛拟伺服器的總數。

Syntax:	limit_conn_zone key zone=name:size;
Default:	—
Context:	http
           
limit_conn_zone $binary_remote_addr zone=addr:10m;
           

在這裡,用戶端IP位址作為關鍵。請注意,不是

$ remote_addr

,而是使用

$ binary_remote_addr

變量。 

$ remote_addr

變量的大小可以從7到15個位元組不等。存儲的狀态在32位平台上占用32或64位元組的記憶體,在64位平台上總是占用64位元組。對于IPv4位址,

$ binary_remote_addr

變量的大小始終為4個位元組,對于IPv6位址則為16個位元組。存儲狀态在32位平台上始終占用32或64個位元組,在64位平台上占用64個位元組。一個兆位元組的區域可以保持大約32000個32位元組的狀态或大約16000個64位元組的狀态。如果區域存儲耗盡,伺服器會将錯誤傳回給所有其他請求。

Syntax:	limit_conn_log_level info | notice | warn | error;
Default:	
limit_conn_log_level error;
Context:	http, server, location
           

當伺服器限制連接配接數時,設定所需的日志記錄級别。

Syntax:	limit_conn_status code;
Default:	
limit_conn_status 503;
Context:	http, server, location
           

設定拒絕請求的傳回值。

實戰

執行個體一 限制通路速率

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit;
    }
}
           

上述規則限制了每個IP通路的速度為2r/s,并将該規則作用于根目錄。如果單個IP在非常短的時間内并發發送多個請求,結果會怎樣呢?

Nginx 限流配置-令牌桶算法

我們使用單個IP在10ms内發并發送了6個請求,隻有1個成功,剩下的5個都被拒絕。我們設定的速度是2r/s,為什麼隻有1個成功呢,是不是Nginx限制錯了?當然不是,是因為Nginx的限流統計是基于毫秒的,我們設定的速度是2r/s,轉換一下就是500ms内單個IP隻允許通過1個請求,從501ms開始才允許通過第二個請求。

執行個體二 burst緩存處理

我們看到,我們短時間内發送了大量請求,Nginx按照毫秒級精度統計,超出限制的請求直接拒絕。這在實際場景中未免過于苛刻,真實網絡環境中請求到來不是勻速的,很可能有請求“突發”的情況,也就是“一股子一股子”的。Nginx考慮到了這種情況,可以通過burst關鍵字開啟對突發請求的緩存處理,而不是直接拒絕。

來看我們的配置:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4;
    }
}
           

我們加入了burst=4,意思是每個key(此處是每個IP)最多允許4個突發請求的到來。如果單個IP在10ms内發送6個請求,結果會怎樣呢?

Nginx 限流配置-令牌桶算法

相比執行個體一成功數增加了4個,這個我們設定的burst數目是一緻的。具體處理流程是:1個請求被立即處理,4個請求被放到burst隊列裡,另外一個請求被拒絕。通過burst參數,我們使得Nginx限流具備了緩存處理突發流量的能力。

但是請注意:burst的作用是讓多餘的請求可以先放到隊列裡,慢慢處理。如果不加nodelay參數,隊列裡的請求不會立即處理,而是按照rate設定的速度,以毫秒級精确的速度慢慢處理。

執行個體三 nodelay降低排隊時間

執行個體二中我們看到,通過設定burst參數,我們可以允許Nginx緩存處理一定程度的突發,多餘的請求可以先放到隊列裡,慢慢處理,這起到了平滑流量的作用。但是如果隊列設定的比較大,請求排隊的時間就會比較長,使用者角度看來就是RT變長了,這對使用者很不友好。有什麼解決辦法呢?nodelay參數允許請求在排隊的時候就立即被處理,也就是說隻要請求能夠進入burst隊列,就會立即被背景worker處理,請注意,這意味着burst設定了nodelay時,系統瞬間的QPS可能會超過rate設定的門檻值。nodelay參數要跟burst一起使用才有作用。

延續執行個體二的配置,我們加入nodelay選項:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4 nodelay;
    }
}
           

單個IP 10ms内并發發送6個請求,結果如下:

Nginx 限流配置-令牌桶算法

跟執行個體二相比,請求成功率沒變化,但是總體耗時變短了。這怎麼解釋呢?執行個體二中,有4個請求被放到burst隊列當中,工作程序每隔500ms(rate=2r/s)取一個請求進行處理,最後一個請求要排隊2s才會被處理;執行個體三中,請求放入隊列跟執行個體二是一樣的,但不同的是,隊列中的請求同時具有了被處理的資格,是以執行個體三中的5個請求可以說是同時開始被處理的,花費時間自然變短了。

但是請注意,雖然設定burst和nodelay能夠降低突發請求的處理時間,但是長期來看并不會提高吞吐量的上限,長期吞吐量的上限是由rate決定的,因為nodelay隻能保證burst的請求被立即處理,但Nginx會限制隊列元素釋放的速度,就像是限制了令牌桶中令牌産生的速度。

看到這裡你可能會問,加入了nodelay參數之後的限速算法,到底算是哪一個“桶”,是漏桶算法還是令牌桶算法?當然還算是漏桶算法。考慮一種情況,令牌桶算法的token為耗盡時會怎麼做呢?由于它有一個請求隊列,是以會把接下來的請求緩存下來,緩存多少受限于隊列大小。但此時緩存這些請求還有意義嗎?如果server已經過載,緩存隊列越來越長,RT越來越高,即使過了很久請求被處理了,對使用者來說也沒什麼價值了。是以當token不夠用時,最明智的做法就是直接拒絕使用者的請求,這就成了漏桶算法。

示例四 自定義傳回值

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4 nodelay;
        limit_req_status 598;
    }
}
           

預設情況下 沒有配置 status 傳回值的狀态:

Nginx 限流配置-令牌桶算法

自定義 status 傳回值的狀态:

Nginx 限流配置-令牌桶算法

參考文檔

Nginx 限流

Nginx限制通路速率和最大并發連接配接數子產品--limit (防範DDOS攻擊) - 星辰大海ゞ - 部落格園

 關于nginx的限速子產品 - 踏雪無痕SS - 部落格園

Module ngx_http_limit_conn_module

Module ngx_http_limit_req_module

Nginx限速子產品初探 - CarpenterLee - 部落格園

繼續閱讀