作者:大魚燉海棠
鏈接:http://m.itdecent.cn/p/2c6e7852e732
系統(tǒng)設(shè)計時一般會預(yù)估負載,當(dāng)系統(tǒng)遭受惡意攻擊或正常突發(fā)流量等都可能導(dǎo)致系統(tǒng)被壓垮,而限流就是保護措施之一。
一、限流算法介紹
令牌桶算法

算法思想是:
- 令牌以固定速率產(chǎn)生,并緩存到令牌桶中;
- 令牌桶放滿時,多余的令牌被丟棄;
- 請求要消耗等比例的令牌才能被處理;
- 令牌不夠時,請求被緩存。
漏桶算法

算法思想是:
- 水(請求)從上方倒入水桶,從水桶下方流出(被處理);
- 來不及流出的水存在水桶中(緩沖),以固定速率流出;
- 水桶滿后水溢出(丟棄)。
這個算法的核心是:緩存請求、勻速處理、多余的請求直接丟棄。
漏桶和令牌桶算法最明顯的區(qū)別在于是否允許突發(fā)流量(burst)的處理,漏桶算法能夠強行限制數(shù)據(jù)的實時傳輸(處理)速率,對突發(fā)流量不做額外處理;而令牌桶算法能夠在限制數(shù)據(jù)的平均傳輸速率的同時允許某種程度的突發(fā)傳輸。
二、Nginx 限流
Nginx 提供兩種限流方式,一是控制速率,二是控制并發(fā)連接數(shù):
-
limit_req_zone模塊用來限制單位時間內(nèi)的請求數(shù),即速率限制,采用的漏桶算法 "leaky bucket"。 -
limit_req_conn模塊用來限制同一時間連接數(shù),即并發(fā)限制。
控制速率
ngx_http_limit_req_module 模塊提供限制請求處理速率能力,它能夠有效針對同一個 IP 反復(fù)請求服務(wù)器,如洪水攻擊或者 DDos 攻擊。下面例子使用 nginx 的 limit_req_zone 和 limit_req 兩個指令,限制單個IP的請求處理速率。
在 nginx.conf 的 http 域中添加限流配置:
# 格式:limit_req_zone key zone rate
http {
limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=10r/s;
}
# 配置 server,使用 limit_req 指令應(yīng)用限流。
server {
location / {
limit_req zone=myRateLimit;
proxy_pass http://my_upstream;
}
}
- $binary_remote_addr:定義限流對象,binary_remote_addr 是一種 key,表示基于 remote_addr(客戶端 IP) 來做限流,binary_ 的目的是壓縮內(nèi)存占用量。
- zone:定義共享內(nèi)存區(qū)來存儲訪問信息, myRateLimit:10m 表示一個大小為10M,名字為myRateLimit的內(nèi)存區(qū)域。1M 內(nèi)存能存儲16000個 IP 地址的訪問信息,10M 內(nèi)存可以存儲 16W個 IP地址訪問信息。
- rate:用于設(shè)置最大訪問速率,rate=10r/s 表示每秒最多處理10個請求。Nginx 實際上以毫秒為粒度來跟蹤請求信息,因此 10r/s 實際上是限制:每100毫秒處理一個請求。這意味著,自上一個請求處理完后,若后續(xù)100毫秒內(nèi)又有請求到達,將拒絕處理該請求。
-
zone=myRateLimit設(shè)置使用哪個配置區(qū)域來做限制,與 limit_req_zone 里的配置對應(yīng)
處理突發(fā)流量
上面例子限制 10r/s,正常流量稍微增大,請求就會被拒絕,面對突發(fā)流量,可以結(jié)合 burst 參數(shù)使用來解決該問題。
server {
location / {
limit_req zone=myRateLimit burst=20;
proxy_pass http://my_upstream;
}
}
burst 譯為突發(fā)、爆發(fā),表示在超過設(shè)定的處理速率后能額外處理的請求數(shù)。當(dāng) rate=10r/s 時,將1s拆成10份,即每100ms可處理1個請求。此處,burst=20,若同時有20個請求到達,Nginx 會處理第一個請求,剩余19個請求將放入隊列,然后每隔100ms從隊列中獲取一個請求進行處理。若請求數(shù)大于20,將拒絕處理多余的請求,直接返回 503
不過,單獨使用 burst 參數(shù)并不實用。假設(shè) burst=50,rate依然為10r/s,排隊中的50個請求雖然每100ms會處理一個,但第50個請求卻需要等待 50 * 100ms即 5s,這么長的處理時間自然難以接受。因此,burst 往往結(jié)合 nodelay 一起使用。
server {
location / {
limit_req zone=myRateLimit burst=20 nodelay;
proxy_pass http://my_upstream;
}
}
nodelay 針對的是 burst 參數(shù),burst=20 nodelay 表示這20個請求立馬處理,不能延遲,相當(dāng)于特事特辦。不過,即使這20個突發(fā)請求立馬處理結(jié)束,后續(xù)來了請求也不會立馬處理。burst=20 相當(dāng)于緩存隊列中占了20個坑,即使請求被處理了,這20個位置這只能按 100ms 一個來釋放。
這就達到了速率穩(wěn)定,但突然流量也能正常處理的效果。
限制連接數(shù)
ngx_http_limit_conn_module 提供了限制連接數(shù)的能力,利用 limit_conn_zone 和 limit_conn 兩個指令即可。下面是 Nginx 官方例子:
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 perip 10作用的 key 是 $binary_remote_addr,表示限制單個 IP 同時最多能持有10個連接。 -
limit_conn perserver 100作用的 key 是 $server_name,表示虛擬主機(server) 同時能處理并發(fā)連接的總數(shù)。
需要注意的是:只有當(dāng) request header 被后端 server 處理后,這個連接才進行計數(shù)。
設(shè)置白名單
限流主要針對外部訪問,內(nèi)網(wǎng)訪問相對安全,可以不做限流,通過設(shè)置白名單即可。利用 Nginx ngx_http_geo_module 和 ngx_http_map_module 兩個工具模塊即可搞定。
在 nginx.conf 的 http 部分中配置白名單:
geo $limit {
default 1; // key=default, value=1
10.0.0.0/8 0;
192.168.0.0/24 0;
172.20.0.35 0;
include conf/whiteip.conf; // 支持白名單以 key:value的形式存儲在配置文件中
}
map $limit $limit_key {
0 "";
1 $binary_remote_addr; // value=1則返回 $binary_remote_addr,也就是IP,否則返回空字符串
}
limit_req_zone $limit_key zone=myRateLimit:10m rate=10r/s;
- geo 定義了子網(wǎng)或 IP 與 0、1 的映射關(guān)系。上述配置中,10.0.0.0/8 網(wǎng)段的 IP 映射到 0,而其他 IP 缺省映射到 1;
- 來訪 IP 通過 geo 進行映射,映射結(jié)果是 0,表明該 IP 被列入白名單,經(jīng) map 轉(zhuǎn)換返回 "" 空字符串;如果映射結(jié)果是1,則返回 $binary_remote_addr,即客戶端實際 IP。
- limit_req_zone 限流采用的 key 不再是 binary_remote_addr,而是采用 $limit_key 動態(tài)獲取值。如果是白名單,limit_req_zone 的限流 key 則為空字符串,將不會限流;若不是白名單,將會對客戶端真實 IP 進行限流。