連接:https://www.cnblogs.com/zjfja...
做者:雪山上的蒲公php
前幾天,之前一個老同事在微信上和我吐槽,一次周未休息,忽然收到公司服務器告警,有一臺服務器掛掉了,致使影響一部分公司業務的運行,過後查看發現原來是前端Nginx流控配置的不夠科學,不得不背上一鍋,影響了這個月的KPI考覈和當年中的加薪指標。html
可見這Nginx流控的配置仍是很重要,因此,本篇文章將會介紹Nginx的流量限制的基礎知識和高級配置,」流量限制」在Nginx Plus中也適用。前端
流量限制(rate-limiting),是Nginx中一個很是實用,卻常常被錯誤理解和錯誤配置的功能。咱們能夠用來限制用戶在給定時間內HTTP請求的數量。請求,能夠是一個簡單網站首頁的GET請求,也能夠是登陸表單的POST請求。java
流量限制能夠用做安全目的,好比能夠減慢暴力密碼破解的速率。經過將傳入請求的速率限制爲真實用戶的典型值,並標識目標URL地址(經過日誌),還能夠用來抵禦DDOS攻擊。更常見的狀況,該功能被用來保護上游應用服務器不被同時太多用戶請求所壓垮。node
Nginx的」流量限制」使用漏桶算法(leaky bucket algorithm),該算法在通信和分組交換計算機網絡中普遍使用,用以處理帶寬有限時的突發狀況。就比如,一個桶口在倒水,桶底在漏水的水桶。若是桶口倒水的速率大於桶底的漏水速率,桶裏面的水將會溢出;一樣,在請求處理方面,水錶明來自客戶端的請求,水桶表明根據」先進先出調度算法」(FIFO)等待被處理的請求隊列,桶底漏出的水錶明離開緩衝區被服務器處理的請求,桶口溢出的水錶明被丟棄和不被處理的請求。nginx
「流量限制」配置兩個主要的指令,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指令定義了流量限制相關的參數,而limit_req指令在出現的上下文中啓用流量限制(示例中,對於」/login/」的全部請求)。limit_req_zone指令一般在HTTP塊中定義,使其可在多個上下文中使用,它須要如下三個參數:安全
當Nginx須要添加新條目時存儲空間不足,將會刪除舊條目。若是釋放的空間仍不夠容納新記錄,Nginx將會返回 503狀態碼(Service Temporarily Unavailable)。另外,爲了防止內存被耗盡,Nginx每次建立新條目時,最多刪除兩條60秒內未使用的條目。
limit_req_zone指令設置流量限制和共享內存區域的參數,但實際上並不限制請求速率。因此須要經過添加limit_req指令,將流量限制應用在特定的location或者server塊。在上面示例中,咱們對/login/請求進行流量限制。服務器
如今每一個IP地址被限制爲每秒只能請求10次/login/,更準確地說,在前一個請求的100毫秒內不能請求該URL。微信
若是咱們在100毫秒內接收到2個請求,怎麼辦?對於第二個請求,Nginx將給客戶端返回狀態碼503。這可能並非咱們想要的結果,由於應用本質上趨向於突發性。相反地,咱們但願緩衝任何超額的請求,而後及時地處理它們。咱們更新下配置,在limit_req中使用burst參數:
location /login/ { limit_req zone=mylimit burst=20; proxy_pass http://my_upstream; }
burst參數定義了超出zone指定速率的狀況下(示例中的mylimit區域,速率限制在每秒10個請求,或每100毫秒一個請求),客戶端還能發起多少請求。上一個請求100毫秒內到達的請求將會被放入隊列,咱們將隊列大小設置爲20。
這意味着,若是從一個給定IP地址發送21個請求,Nginx會當即將第一個請求發送到上游服務器羣,而後將餘下20個請求放在隊列中。而後每100毫秒轉發一個排隊的請求,只有當傳入請求使隊列中排隊的請求數超過20時,Nginx纔會向客戶端返回503。
配置burst參數將會使通信更流暢,可是可能會不太實用,由於該配置會使站點看起來很慢。在上面的示例中,隊列中的第20個包須要等待2秒才能被轉發,此時返回給客戶端的響應可能再也不有用。要解決這個狀況,能夠在burst參數後添加nodelay參數:
location /login/ { limit_req zone=mylimit burst=20 nodelay; proxy_pass http://my_upstream; }
使用nodelay參數,Nginx仍將根據burst參數分配隊列中的位置,並應用已配置的速率限制,而不是清理隊列中等待轉發的請求。相反地,當一個請求到達「太早」時,只要在隊列中能分配位置,Nginx將當即轉發這個請求。將隊列中的該位置標記爲」taken」(佔據),而且不會被釋放以供另外一個請求使用,直到一段時間後纔會被釋放(在這個示例中是,100毫秒後)。
假設如前所述,隊列中有20個空位,從給定的IP地址發出的21個請求同時到達。Nginx會當即轉發這個21個請求,而且標記隊列中佔據的20個位置,而後每100毫秒釋放一個位置。若是是25個請求同時到達,Nginx將會當即轉發其中的21個請求,標記隊列中佔據的20個位置,而且返回503狀態碼來拒絕剩下的4個請求。
如今假設,第一組請求被轉發後101毫秒,另20個請求同時到達。隊列中只會有一個位置被釋放,因此Nginx轉發一個請求並返回503狀態碼來拒絕其餘19個請求。若是在20個新請求到達以前已通過去了501毫秒,5個位置被釋放,因此Nginx當即轉發5個請求並拒絕另外15個。
效果至關於每秒10個請求的「流量限制」。若是但願不限制兩個請求間容許間隔的狀況下實施「流量限制」,nodelay參數是很實用的。
注意:對於大部分部署,咱們建議使用burst和nodelay參數來配置limit_req指令。
經過將基本的「流量限制」與其餘Nginx功能配合使用,咱們能夠實現更細粒度的流量限制。
白名單
下面這個例子將展現,如何對任何不在白名單內的請求強制執行「流量限制」:
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地址對應的$limit變量分配一個值0,給其它不在白名單中的分配一個值1。而後咱們使用一個映射將這些值轉爲key,以下:
limit_req指令將限制應用到/的location塊,容許在配置的限制上最多超過10個數據包的突發,而且不會延遲轉發。
咱們能夠在一個location塊中配置多個limit_req指令。符合給定請求的全部限制都被應用時,意味着將採用最嚴格的那個限制。例如,多個指令都制定了延遲,將採用最長的那個延遲。一樣,請求受部分指令影響被拒絕,即便其餘指令容許經過也無濟於事。
擴展前面將「流量限制」應用到白名單內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; # ... } } }
白名單內的IP地址不會匹配到第一個「流量限制」,而是會匹配到第二個req_zone_wl,而且被限制到每秒15個請求。不在白名單內的IP地址兩個限制能匹配到,因此應用限制更強的那個:每秒5個請求。
日誌記錄 默認狀況下,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"
日誌條目中包含的字段:
默認狀況下,Nginx以error級別來記錄被拒絕的請求,如上面示例中的[error]所示(Ngin以較低級別記錄延時請求,通常是info級別)。如要更改Nginx的日誌記錄級別,須要使用limit_req_log_level指令。這裏,咱們將被拒絕請求的日誌記錄級別設置爲warn:
location /login/ { limit_req zone=mylimit burst=20 nodelay; limit_req_log_level warn; proxy_pass http://my_upstream; }
通常狀況下,客戶端超過配置的流量限制時,Nginx響應狀態碼爲503(Service Temporarily Unavailable)。可使用limit_req_status指令來設置爲其它狀態碼(例以下面的444狀態碼):
location /login/ { limit_req zone=mylimit burst=20 nodelay; limit_req_status 444; }
若是你想拒絕某個指定URL地址的全部請求,而不是僅僅對其限速,只須要在location塊中配置deny all指令:
location /foo.php { deny all; }
前文已經涵蓋了Nginx和Nginx Plus提供的「流量限制」的不少功能,包括爲HTTP請求的不一樣loation設置請求速率,給「流量限制」配置burst和nodelay參數。還涵蓋了針對客戶端IP地址的白名單和黑名單應用不一樣「流量限制」的高級配置,闡述瞭如何去日誌記錄被拒絕和延時的請求。