Nginx 限流模塊

【轉載請註明出處】:juejin.im/post/5eacf0…html

生活中的 「限流」?

限流並不是新鮮事,在生活中亦無處不在,下面例舉一二:node

  • 博物館:限制天天參觀總人數以保護文物
  • 高鐵安檢:有若干安檢口,旅客依次排隊,工做人員根據安檢快慢決定是否放人進去。遇到節假日,能夠增長安檢口來提升處理能力(橫向拓展),同時增長排隊等待區長度(緩存待處理任務)。
  • 辦理銀行業務:全部人先領號,各窗口叫號處理。每一個窗口處理速度根據客戶具體業務而定,全部人排隊等待叫號便可。若快下班時,告知客戶明日再來(拒絕流量)。
  • 水壩泄洪:水壩能夠經過閘門控制泄洪速度(控制處理速度)。

在開發高併發系統時有三把利器用來保護系統:緩存、降級和限流nginx

  • 緩存:緩存的目的是提高系統訪問速度和增大系統處理容量
  • 降級:降級是當服務器壓力劇增的狀況下,根據當前業務狀況及流量對一些服務和頁面有策略的降級,以此釋放服務器資源以保證核心任務的正常運行
  • 限流:限流的目的是經過對併發訪問/請求進行限速,或者對一個時間窗口內的請求進行限速來保護系統,一旦達到限制速率則能夠拒絕服務、排隊或等待、降級等處理

兩大限流算法

經常使用的限流算法有令牌桶和和漏桶,而Google開源項目Guava中的RateLimiter使用的就是令牌桶控制算法。算法

漏桶算法

把請求比做是水,水來了都先放進桶裏,並以限定的速度出水,當水來得過猛而出水不夠快時就會致使水直接溢出,即拒絕服務。 後端

image.png
漏斗有一個進水口 和 一個出水口,出水口以必定速率出水,而且有一個最大出水速率:

在漏斗中沒有水的時候緩存

  • 若是進水速率小於等於最大出水速率,那麼,出水速率等於進水速率,此時,不會積水
  • 若是進水速率大於最大出水速率,那麼,漏斗以最大速率出水,此時,多餘的水會積在漏斗中

在漏斗中有水的時候安全

  • 出水口以最大速率出水
  • 若是漏斗未滿,且有進水的話,那麼這些水會積在漏斗中
  • 若是漏斗已滿,且有進水的話,那麼這些水會溢出到漏斗以外
令牌桶算法

對於不少應用場景來講,除了要求可以限制數據的平均傳輸速率外,還要求容許某種程度的突發傳輸。這時候漏桶算法可能就不合適了,令牌桶算法更爲適合。 bash

image.png

令牌桶算法的原理是系統以恆定的速率產生令牌,而後把令牌放到令牌桶中,令牌桶有一個容量,當令牌桶滿了的時候,再向其中放令牌,那麼多餘的令牌會被丟棄;當想要處理一個請求的時候,須要從令牌桶中取出一個令牌,若是此時令牌桶中沒有令牌,那麼則拒絕該請求。服務器

令牌桶算法VS漏桶算法

漏桶 漏桶的出水速度是恆定的,那麼意味着若是瞬時大流量的話,將有大部分請求被丟棄掉(也就是所謂的溢出)。併發

令牌桶 生成令牌的速度是恆定的,而請求去拿令牌是沒有速度限制的。這意味,面對瞬時大流量,該算法能夠在短期內請求拿到大量令牌,並且拿令牌的過程並非消耗很大的事情。

Nginx限流

Nginx官方版本限制IP的鏈接和併發分別有兩個模塊:

  • limit_req_zone 用來限制單位時間內的請求數,即速率限制,採用的漏桶算法 "leaky bucket"。
  • limit_req_conn 用來限制同一時間鏈接數,即併發限制。

ngx_http_limit_req_module 模塊

Nginx按請求速率限速模塊使用的是漏桶算法,即可以強行保證請求的實時處理速度不會超過設置的閾值。 指令

Syntax: limit_req zone=name [burst=number] [nodelay | delay=number]; Default: — Context: http, server, location

正常流量

limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

  • key :定義限流對象,binary_remote_addr 是一種key,表示基於 remote_addr(客戶端IP) 來作限流,binary_ 的目的是壓縮內存佔用量。
  • zone:定義共享內存區來存儲訪問信息, one:10m 表示一個大小爲10M,名字爲one的內存區域。1M能存儲16000 IP地址的訪問信息,10M能夠存儲16W IP地址訪問信息。
  • rate 用於設置最大訪問速率,rate=10r/s 表示每秒最多處理10個請求。Nginx 實際上以毫秒爲粒度來跟蹤請求信息,所以 10r/s 其實是限制:每100毫秒處理一個請求。這意味着,自上一個請求處理完後,若後續100毫秒內又有請求到達,將拒絕處理該請求。若是限制的頻率低於1r/s,則可使用r/m,如30r/m。
突發流量

limit_req zone=one burst=5 nodelay;

  • zone=one 設置使用哪一個配置區域來作限制,與上面limit_req_zone 裏的name對應。
  • burst=5,burst爆發的意思,這個配置的意思是設置一個大小爲5的緩衝區當有大量請求(爆發)過來時,超過了訪問頻次限制的請求能夠先放到這個緩衝區內。
  • nodelay,若是設置,超過訪問頻次並且緩衝區也滿了的時候就會直接返回503,若是沒有設置,則全部請求會等待排隊。
日誌級別

爲服務器因爲超過頻次或延遲處理而拒絕處理請求的狀況設置所需的日誌記錄級別。延遲的日誌記錄級別比拒絕的日誌記錄級別低1級;例如,「limit_req_log_level notice」 的延遲日誌記錄級別是info。

Syntax: limit_req_log_level info | notice | warn | error; Default: limit_req_log_level error; Context: http, server, location

拒絕響應狀態碼

Syntax: limit_req_status code; Default: limit_req_status 503; Context: http, server, location

案例
http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; 
    server {
        location /search/ {
            limit_req zone=one; 
        }
}       
複製代碼

限流速度爲每秒10次請求,若是有10次請求同時到達一個空閒的nginx,他們都能獲得執行嗎?

image.png

漏桶漏出請求是勻速的。10r/s是怎樣勻速的呢?每100ms漏出一個請求。在這樣的配置下,桶是空的,全部不能實時漏出的請求,都會被拒絕掉。因此若是10次請求同時到達,那麼只有一個請求可以獲得執行,其它的,都會被拒絕。 這不太友好,大部分業務場景下咱們但願這10個請求都能獲得執行,添加突發流量處理機制。

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; 
    server {
        location /search/ {
            limit_req zone=one burst=12; 
        }
}       
複製代碼

burst=12 漏桶的大小設置爲12。

image.png

邏輯上叫漏桶,實現起來是FIFO隊列,把得不到執行的請求暫時緩存起來。這樣漏出的速度仍然是100ms一個請求,但就併發而言,暫時得不到執行的請求,能夠先緩存起來。只有當隊列滿了的時候,纔會拒絕接受新請求。這樣漏桶在限流的同時,也起到了削峯填谷的做用。

在這樣的配置下,若是有10次請求同時到達,它們會依次執行,每100ms執行1個。雖然獲得執行了,但由於排隊執行,延遲大大增長,在不少場景下仍然是不能接受的。繼續修改配置,解決Delay過久致使延遲增長的問題。

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; 
    server {
        location /search/ {
            limit_req zone=one burst=12 nodelay;
        }
}       
複製代碼

nodelay 把開始執行請求的時間提早,之前是delay到從桶裏漏出來才執行,如今不delay了,只要入桶就開始執行。

image.png

要麼馬上執行,要麼被拒絕,請求不會由於限流而增長延遲了。由於請求從桶裏漏出來仍是勻速的(100ms釋放1個),桶的空間又是固定的,最終平均下來,仍是每秒執行了10次請求,限流的目的仍是達到了。 可是請注意,雖然設置burst和nodelay可以下降突發請求的處理時間,可是長期來看並不會提升吞吐量的上限,長期吞吐量的上限是由rate決定的,由於nodelay只能保證burst的請求被當即處理,但Nginx會限制隊列元素釋放的速度,就像是限制了令牌桶中令牌產生的速度。

但這樣也有缺點,限流是限了,可是限得不那麼勻速。以上面的配置舉例,若是有12個請求同時到達,那麼這12個請求都可以馬上執行,而後後面的請求只能勻速進桶,100ms執行1個。若是有一段時間沒有請求,桶空了,那麼又可能出現併發的12個請求一塊兒執行。 大部分狀況下,這種限流不勻速,不算是大問題。不過nginx也提供了一個參數控制併發執行也就是nodelay的請求的數量。

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; 
    server {
        location /search/ {
            limit_req zone=one burst=12 delay=4;
        }
}       
複製代碼

delay=4 從桶內第5個請求開始delay

image.png

這樣經過控制delay參數的值,能夠調整容許併發執行的請求的數量,使得請求變的均勻起來,在有些耗資源的服務上控制這個數量,仍是有必要的。

ngx_http_limit_conn_module 模塊

這個模塊用來限制單個IP的請求數。並不是全部的鏈接都被計數。只有在服務器處理了請求而且已經讀取了整個請求頭時,鏈接才被計數。

Syntax: limit_conn zone number; Default: — Context: http, server, location

如:

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) 同時能處理併發鏈接的總數。

須要注意的是:只有當 request header 被後端server處理後,這個鏈接才進行計數。

日誌級別

Syntax: limit_conn_log_level info | notice | warn | error; Default: limit_conn_log_level error; Context: http, server, location

拒絕響應狀態碼

Syntax: limit_conn_status code; Default: limit_conn_status 503; Context: http, server, location

設置白名單

限流主要針對外部訪問,內網訪問相對安全,能夠不作限流,經過設置白名單便可。利用 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;
        172.20.0.35 0;
    }
    map $limit $limit_key {
        0 "";
        1 $binary_remote_addr;
    }
    limit_req_zone $limit_key zone=myRateLimit:10m rate=10r/s;
複製代碼

geo 對於白名單(子網或IP均可以) 將返回0,其餘IP將返回1。

map 將 $limit 轉換爲 $limit_key,若是是 $limit 是0(白名單),則返回空字符串;若是是1,則返回客戶端實際IP。

limit_req_zone 限流的key再也不使用 $binary_remote_addr,而是 $limit_key 來動態獲取值。若是是白名單,limit_req_zone 的限流key則爲空字符串,將不會限流;若不是白名單,將會對客戶端真實IP進行限流。

限制數據傳輸速度

除限流外,ngx_http_core_module 還提供了限制數據傳輸速度的能力(即常說的下載速度)。

例如:

location /flv/ {
        flv;
        limit_rate_after 20m;
        limit_rate       100k;
    }
複製代碼

這個限制是針對每一個請求的,表示客戶端下載前20M時不限速,後續限制100kb/s。

限制特定UA

能夠限制特定UA(好比爬蟲)的訪問

limit_req_zone  $anti_spider  zone=one:10m   rate=10r/s;
limit_req zone=one burst=100 nodelay;
if ($http_user_agent ~* (YisouSpider|Scrapy)) {
    set $anti_spider $http_user_agent;
}
複製代碼

【轉載請註明出處】:juejin.im/post/5eacf0…

相關文章
相關標籤/搜索