Nginx 限速使用的是漏桶算法,此算法圖示如下,一個(gè)桶有一定的容量,水從桶的上方流入,如果桶中有水,水就會(huì)從下方按照一定的速率流出。
當(dāng)然如果桶的容量已滿,流入的部分水就會(huì)溢出。如果桶沒(méi)有滿,水流入速度大于流出速度,那么桶的容量就會(huì)上升。
類比nginx環(huán)境,設(shè)置限速是1秒100個(gè)請(qǐng)求。Nginx時(shí)間粒度是毫秒,也就是10ms允許通過(guò)1個(gè)請(qǐng)求。那么可以認(rèn)為桶的容量(10ms)是1。如果10ms到達(dá)2個(gè)請(qǐng)求,那么1個(gè)請(qǐng)求可以通過(guò),1個(gè)會(huì)被拒絕。如果10毫秒到達(dá)1個(gè)請(qǐng)求,那么都可以順利的通過(guò)。這是非常簡(jiǎn)單的場(chǎng)景,實(shí)際上漏桶算法也會(huì)考慮burst突發(fā)的場(chǎng)景。
漏桶算法桶的容量需要包括rate和burst部分。rate表示流出速率,burst表示突發(fā)情況下,允許的最大隊(duì)列。
漏桶算法算法常被用作(Traffic Shaping)或速率限制(Rate Limiting)。

如何配置最基本的限速?
配置Nginx 配置限速主要需要兩個(gè)指令limit_req_zone 用來(lái)配置限速,limit_req用來(lái)使用限速

limit_req_zone一般被配置在http作用域,需要三個(gè)參數(shù):
Key 用來(lái)設(shè)置一次請(qǐng)求的key,比如上圖,設(shè)置的是用戶二進(jìn)制IP。
Zone 設(shè)置一個(gè)共享的區(qū)域。用來(lái)保存每個(gè)特定的IP狀態(tài),以及此IP訪問(wèn)被限速的url頻次等。Zone等號(hào)后面是共享區(qū)域的名字。名字后面的冒號(hào)表示共享區(qū)域的大小。1M空間可以保存16000個(gè)IP信息,本例子可以大約保存1.6W個(gè)IP信息。
如果此空間被耗盡,nginx嘗試去移除老的記錄,但是如果空間仍然不夠用的話,NGINX會(huì)返回503 (Service Temporarily Unavailable)。
Rate 設(shè)置允許的最大請(qǐng)求速率。再次強(qiáng)調(diào)Nginx時(shí)間粒度是毫秒,對(duì)于本例子也就是100毫秒,只允許通過(guò)一個(gè)請(qǐng)求。
Nginx如何處理突發(fā)?
如果我們?cè)?00毫秒內(nèi)收到兩個(gè)請(qǐng)求呢?對(duì)于第二個(gè)請(qǐng)求,NGINX將狀態(tài)碼503返回給客戶端。這可能不是我們想要的,因?yàn)閼?yīng)用程序在本質(zhì)上往往是突發(fā)的。,我們更希望能緩沖多余的請(qǐng)求,盡可能及時(shí)處理。NGINX可以通過(guò)limit_req指令的burst參數(shù)覆蓋這種場(chǎng)景:

burst參數(shù)設(shè)置,如果請(qǐng)求超過(guò)限速設(shè)置后,系統(tǒng)仍然可以接受多少請(qǐng)求,而不是直接拒絕。如上圖,桶的容量相當(dāng)于21.如果100ms之內(nèi)一個(gè)特定的IP地址,到來(lái)21個(gè)請(qǐng)求。Nginx會(huì)立即將一個(gè)請(qǐng)求送到upstream處理。剩下的20個(gè)請(qǐng)求就會(huì)入burst隊(duì)列,此隊(duì)列按照先進(jìn)先出的方式消費(fèi)。緊接著100ms,nginx直接從隊(duì)列的取出一個(gè)請(qǐng)求,進(jìn)行消費(fèi)。如果請(qǐng)求超過(guò)了burst隊(duì)列容量,請(qǐng)求依然會(huì)被拒絕。
Nginx 處理觸發(fā) NoDelay。
limit_req配置burst參數(shù)后,一定程度上支持突發(fā)的流量。但是根據(jù)上面的介紹大家也看到了,其實(shí)消費(fèi)速度還是100ms一個(gè),只是先進(jìn)了一個(gè)burst隊(duì)列。
另一種場(chǎng)景,假設(shè)被入隊(duì)的20個(gè)請(qǐng)求,第20個(gè)被處理的時(shí)候已經(jīng)是2s之后了。而這20個(gè)請(qǐng)求是有關(guān)聯(lián)的。那么第20個(gè)請(qǐng)求返回給用戶的時(shí)候,可能用戶已經(jīng)不在需要了。
針對(duì)這種場(chǎng)景nginx提供了一個(gè)額外參數(shù)nodelay

使用nodelay參數(shù),當(dāng)一個(gè)burst請(qǐng)求到達(dá)時(shí),只要隊(duì)列中有slot,nginx會(huì)立即轉(zhuǎn)發(fā),但是它將這個(gè)slot標(biāo)記為“已占用”,直到適當(dāng)?shù)臅r(shí)間過(guò)去(在我們的示例中,在100毫秒之后)才釋放它供另一個(gè)請(qǐng)求使用。
假設(shè)隊(duì)列20 slot是空的,并且有21個(gè)請(qǐng)求同時(shí)從給定的IP地址到達(dá)。NGINX立即轉(zhuǎn)發(fā)所有21個(gè)請(qǐng)求,并標(biāo)記隊(duì)列中的20個(gè)slot,然后每100毫秒釋放一個(gè)slot。(如果有25個(gè)請(qǐng)求,NGINX會(huì)立即轉(zhuǎn)發(fā)其中21個(gè),標(biāo)記20個(gè)slot,拒絕剩下的4個(gè)請(qǐng)求)。
現(xiàn)在假設(shè)新的100ms內(nèi),又有20個(gè)請(qǐng)求同時(shí)到達(dá)。但是隊(duì)列中只有一個(gè)slot被釋放,因此NGINX轉(zhuǎn)發(fā)一個(gè)請(qǐng)求,并拒絕其他19個(gè)請(qǐng)求。
我們可以看到加了nodelay參數(shù)后,在突發(fā)的情況下QPS會(huì)短暫超過(guò)設(shè)置的rate。但是從平均水平看,依舊是1秒10個(gè)。
注意:對(duì)于大多數(shù)部署,nginx建議在limit_req指令中包含burst和nodelay參數(shù)。
如何改變限速返回值?
默認(rèn)情況下,當(dāng)客戶端超過(guò)其速率限制時(shí),NGINX返回503(Service Temporarily Unavailable)。使用limit_req_status指令設(shè)置不同的狀態(tài)碼(本例中為444):

如何調(diào)整超限日志級(jí)別?
超限后的日志默認(rèn)是error,用戶可以通過(guò)limit_req_log_level
,指令調(diào)整日志級(jí)別。

參考
https://blog.csdn.net/tjcyjd/article/details/77916146
https://www.nginx.com/blog/rate-limiting-nginx/
https://www.cnblogs.com/SUNSHINEC/p/9577682.html