1 概述
限流(Rate Limitting)是服務降級的一種方式,透過限制系統的輸入和輸出流量以達到保護系統的目的。
比如我們的網站暴露在公網環境中,除了使用者的正常存取,網路爬蟲、惡意攻擊或者大促等突發流量都可能都會對系統造成壓力,如果這種壓力超出了伺服器的處理能力,會造成響應過慢甚至系統崩潰的問題。
因此,當並行請求數過大時,我們透過限制一部份請求(比如限制同一IP的頻繁請求)來保證伺服器可以正確響應另一部份的請求。
nginx 提供了兩種限流方式,一種是限制請求速率,一種是限制連線數量。
另外還提供了對下載/上傳速度的限制。
2 限制請求速率
nginx 的 ngx_http_limit_req_module 模組提供限制請求處理速率的能力,使用了漏桶演算法(leaky bucket algorithm)。我們可以想像有一只上面進水、下面勻速出水的桶,如果桶裏面有水,那剛進去的水就要存在桶裏等下面的水流完之後才會流出,如果進水的速度大於水流出的速度,桶裏的水就會滿,這時水就不會進到桶裏,而是直接從桶的上面溢位。
對應到處理網路請求,水代表從客戶端來的請求,而桶代表一個佇列,請求在該佇列中依據先進先出(FIFO)演算法等待被處理。漏的水代表請求離開緩沖區並被伺服器處理,溢位代表了請求被丟棄並且永不被服務。
2.1、正常限流
nginx 中有兩個主要的指令可以用來配置限流:
limit_req_zone
和
limit_req
。
下面是一個最簡單的限流的例子:
limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;
server {
location / {
limit_req zone=test;
}
}
imit_req_zone 用於設定限流和共享記憶體區域的參數,格式為:limit_req_zone key zone rate。
key:
定義限流物件,
$binary_remote_addr
是 nginx 中的變量,表示基於
remote_addr
(客戶端IP) 來做限流,
binary_
是二進制儲存。使用
$binary_remote_addr
而不是
$remote_addr
是因為二進制儲存可以壓縮記憶體占用量。
$remote_addr
變量的大小從7到15個字節不等,而
$binary_remote_addr
變量的大小對於 IPv4 始終為4個字節,對於 IPv6 地址則為16個字節。
zone: 定義共享記憶體區來儲存存取資訊,存取資訊包括每個 IP 地址狀態和存取受限請求 URL 的頻率等。zone 的定義又分為兩個部份:由 zone= 關鍵字標識的區網域名稱稱,以及冒號後面的區域大小。test:10m 表示一個大小為10M,名字為 test 的記憶體區域。1M 能儲存16000個 IP 地址的存取資訊,test 大概可以儲存約160000個地址。nginx 建立新記錄的時候,會移除前60秒內沒有被使用的記錄,如果釋放的空間還是儲存不了新的記錄,會返回503的狀態碼。
rate:
設定最大的存取速率。
rate=2r/s
(為了好模擬,rate 設定的值比較小),表示每秒最多處理 2個請求。事實上 nginx 是以毫秒為粒度追蹤請求的,
rate=2r/s
實際上是每500毫秒1個請求,也就是說,上一個請求完成後,如果500毫秒內還有請求到達,這些請求會被拒絕(預設返回503,如果想修改返回值,可以設定
limit_req_status
)。
limit_req_zone
只是設定限流參數,如果要生效的話,必須和
limit_req
配合使用。
limit_req
的格式為:
limit_req zone=name [burst=number] [nodelay]
。
上面的例子只簡單指定了
zone=test
,表示使用 test 這個區域的配置,在請求 html 檔時進行限流。我們可以理解為這個桶目前沒有任何儲存水滴的能力,到達的所有不能立即漏出的請求都會被拒絕。如果我1秒內發送了10次請求,其中前500毫秒1次,後500毫秒9次,那麽只有前500毫秒的請求和後500毫秒的第一次請求會響應,其余請求都會被拒絕。
2.2、處理突發流量
上面的配置保證了 nginx 以固定的速度提供服務(2r/s),但是這種情況不適用於有突發流量的情況,我們希望可以盡可能的緩存請求並處理它們,此時需要在
limit_req
上增加 burst 參數:
limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;
server {
location / {
limit_req zone=test burst=5;
}
}
burst 表示在超過設定的存取速率後能額外處理的請求數。當
rate=2r/s
時,表示每500ms 可以處理一個請求。
burst=5
時,如果同時有10個請求到達,nginx 會處理第1個請求,剩余9個請求中,會有5個被放入佇列,剩余的4個請求會直接被拒絕。然後每隔500ms從佇列中獲取一個請求進行處理,此時如果後面繼續有請求進來,如果佇列中的請求數目超過了5,會被拒絕,不足5的時候會添加到佇列中進行等待。我們可以理解為現在的桶可以存5滴水:
配置 burst 之後,雖然同時到達的請求不會全部被拒絕,但是仍需要等待500ms 一次的處理時間,放入桶中的第5個請求需要等待
500ms * 4
的時間才能被處理,更長的等待時間意味著使用者的流失,在許多場景下,這個等待時間是不可接受的。此時我們需要增加 nodelay 參數,和 burst 配合使用。
limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;
server {
location / {
limit_req zone=test burst=5 nodelay;
}
}
nodelay 表示不延遲。設定 nodelay 後,第一個到達的請求和佇列中的請求會立即進行處理,不會出現等待的請求。
需要註意的是,雖然佇列中的5個請求立即被處理了,但是佇列中的位置依舊是按照500ms 的速度依次被釋放的。後面的4個請求依舊是被拒絕的,長期來看並不會提高吞吐量的上限,長期吞吐量的上限是由設定的 rate 決定的。
2.3、設定白名單
如果遇到不需要限流的情況,比如測試要壓測,可以透過配置白名單,取消限流的設定。白名單要用到 nginx 的
ngx_http_geo_module
和
ngx_http_map_module
模組。
geo $limit {
default 1;
10.0.0.0/8 0;
192.168.0.0/24 0;
}
map $limit$limit_key {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=mylimit:10m rate=2r/s;
geo 指令可以根據 IP 建立變量
$limit
。
$limit
的預設值是1,如果匹配到了下面的 IP,則返回對應的值(這裏返回的是0)。
之後透過 map 指令,將
$limit
的值對映為
$limit_key
:在白名單內的,
$limit_key
為空字串,不在白名單內的,則為
$binary_remote_addr
。當
limit_req_zone
指令的第一個參數是一個空字串,限制不起作用,因此白名單的 IP 地址(在10.0.0.0/8和192.168.0.0/24子網路中)沒有被限制,其它 IP 地址都被限制為 2r/s
2.4、limit_req重復
如果同一個 location 下配置了多條
limit_req
的指令,這些指令所定義的限制都會被使用。
geo $limit {
default 1;
10.0.0.0/8 0;
192.168.0.0/24 0;
}
map $limit$limit_key {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=mylimit:10m rate=2r/s;
limit_req_zone $binary_remote_addr zone=myLimit2:10m rate=10r/s;
server {
location ~* \.(html)$ {
limit_req zone=mylimit burst=5 nodelay;
limit_req zone=myLimit2 burst=5 nodelay;
}
}
上面的例子配置了兩條規則,myLimit 和 myLimit2。白名單使用者雖然沒有匹配到mylimit的規則,但是根據規則 mylimit2,被限制為10r/s。對於不在白名單的使用者,則需要同時匹配mylimit 和 mylimit2,兩者中最嚴格的條件 2r/s 會起作用。
3 限制連線數
nginx 的
ngx_http_limit_conn_module
模組提供限制連線數的能力,包含兩個指令
limit_conn_zone
和
limit_conn
,格式為
limit_conn_zone key zone
。
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
location ~* \.(html)$ {
limit_conn perip 10;
limit_conn perserver 100;
}
}
limit_conn perip 10:
key 是
$binary_remote_addr
,表示限制單個IP同時最多能持有10個連線。
limit_conn perserver 100:
key 是
$server_name
,表示虛擬主機(server) 同時能處理並行連線的總數為100。
需要註意的是:只有當 request header 被後端server處理後,這個連線才進行計數。
4 上傳/下載速率限制
limit_rate
主要用於限制使用者和伺服器之間傳輸的字節數,最常用的場景可能就是下載/上傳限速。
limit_rate
並沒有單獨的一個模組,而是在
ngx_http_core_module
中,同時它的相關指令也比較少,只有
limit_rate
和
limit_rate_after
這兩個指令。
4.1、limit_rate
server {
location / {
limit_rate 4k;
}
}
limit_rate的用法非常簡單,後面跟隨的rate就是具體限速的閾值
註意預設的單位是bytes/s,也就是每秒傳輸的字節數Bytes而不是位元數bits
rate可以設定為變量,從而可以實作動態限速
限速指令的生效範圍是根據每個連線確定的,例如上面限定每個連線的速率為4k,也就是當客戶端發起兩個連線的時候,速率就可以變為8k
4.2、limit_rate_after
server {
location / {
limit_rate_after 500k;
limit_rate 4k;
}
}
limit_rate_after
允許在傳輸了一部份數據之後再進行限速,例如上面的配置中就是傳輸的前500k數據不限速,500k之後再進行限速。比較常見的套用場景如分段下載限速,超過指定大小的部份再進行限速;又或者是串流媒體視訊網站一般為了保證使用者體驗而不會對第一個畫面進行限速,確保其能夠盡快載入出來,等使用者開始觀看視訊之後,再把頻寬限制在合理的範圍內,從而降低因客戶端網速過快導致提前載入過多內容帶來的額外成本。
4.3、proxy_limit_rate
proxy_limit_rate
的基本原理和用法與
limit_rate
幾乎一樣,唯一不同的是
proxy_limit_rate
是限制nginx和後端upstream伺服器之間的連線速率而
limit_rate
限制的是nginx和客戶端之間的連線速率。需要註意的是
proxy_limit_rate
需要開啟了
proxy_buffering
這個指令才會生效。
#語法:
Syntax: proxy_limit_rate rate;
Default: proxy_limit_rate 0;
Context: http, server, location
This directive appeared in version 1.7.7.
4.4、動態限速
limit_rate
的一大特點就是能夠使用變量,這就意味著和map指令之類的進行組合就可以實作動態限速功能,這裏只列幾個簡單的示範
4.4.1、基於時間動態限速
這裏引入了nginx內建的一個ssi模組,這個模組有兩個比較有意思的時間變量:
$date_local
和
$date_gmt
,分別對應當前時間和GMT時間
這裏使用變量和map指令組合的方式,利用正規表式匹配不同的時間段,再結合map變量將不同時間段和不同的限速對應起來。
map $date_local$limit_rate_time {
default 4K;
~(00:|01:|02:|03:|04:|05:|06:|07:).*:.* 16K;
~(08:|12:|13:|18:).*:.* 8K;
~(19:|20:|21:|22:|23:).*:.* 16K;
}
limit_rate $limit_rate_time
4.2、基於變量動態限速
有些服務可能會對不用的使用者進行不同的限速,例如VIP使用者的速度要更快一些等,例如下面可以針對不同的cookie進行限速
map $cookie_User$limit_rate_cookie {
gold 64K;
silver 32K;
copper 16K;
iron 8K;
}
limit_rate $limit_rate_cookie
感謝閱讀,希望對你有所幫助 :) 來源:
blog.csdn.net/cold___play/article/details/132094865