1. 程式人生 > >Nginx 伺服器之速率限制

Nginx 伺服器之速率限制

Nginx伺服器有一個非常有用的限速功能,但是它卻常常被錯誤配置。

 

這個功能用來限制使用者在某此時間段內請求的的HTTP請求數,此請求應該是 GET 或POST 來發出的請求。

 

這個限速功能常常被應用於網路安全方面。比如減慢暴力密碼破解的攻擊,爬蟲對網頁的抓取,防止DDOS攻擊等。通過它來限制和過濾為為真實使用者的標準數值,它會把來源URL等資訊寫到系統日誌中。更確切地說,這個功能常用於提供極少量的應用伺服器,使用者訪問量不多,但卻常常癱瘓的問題。

 

在本文中,我們將詳細介紹Nginx的速率限制基礎及更高階的配置。值得一提的是,Nginx限速的工作原理與Nginx Plus相同。

Nginx限速是怎樣工作的

Nginx限速使用 Leaky(嗊水桶)演算法,比喻為水桶頂部倒水,底部漏水,如果倒入水的速率超過漏水的速度,則水桶漏出。在電信網路和分組交換網路中,頻寬有限的情況下該演算法使用場景較多。

 

 

就請求處理而言,水代表客戶端的請求,存水的桶按先進先出(FIFO)排程演算法處理的佇列。漏出的水錶示退出緩衝區等伺服器處理,而溢位表示請被丟棄且不再提供服務。

配置基本的速率限制

速率限制主要有2個主要指令,limit_req_zone和limit_req。如下程式碼:

 

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/sm;

server {

    location /login/ {

        limit_req zone=mylimit;

        proxy_pass http://my_upstream;

    }

}

limit_req_zone指令定義了速度限制的引數,同時在出現的上下文中啟用速率限制。(在本例中是針對於 /login/ URI的所有請求)

limit_requ_zone 指令通常定義在HTTP塊中,這樣可以用於多個上下文。它包含3個引數:

Key - 定義應用限制的請求特徵。 在這個例子中,它是Nginx變數$binary_remote_addr ,它儲存著客戶端IP地址的二進位制表示。 這意味著我們將每個唯一的IP地址限制為由第三個引數定義的請求速率(我們使用這個變數,因為它比客戶端IP地址的字串表示$remote_addr佔用更少的空間)。

Zone - 定義用於儲存每個IP地址狀態的共享記憶體區域以及訪問請求受限URL的頻率。 將資訊儲存在共享記憶體中意味著它可以在Nginx工作程序之間共享。 

定義有兩個部分: zone= keyword標識的區域名稱和冒號後面的大小。 大約16,000個IP地址的狀態資訊需要1兆位元組,所以我們的區域可以儲存大約160,000個地址。 如果Nginx需要新增一個新條目時,儲存空間將被耗盡,它將刪除最舊的條目。 

如果釋放的空間不足以容納新記錄,則Nginx返回狀態碼503(Temporarily Unavailable) 。 此外,為了防止記憶體耗盡,每當Nginx建立一個新條目時,最多可以刪除兩個在前60秒內沒有使用的條目。

Rate - 設定最大請求率。 在這個例子中,速率不能超過每秒10個請求。 Nginx實際上以毫秒粒度跟蹤請求,所以這個限制對應於每100毫秒1個請求。 由於我們不允許爆發,這意味著如果請求在前一個允許的時間之後小於100毫秒時被拒絕。

limit_req_zone指令為速率限制和共享記憶體區域設定引數,但實際上並不限制請求速率。 

因此,您需要通過在其中包含limit_req指令來將限制應用於特定location或server塊。 在這個例子中,我們是對/login/的URI速率限制請求。

因此,現在每個唯一的IP地址被限制,/login/每秒10個請求 - 或者更確切地說,在前一個100毫秒內不能請求該URL。

處理併發

如果我們在100毫秒內得到兩個請求會怎麼樣? 對於第二個請求,Nginx將狀態碼503返回給客戶端。 這可能不是我們想要的,因為應用程式本質上是突發性的。

相反,我們想要緩衝任何多餘的請求並及時提供服務。 這是我們使用burst引數limit_req ,在這個更新的配置:

location /login/ {

  limit_req zone=mylimit burst=20;

  proxy_pass http://my_upstream;

}

burst引數定義了客戶端可以超過區域指定的速率(使用我們的示例mylimit區域,速率限制為每秒10個請求,或每100毫秒1個)可以產生多少個請求。 

在前一個請求到達100毫秒後的請求被放入一個佇列中,這裡我們將佇列大小設定為20。

這意味著如果21個請求同時從一個給定的IP地址到達,Nginx立即將第一個請求轉發到上游伺服器組,並將剩下的20個放入佇列中。 然後,它每100毫秒轉發一個排隊的請求,並且只有當傳入的請求使排隊請求的數量超過20時才返回503給客戶端。

無延遲佇列

具有burst的配置會導致流量暢通,但不是很實用,因為它可能會使您的網站顯得很慢。 

在我們的例子中,佇列中的第20個數據包等待2秒鐘被轉發,此時對其的響應可能對客戶端不再有用。 要解決這種情況,請將nodelay引數與burst引數一起新增:

location /login/ { 

  limit_req zone=mylimit burst=20 nodelay; 

  proxy_pass http://my_upstream;

}

通過nodelay引數,Nginx仍然根據burst引數在佇列中分配時隙,並且強加配置的速率限制,但是不排除轉發排隊的請求。 相反,當請求到達“太快”時,Nginx會立即轉發,只要佇列中有一個可用的時隙。 它將該插槽標記為“已佔用”,並且不會將其釋放以供其他請求使用,直到經過適當的時間(在本例中為100毫秒之後)。

假設像以前一樣,20個時隙的佇列是空的,21個請求同時從給定的IP地址到達。 Nginx立即轉發所有21個請求,並將佇列中的20個插槽標記為已佔用,然後每100毫秒釋放1個插槽(如果有25個請求,Nginx會立即轉發21個插槽,標記20個插槽,拒絕4個請求狀態503 )。

現在假設在第一組請求之後101毫秒被轉發,另外20個請求同時到達。 佇列中只有1個插槽被釋放,所以Nginx轉發1個請求,並拒絕其他19個狀態為503的佇列。 如果在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/24 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地址分配一個0值到$limit值,其他0 1 。 然後,我們使用地圖將這些值轉換為一個金鑰,以便:

如果$limit是0,$limit_key設定為空字串。

如果$limit是1,則$limit_key以二進位制格式設定為客戶端的IP地址。

把兩者放在一起,$limit_key被設定為白名單IP地址的空字串,否則設定為客戶端的IP地址。 當limit_req_zone目錄(金鑰)的第一個引數為空字串時,限制不適用,因此列入白名單的IP地址(在10.0.0.0/8和192.168.0.0/24子網中)不受限制。 所有其他IP地址每秒限制為5個請求。

limit_req指令將限制應用於/位置,並且允許在配置的限制上突發多達10個分組而沒有轉發延遲

在一個位置包含多個limit_req指令

您可以在一個位置包含多個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 )不匹配,但匹配第二個( req_zone_wl ),因此每秒限制為15個請求。 

不在白名單上的IP地址與兩個速率限制相匹配,所以限制性較強的一個適用:每秒5個請求。

配置相關功能

記錄

預設情況下,Nginx 記錄由於速率限制而延遲或丟棄的請求,如下例所示:

 2017/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, request: "GET / HTTP/1.0", host: "Nginx.com"

日誌條目中的欄位包括:

limitingrequests - 指示日誌條目記錄速率限制。

excess - 此請求表示的配置速率每毫秒的請求數。

zone - 定義強加的限制的區域。

client - 發出請求的客戶client IP地址。

server - server IP地址或主機名。

request - 客戶端request實際HTTP請求。

host - Host HTTP頭的值。

預設情況下,Nginx在error級別記錄被拒絕的請求,如上例中的[error]所示(它記錄延遲的請求在一個較低的級別,所以預設info )。 要更改日誌級別,請使用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作為響應。 

使用limit_req_status指令來設定一個不同的狀態碼(在這個例子中是444 ):

location /login/ {

  limit_req zone=mylimit burst=20 nodelay;

  limit_req_status 444;

}

拒絕所有請求到特定的位置

如果您想要拒絕所有特定URL的請求,而不是限制它們,請為其配置一個塊幷包含all指令:

location /assets/header.php { 

  deny all;

}

小結

我們已經介紹了Nginx和Nginx Plus提供的更多速率限制的功能,包括為HTTP請求的不同位置設定請求速率,以及配置其他功能來限制速度,如burst和nodelay引數。我們還介紹了對客戶端IP地址配置不同的白名單和黑名單來匹配不同的高階配置,並介紹瞭如何記錄被拒絕和延遲的記錄。

加Java架構師群獲取Java工程化、高效能及分散式、高效能、深入淺出。高架構。效能調優、Spring,MyBatis,Netty原始碼分析和大資料等多個知識點高階進階乾貨的直播免費學習許可權 都是大牛帶飛 讓你少走很多的彎路的 群..號是:855801563 對了 小白勿進 最好是有開發經驗的哦~