Nginx如何限流?
流量限制(rate-limiting),是Nginx中一個非常實(shí)用,卻經(jīng)常被錯誤理解和錯誤配置的功能。我們可以用來限制用戶在給定時間內(nèi)HTTP請求的數(shù)量。請求,可以是一個簡單網(wǎng)站首頁的GET請求,也可以是登錄表單的POST請求。
流量限制可以用作安全目的,比如可以減慢暴力密碼破解的速率。通過將傳入請求的速率限制為真實(shí)用戶的典型值,并標(biāo)識目標(biāo)URL地址(通過日志),還可以用來抵御DDOS攻擊。更常見的情況,該功能被用來保護(hù)上游應(yīng)用服務(wù)器不被同時太多用戶請求所壓垮。
本篇文章將會介紹Nginx的 流量限制 的基礎(chǔ)知識和高級配置,”流量限制”在Nginx Plus中也適用。
Nginx如何限流
Nginx的”流量限制”使用漏桶算法(leaky bucket algorithm),該算法在通訊和分組交換計(jì)算機(jī)網(wǎng)絡(luò)中廣泛使用,用以處理帶寬有限時的突發(fā)情況。就好比,一個桶口在倒水,桶底在漏水的水桶。如果桶口倒水的速率大于桶底的漏水速率,桶里面的水將會溢出;同樣,在請求處理方面,水代表來自客戶端的請求,水桶代表根據(jù)”先進(jìn)先出調(diào)度算法”(FIFO)等待被處理的請求隊(duì)列,桶底漏出的水代表離開緩沖區(qū)被服務(wù)器處理的請求,桶口溢出的水代表被丟棄和不被處理的請求。

配置基本的限流
“流量限制”配置兩個主要的指令,limit_req_zone和limit_req,如下所示:
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;server {location /login/ {limit_req zone=mylimit;proxy_pass http://my_upstream;}}
limit_req_zone指令定義了流量限制相關(guān)的參數(shù),而limit_req指令在出現(xiàn)的上下文中啟用流量限制(示例中,對于”/login/”的所有請求)。limit_req_zone指令通常在HTTP塊中定義,使其可在多個上下文中使用,它需要以下三個參數(shù):
Key - 定義應(yīng)用限制的請求特性。示例中的Nginx變量remote_addr,占用更少的空間) Zone - 定義用于存儲每個IP地址狀態(tài)以及被限制請求URL訪問頻率的共享內(nèi)存區(qū)域。保存在內(nèi)存共享區(qū)域的信息,意味著可以在Nginx的worker進(jìn)程之間共享。定義分為兩個部分:通過zone=keyword標(biāo)識區(qū)域的名字,以及冒號后面跟區(qū)域大小。16000個IP地址的狀態(tài)信息,大約需要1MB,所以示例中區(qū)域可以存儲160000個IP地址。 Rate - 定義最大請求速率。在示例中,速率不能超過每秒10個請求。Nginx實(shí)際上以毫秒的粒度來跟蹤請求,所以速率限制相當(dāng)于每100毫秒1個請求。因?yàn)椴辉试S”突發(fā)情況”(見下一章節(jié)),這意味著在前一個請求100毫秒內(nèi)到達(dá)的請求將被拒絕。
當(dāng)Nginx需要添加新條目時存儲空間不足,將會刪除舊條目。如果釋放的空間仍不夠容納新記錄,Nginx將會返回 503狀態(tài)碼(Service Temporarily Unavailable)。另外,為了防止內(nèi)存被耗盡,Nginx每次創(chuàng)建新條目時,最多刪除兩條60秒內(nèi)未使用的條目。
limit_req_zone指令設(shè)置流量限制和共享內(nèi)存區(qū)域的參數(shù),但實(shí)際上并不限制請求速率。所以需要通過添加limit_req指令,將流量限制應(yīng)用在特定的location或者server塊。在上面示例中,我們對/login/請求進(jìn)行流量限制。
現(xiàn)在每個IP地址被限制為每秒只能請求10次/login/,更準(zhǔn)確地說,在前一個請求的100毫秒內(nèi)不能請求該URL。
處理突發(fā)
如果我們在100毫秒內(nèi)接收到2個請求,怎么辦?對于第二個請求,Nginx將給客戶端返回狀態(tài)碼503。這可能并不是我們想要的結(jié)果,因?yàn)閼?yīng)用本質(zhì)上趨向于突發(fā)性。相反地,我們希望緩沖任何超額的請求,然后及時地處理它們。我們更新下配置,在limit_req中使用burst參數(shù):
location /login/ {limit_req zone=mylimit burst=20;proxy_pass http://my_upstream;}
burst參數(shù)定義了超出zone指定速率的情況下(示例中的mylimit區(qū)域,速率限制在每秒10個請求,或每100毫秒一個請求),客戶端還能發(fā)起多少請求。上一個請求100毫秒內(nèi)到達(dá)的請求將會被放入隊(duì)列,我們將隊(duì)列大小設(shè)置為20。
這意味著,如果從一個給定IP地址發(fā)送21個請求,Nginx會立即將第一個請求發(fā)送到上游服務(wù)器群,然后將余下20個請求放在隊(duì)列中。然后每100毫秒轉(zhuǎn)發(fā)一個排隊(duì)的請求,只有當(dāng)傳入請求使隊(duì)列中排隊(duì)的請求數(shù)超過20時,Nginx才會向客戶端返回503。
無延遲的排隊(duì)
配置burst參數(shù)將會使通訊更流暢,但是可能會不太實(shí)用,因?yàn)樵撆渲脮拐军c(diǎn)看起來很慢。在上面的示例中,隊(duì)列中的第20個包需要等待2秒才能被轉(zhuǎn)發(fā),此時返回給客戶端的響應(yīng)可能不再有用。要解決這個情況,可以在burst參數(shù)后添加nodelay參數(shù):
location /login/ {limit_req zone=mylimit burst=20 nodelay;proxy_pass http://my_upstream;}
使用nodelay參數(shù),Nginx仍將根據(jù)burst參數(shù)分配隊(duì)列中的位置,并應(yīng)用已配置的速率限制,而不是清理隊(duì)列中等待轉(zhuǎn)發(fā)的請求。相反地,當(dāng)一個請求到達(dá)“太早”時,只要在隊(duì)列中能分配位置,Nginx將立即轉(zhuǎn)發(fā)這個請求。將隊(duì)列中的該位置標(biāo)記為”taken”(占據(jù)),并且不會被釋放以供另一個請求使用,直到一段時間后才會被釋放(在這個示例中是,100毫秒后)。
假設(shè)如前所述,隊(duì)列中有20個空位,從給定的IP地址發(fā)出的21個請求同時到達(dá)。Nginx會立即轉(zhuǎn)發(fā)這個21個請求,并且標(biāo)記隊(duì)列中占據(jù)的20個位置,然后每100毫秒釋放一個位置。如果是25個請求同時到達(dá),Nginx將會立即轉(zhuǎn)發(fā)其中的21個請求,標(biāo)記隊(duì)列中占據(jù)的20個位置,并且返回503狀態(tài)碼來拒絕剩下的4個請求。
現(xiàn)在假設(shè),第一組請求被轉(zhuǎn)發(fā)后101毫秒,另20個請求同時到達(dá)。隊(duì)列中只會有一個位置被釋放,所以Nginx轉(zhuǎn)發(fā)一個請求并返回503狀態(tài)碼來拒絕其他19個請求。如果在20個新請求到達(dá)之前已經(jīng)過去了501毫秒,5個位置被釋放,所以Nginx立即轉(zhuǎn)發(fā)5個請求并拒絕另外15個。
效果相當(dāng)于每秒10個請求的“流量限制”。如果希望不限制兩個請求間允許間隔的情況下實(shí)施“流量限制”,nodelay參數(shù)是很實(shí)用的。
注意:對于大部分部署,我們建議使用burst和nodelay參數(shù)來配置limit_req指令。
高級配置示例
通過將基本的“流量限制”與其他Nginx功能配合使用,我們可以實(shí)現(xiàn)更細(xì)粒度的流量限制。
白名單
下面這個例子將展示,如何對任何不在白名單內(nèi)的請求強(qiáng)制執(zhí)行“流量限制”:
geo $limit {default 1;10.0.0.0/8 0;192.168.0.0/64 0;}map $limit $limit_key {0 "";1 $binary_remote_addr;}limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;server {location / {limit_req zone=req_zone burst=10 nodelay;# ...}}
這個例子同時使用了geo和map指令。geo塊將給在白名單中的IP地址對應(yīng)的$limit變量分配一個值0,給其它不在白名單中的分配一個值1。然后我們使用一個映射將這些值轉(zhuǎn)為key,如下:
如果limit_key變量將被賦值為空字符串 如果limit_key變量將被賦值為客戶端二進(jìn)制形式的IP地址 兩個指令配合使用,白名單內(nèi)IP地址的$limit_key變量被賦值為空字符串,不在白名單內(nèi)的被賦值為客戶端的IP地址。當(dāng)limit_req_zone后的第一個參數(shù)是空字符串時,不會應(yīng)用“流量限制”,所以白名單內(nèi)的IP地址(10.0.0.0/8和192.168.0.0/24 網(wǎng)段內(nèi))不會被限制。其它所有IP地址都會被限制到每秒5個請求。
limit_req指令將限制應(yīng)用到/的location塊,允許在配置的限制上最多超過10個數(shù)據(jù)包的突發(fā),并且不會延遲轉(zhuǎn)發(fā)。
location包含多l(xiāng)imit_req指令
我們可以在一個location塊中配置多個limit_req指令。符合給定請求的所有限制都被應(yīng)用時,意味著將采用最嚴(yán)格的那個限制。例如,多個指令都制定了延遲,將采用最長的那個延遲。同樣,請求受部分指令影響被拒絕,即使其他指令允許通過也無濟(jì)于事。
擴(kuò)展前面將“流量限制”應(yīng)用到白名單內(nèi)IP地址的例子:
http {# ...limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;limit_req_zone $binary_remote_addr zone=req_zone_wl:10m rate=15r/s;server {# ...location / {limit_req zone=req_zone burst=10 nodelay;limit_req zone=req_zone_wl burst=20 nodelay;# ...}}}
白名單內(nèi)的IP地址不會匹配到第一個“流量限制”,而是會匹配到第二個req_zone_wl,并且被限制到每秒15個請求。不在白名單內(nèi)的IP地址兩個限制能匹配到,所以應(yīng)用限制更強(qiáng)的那個:每秒5個請求。
配置相關(guān)功能
日志記錄 默認(rèn)情況下,Nginx會在日志中記錄由于流量限制而延遲或丟棄的請求,如下所示:
2015/06/13 04:20:00 [error] 120315#0: *32086 limiting requests, excess: 1.000 by zone "mylimit", client: 192.168.1.2, server: nginx.com, <br>request: "GET / HTTP/1.0", host: "nginx.com"日志條目中包含的字段:
limiting requests - 表明日志條目記錄的是被“流量限制”請求 excess - 每毫秒超過對應(yīng)“流量限制”配置的請求數(shù)量 zone - 定義實(shí)施“流量限制”的區(qū)域 client - 發(fā)起請求的客戶端IP地址 server - 服務(wù)器IP地址或主機(jī)名 request - 客戶端發(fā)起的實(shí)際HTTP請求 host - HTTP報頭中host的值
默認(rèn)情況下,Nginx以error級別來記錄被拒絕的請求,如上面示例中的[error]所示(Ngin以較低級別記錄延時請求,一般是info級別)。如要更改Nginx的日志記錄級別,需要使用limit_req_log_level指令。這里,我們將被拒絕請求的日志記錄級別設(shè)置為warn:
location /login/ {limit_req zone=mylimit burst=20 nodelay;limit_req_log_level warn;proxy_pass http://my_upstream;}
發(fā)送到客戶端的錯誤代碼
一般情況下,客戶端超過配置的流量限制時,Nginx響應(yīng)狀態(tài)碼為503(Service Temporarily Unavailable)??梢允褂胠imit_req_status指令來設(shè)置為其它狀態(tài)碼(例如下面的444狀態(tài)碼):
location /login/ {limit_req zone=mylimit burst=20 nodelay;limit_req_status 444;}
指定location拒絕所有請求
如果你想拒絕某個指定URL地址的所有請求,而不是僅僅對其限速,只需要在location塊中配置deny all指令:
location /foo.php {deny all;}
總結(jié)
前文已經(jīng)涵蓋了Nginx和Nginx Plus提供的“流量限制”的很多功能,包括為HTTP請求的不同loation設(shè)置請求速率,給“流量限制”配置burst和nodelay參數(shù)。還涵蓋了針對客戶端IP地址的白名單和黑名單應(yīng)用不同“流量限制”的高級配置,闡述了如何去日志記錄被拒絕和延時的請求。
原文地址:https://www.cnblogs.com/zjfjava/p/10947264.html
原文作者:雪山上的蒲公英
關(guān)注「開源Linux」加星標(biāo),提升IT技能

