前言
大家好,我是老馬。很高興遇到你。
我們為 java 開發者實現了 java 版本的 nginx
https://github.com/houbb/nginx4j
如果你想知道 servlet 如何處理的,可以參考我的另一個專案:
手寫從零實現簡易版 tomcat minicat
手寫 nginx 系列
如果你對 netty 不是很熟悉,可以讀一下
從零手寫實現 nginx-01-為什麼不能有 java 版本的 nginx?
從零手寫實現 nginx-02-nginx 的核心能力
從零手寫實現 nginx-03-nginx 基於 Netty 實現
從零手寫實現 nginx-04-基於 netty http 出入參最佳化處理
從零手寫實現 nginx-05-MIME型別(Multipurpose Internet Mail Extensions,多用途網際網路郵件擴充套件型別)
從零手寫實現 nginx-06-資料夾自動索引
從零手寫實現 nginx-07-大檔案下載
從零手寫實現 nginx-08-範圍查詢
從零手寫實現 nginx-09-檔案壓縮
限制訪問代理的 HTTP 資源
透過限制連線、請求速率或頻寬,基於客戶端 IP 地址或其他變數,來保護您的上游 Web 和應用伺服器。
本文解釋瞭如何設定連線的最大請求數,或者從伺服器下載內容的最大速率。
簡介
使用 NGINX 和 NGINX Plus,可以限制:
- 每個鍵值(例如,每個 IP 地址)的連線數
- 每個鍵值(每秒或每分鐘允許處理的請求數)
- 連線的下載速度
請注意,IP 地址可以在 NAT 裝置後共享,因此應謹慎使用按 IP 地址限制。
限制連線數
要限制連線數:
- 使用
limit_conn_zone
指令定義鍵並設定共享記憶體區域的引數(工作程序將使用此區域來共享鍵值的計數器)。在
第一個引數中,指定為鍵計算的表示式。在第二個引數 zone
中,指定區域的名稱和大小:
limit_conn_zone $binary_remote_addr zone=addr:10m;
-
使用
limit_conn
指令在location {}
、server {}
或http {}
上下文中應用限制。將共享記憶體區域的名稱作為第一個引數,並將每個鍵允許的連線數作為第二個引數:location /download/ { limit_conn addr 1; }
連線數基於 IP 地址進行限制,因為使用了 $binary_remote_addr
變數作為鍵。
另一種限制給定伺服器的連線數的方法是使用 $server_name
變數:
http {
limit_conn_zone $server_name zone=servers:10m;
server {
limit_conn servers 1000;
}
}
限制請求速率
速率限制可用於防止 DDoS 攻擊,或防止上游伺服器同時收到過多請求而被淹沒。該方法基於漏桶演算法:請求以不同的速率到達桶中,並以固定的速率離開桶。
在使用速率限制之前,您需要配置“漏桶”的全域性引數:
- 鍵(key):用於區分一個客戶端和另一個客戶端的引數,通常是一個變數。
- 共享記憶體區域(shared memory zone):儲存這些鍵的狀態(“漏桶”)的區域的名稱和大小。
- 速率(rate):以每秒請求數(r/s)或每分鐘請求數(r/m)指定的請求速率限制(“漏桶排空”)。每分鐘請求用於指定少於每秒一個請求的速率。
這些引數是使用 limit_req_zone
指令設定的。該指令在 http {}
級別定義 - 這種方法允許將不同的區域和請求溢位引數應用於不同的上下文:
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
}
透過這種配置,將建立一個名為 one
、大小為 10 兆位元組的共享記憶體區域。該區域儲存了使用 $binary_remote_addr
變數設定的客戶端 IP 地址的狀態。請注意,與 $remote_addr
相比,後者也儲存客戶端的 IP 地址,而 $binary_remote_addr
儲存的是 IP 地址的二進位制表示,長度更短。
共享記憶體區域的最佳大小可以使用以下資料計算:IPv4 地址的 $binary_remote_addr
值大小為 4 位元組,在 64 位平臺上,儲存狀態佔據 128 位元組。因此,大約 16,000 個 IP 地址的狀態資訊佔用 1 兆位元組的區域。
如果當 NGINX 需要新增新條目時儲存空間已經耗盡,則會刪除最舊的條目。如果釋放的空間仍然不足以容納新記錄,則 NGINX 返回狀態碼 503 Service Unavailable。您可以使用 limit_req_status
指令重新定義狀態碼。
設定了區域之後,您可以在 NGINX 配置的任何位置使用請求限制,使用 limit_req
在 server {}
、location {}
或 http {}
上下文中指定:
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
#...
location /search/ {
limit_req zone=one;
}
}
}
透過這種配置,NGINX 在 /search/
位置內每秒最多處理 1 個請求。這些請求的處理會以這樣的方式延遲,以確保總速率不超過指定值。如果請求的數量超過指定的速率,NGINX 將延遲處理這些請求,直到“桶”(共享記憶體區域 one
)已滿。
對於到達滿桶的請求,NGINX 將使用 503 Service Unavailable 錯誤進行響應(如果沒有使用 limit_req_status
重新定義)。
測試請求速率限制
在配置實際的請求速率限制之前,您可以嘗試“幹執行”模式,該模式不會限制請求處理速率。但是,這些過多的請求仍然會計入共享記憶體區域並進行記錄。您可以使用 limit_req_dry_run
指令啟用“幹執行”模式:
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
#...
location /search/ {
limit_req zone=one;
limit_req_dry_run on;
}
}
}
每個超出定義的速率限制的請求都將帶有“幹執行”標記進行記錄:
2019/09/03 10:28:45 [error] 142#142: *13246 limiting requests, dry run, excess: 1.000 by zone "one", client: 172.19.0.1, server: www.example.com, request: "GET / HTTP/1.0", host: "www.example.com:80"
處理過多的請求 Handling Excessive Requests
請求被限制以符合 limit_req_zone
指令中定義的速率。
如果請求的數量超過了指定的速率,並且共享記憶體區域變滿,NGINX 將以錯誤響應。
由於流量往往是突發性的,返回錯誤以響應流量突發期間的客戶端請求並非最佳方案。
在 NGINX 中,這些過多的請求可以進行緩衝和處理。limit_req
指令的 burst
引數設定了等待以指定速率處理的過多請求的最大數量:
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
#...
location /search/ {
limit_req zone=one burst=5;
}
}
}
透過這種配置,如果請求速率超過每秒 1 個請求,超出速率的請求將放入區域 one
。當區域滿時,過多的請求將被排隊(burst),此佇列的大小為 5 個請求。佇列中的請求處理會延遲,以確保總速率不超過指定值。超出突發限制的請求將用 503 錯誤拒絕。
如果不希望在流量突發期間延遲請求,可以新增 nodelay
引數:
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
#...
location /search/ {
limit_req zone=one burst=5 nodelay;
}
}
}
透過這種配置,突發限制內的過多請求將立即服務,而不考慮指定的速率,超出突發限制的請求將用 503 錯誤拒絕。
延遲過多的請求 Delaying Excessive Requests
處理過多請求的另一種方法是在一定數量的請求中提供無延遲服務,然後在超出此數量後應用速率限制,直到拒絕過多的請求。
可以使用 delay
和 burst
引數來實現這一點。delay
引數定義了超出請求被延遲以符合定義的速率限制的點:
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
#...
location /search/ {
limit_req zone=one burst=5 delay=3;
}
}
}
透過這種配置,前 3 個請求(delay
)將無延遲透過,接下來的 2 個請求(burst - delay
)將以延遲的方式進行處理,以確保總速率不超過指定的值,進一步過多的請求將被拒絕,因為已超出了總突發大小,後續請求將被延遲處理。
同步多個共享記憶體區域的內容 Synchronizing Contents of Many Shared Memory Zones
如果您有一個帶有多個 NGINX 例項的計算機叢集,並且這些例項使用了 limit_req
方法,則可以在以下條件下同步它們的共享記憶體區域的內容:
- 每個例項都配置了
zone_sync
功能 - 每個例項的
limit_req_zone
指令設定的共享記憶體區域具有相同的名稱 - 每個例項的
limit_req_zone
指令指定了sync
引數:
http {
#...
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s sync;
}
有關詳細資訊,請參閱叢集中的執行時狀態共享。
限制頻寬
要限制每個連線的頻寬,可以使用 limit_rate
指令:
location /download/ {
limit_rate 50k;
}
透過這個設定,客戶端將能夠透過單個連線以最大速度下載 50 千位元組/秒的內容。但是,客戶端可以開啟多個連線。因此,如果目標是防止下載速度超過指定值,還應該限制連線的數量。例如,每個 IP 地址一個連線(如果使用上面指定的共享記憶體區域):
location /download/ {
limit_conn addr 1;
limit_rate 50k;
}
要在客戶端下載一定數量的資料後才施加限制,可以使用 limit_rate_after
指令。允許客戶端快速下載一定數量的資料(例如,檔案頭 - 影片索引),然後限制下載其餘資料的速率可能是合理的(讓使用者觀看電影,而不是下載)。
limit_rate_after 500k;
limit_rate 20k;
下面的示例展示了限制連線數和頻寬的組合配置。允許的最大連線數設定為每個客戶端地址 5 個連線,這適用於大多數常見情況,因為現代瀏覽器通常同時開啟最多 3 個連線。同時,用於提供下載的位置只允許一個連線:
http {
limit_conn_zone $binary_remote_address zone=addr:10m
server {
root /www/data;
limit_conn addr 5;
location / {
}
location /download/ {
limit_conn addr 1;
limit_rate_after 1m;
limit_rate 50k;
}
}
}
動態頻寬控制 Dynamic Bandwidth Control
limit_rate
值也可以指定為變數 - 這可以實現動態頻寬使用案例,例如,允許現代瀏覽器有更高的頻寬限制:
map $ssl_protocol $response_rate {
"TLSv1.1" 10k;
"TLSv1.2" 100k;
"TLSv1.3" 1000k;
}
server {
listen 443 ssl;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
location / {
limit_rate $response_rate; # 根據 TLS 版本限制頻寬
limit_rate_after 512; # 傳送頭部後應用限制
proxy_pass http://my_backend;
}
}
參見
使用NGINX和NGINX Plus進行速率限制
參考資料
https://docs.nginx.com/nginx/admin-guide/security-controls/controlling-access-proxied-http/