一、nginx限速
在生產環境中,為了保護WEB服務器的安全,我們都會對用戶的訪問做出一些限制,保證服務器的安全及資源的合理分配。
限流(rate limiting)是NGINX眾多特性中最有用的,也是經常容易被誤解和錯誤配置的,特性之一訪問請求限速。該特性可以限制某個用戶在一個給定時間段內能夠產生的HTTP請求數。請求可以簡單到就是一個對於主頁的GET請求或者一個登陸表格的POST請求。用於安全目的上,比如減慢暴力密碼破解攻擊。通過限制進來的請求速率,並且(結合日志)標記出目標URLs來幫助防范DDoS攻擊。一般地說,限流是用在保護上游應用服務器不被在同一時刻的大量用戶請求湮沒
限速說的很籠統,其實限速分為很多種限速方法:
1)下載速度限速
2)單位時間內請求數限制
3)基於客戶端的並發連接限速
nginx限速模塊
Nginx官方版本限制IP的連接和並發分別有兩個模塊:
limit_req_zone 用來限制單位時間內的請求數,即速率限制,采用的漏桶算法 "leaky bucket"。
limit_req_conn 用來限制同一時間連接數,即並發限制。
二、應用場景
下載限速:限制現在速度及並發連接數,應用在下載服務器中,保護帶寬及服務器的IO資源。
請求限速:限制單位時間內用戶訪問請求,防止惡意攻擊,保護服務器及資源安全。
三、限速原理
網絡傳輸中常用兩個的流量控制算法:漏桶算法和令牌桶算法。Nginx按請求速率限速模塊使用的是漏桶算法
漏桶算法(leaky bucket)
漏桶算法(leaky bucket)算法思想如圖所示:
一個形象的解釋是:
-
水(請求)從上方倒入水桶,從水桶下方流出(被處理);
-
來不及流出的水存在水桶中(緩沖),以固定速率流出;
-
水桶滿后水溢出(丟棄)。
這個算法的核心是:緩存請求、勻速處理、多余的請求直接丟棄。
令牌桶算法(token bucket)
算法思想是:
-
令牌以固定速率產生,並緩存到令牌桶中;
-
令牌桶放滿時,多余的令牌被丟棄;
-
請求要消耗等比例的令牌才能被處理;
-
令牌不夠時,請求被緩存。
相比漏桶算法,令牌桶算法不同之處在於它不但有一只“桶”,還有個隊列,這個桶是用來存放令牌的,隊列才是用來存放請求的。
從作用上來說,漏桶和令牌桶算法最明顯的區別就是是否允許突發流量(burst)的處理,漏桶算法能夠強行限制數據的實時傳輸(處理)速率,對突發流量不做額外處理;而令牌桶算法能夠在限制數據的平均傳輸速率的同時允許某種程度的突發傳輸。
Nginx按請求速率限速模塊使用的是漏桶算法,即能夠強行保證請求的實時處理速度不會超過設置的閾值。
四、限速實現
nginx限速可以通過 ngx_http_limit_conn_module 和 ngx_http_limit_req_module 模塊來實現限速的功能。
(一)限制並發數ngx_http_limit_conn_module
這個模塊可以設置每個定義的變量(比如客戶端ip)的並發連接數,比如:某個客戶端ip在同一時間內的連接數不能超過某個值。
語法:
定義限制鏈接區域
(nginx 1.18以后用 limit_conn_zone 取代了 limit_conn)
Syntax: limit_conn_zone key zone=name:size; Default: — Context: http
設置連接數限制
Syntax: limit_conn zone number; Default: — Context: http, server, location
示例:
http { ... # 根據客戶端ip進行限制,區域名稱為perip,總容量為10m limit_conn_zone $binary_remote_addr zone=perip:10m; limit_conn_zone $server_name zone=perserver:10m; ... server { ... ... # 使用perip區域名稱(zone name),同一時間並發數不得超過10 limit_conn perip 10; limit_conn perserver 100; ... } }
分析:
limit_conn perip 10 定義針對perip這個zone,並發連接為10個。在這需要注意一下,這個10指的是單個IP的並發最多為10個。
(二)速度限制limit_rate、limit_rate_after
limit_rate語法
Syntax: limit_rate rate; Default: limit_rate 0; Context: http, server, location, if in location
limit_rate_after語法,在下載完成x文件大小時開始限速
Syntax: limit_rate_after size; Default: limit_rate_after 0; Context: http, server, location, if in location This directive appeared in version 0.8.0.
示例
http { ... # 根據客戶端ip進行限制,區域名稱為perip,總容量為10m limit_conn_zone $binary_remote_addr zone=perip:10m; limit_conn_zone $server_name zone=perserver:10m; ... server { ... limit_rate_after 512k; limit_rate 150k; ... } }
分析:說明:limit_rate_after定義當一個文件下載到指定大小(本例中為512k)之后開始限速; limit_rate 定義下載速度為150k/s。
注意:這兩個參數針對每個請求限速,意思表示每個連接的傳輸速度不能超過 150k。
(三)ngx_http_limit_req_module
1、limit_req_zone、limit_req:
limit_req_zone語法
Syntax: limit_req_zone key zone=name:size rate=rate [sync]; Default: — Context: http
示例
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
分析:在 http
配置段中定義限制的參照標准和狀態緩存區大小。比如上面的配置就是定義了使用客戶端的 IP 作為參照依據,並使用一個 10M 大小的狀態緩存區。結尾的 rate=1r/s
表示針對每個 IP 的請求每秒只接受一次。NAME 是自定義的緩存區名稱,可以隨意命名,比如 per_ip
或 one
等。在 server
配置段中會用到。10M 的狀態緩存空間夠不夠用呢?官方給出的答案是 1M 的緩存空間可以在 32 位的系統中服務 3.2 萬 IP 地址,在 64 位的系統中可以服務 1.6 萬 IP 地址,所以需要自己看情況調整。如果狀態緩存耗光,后面所有的請求都會收到 503(Service Temporarily Unavailable) 錯誤。
limit_req語法
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; server { location /search/ { limit_req zone=one burst=5; }
分析:
第一段配置
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
第一個參數:$binary_remote_addr 表示通過remote_addr這個標識來做限制,“binary_”的目的是縮寫內存占用量,是限制同一客戶端ip地址 第二個參數:zone=one:10m表示生成一個大小為10M,名字為one的內存區域,用來存儲訪問的頻次信息 第三個參數:rate=1r/s表示允許相同標識的客戶端的訪問頻次,這里限制的是每秒1次,還可以有比如30r/m的
第二段配置
limit_req zone=one burst=5;
第一個參數:zone=one 設置使用哪個配置區域來做限制,與上面limit_req_zone 里的name對應 第二個參數:burst=5,重點說明一下這個配置,burst爆發的意思,如果單個 IP 有每秒有超過 1 個請求限制的時候,設置一個大小為5的緩沖區當有大量請求(爆發)過來時,5是最多接受5個超出的配額。超過了訪問頻次限制的請求可以先放到這個緩沖區內 第三個參數:nodelay,如果設置,超過訪問頻次而且緩沖區也滿了的時候就會直接返回503,如果沒有設置,則所有請求會等待排隊
限制平均每秒不超過一個請求,同時允許超過頻率限制的請求數不多於5個。
如果不希望超過的請求被延遲,可以用nodelay參數,如:
limit_req zone=one burst=5 nodelay;
應用示例
示例1
需求:基於IP對下載速率做限制, 限制每秒處理1次請求,對突發超過5個以后的請求放入緩存區,
過程:
在 /usr/local/nginx/html
目錄下創建目錄,寫入index.html文件
[root@localhost nginx]# mkdir html/abc [root@localhost nginx]# ls html/ 50x.html abc index.html [root@localhost nginx]# echo hello word! >html/abc/index.html [root@localhost nginx]# cat html/abc/index.html hello word! [root@localhost nginx]#
配置虛擬主機
修改 /usr/local/nginx
目錄下的 nginx.conf 配置文件:
... http{ ... limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; server { listen 80; location /abc { limit_req zone=one burst=5 nodelay; } } }
重啟nginx,測試效果
[root@localhost nginx]# killall -s HUP nginx [root@localhost nginx]# elinks http://192.168.199.228/abc --dump hello word! [root@localhost nginx]# elinks http://192.168.199.228/abc --dump hello word! [root@localhost nginx]# elinks http://192.168.199.228/abc --dump hello word! [root@localhost nginx]# elinks http://192.168.199.228/abc --dump hello word! [root@localhost nginx]# elinks http://192.168.199.228/abc --dump hello word! [root@localhost nginx]# elinks http://192.168.199.228/abc --dump 503 Service Temporarily Unavailable -------------------------------------------------------------------------- nginx/1.15.5 [root@localhost nginx]#
示例2
需求:基於IP做連接限制, 限制同一IP並發為1 下載速度為限制為100K
過程:
VM1 的ip192.168.199.228
在 /usr/local/nginx/html
目錄下創建目錄,生成300m大文件做測試用
[root@localhost nginx]# ls html/abc index.html [root@localhost nginx]# dd if=/dev/zero of=html/abc/bigfile bs=1M count=300 300+0 records in 300+0 records out 314572800 bytes (315 MB) copied, 3.21417 s, 97.9 MB/s [root@localhost nginx]# ls html/abc bigfile index.html
未做任何限制的情況下,在另外一台vm2192.168.199.229下載上面生成的大文件
[root@localhost ~]# cd /tmp [root@localhost tmp]# ls ks-script-LAqqiN vmware-root yum.log [root@localhost tmp]# wget http://192.168.199.228/abc/bigfile --2019-10-16 17:28:57-- http://192.168.199.228/abc/bigfile 正在連接 192.168.199.228:80... 已連接。 已發出 HTTP 請求,正在等待回應... 200 OK 長度:314572800 (300M) [application/octet-stream] 正在保存至: “bigfile” 100%[====================================>] 314,572,800 53.2MB/s 用時 5.3s 2019-10-16 17:29:03 (56.3 MB/s) - 已保存 “bigfile” [314572800/314572800]) [root@localhost tmp]#
可以看到速度還是很快的
限制方式一,用limit_rate做限制
server { location /abc { limit_rate 100k;#通過這個限制單個連接數的帶寬 } }
效果
[root@localhost tmp]# wget http://192.168.199.228/abc/bigfile --2019-10-16 17:34:48-- http://192.168.199.228/abc/bigfile 正在連接 192.168.199.228:80... 已連接。 已發出 HTTP 請求,正在等待回應... 200 OK 長度:314572800 (300M) [application/octet-stream] 正在保存至: “bigfile.2” 1% [ ] 5,120,000 99.7KB/s 剩余 50m 25s
可以看到速度就被限制住了
方式二:在限制單個鏈接速度的同時,為了防止開大量鏈接分段下載,然后進行合並文件的情況提速,對並發的鏈接也做限制
http { limit_conn_zone $binary_remote_addr zone=addr:10m; ... server { ... location /download/ { limit_conn addr 1; #通過這個限制鏈接數,單個ip最多的可連接數 limit_rate 100k; } } }
效果,如果單個ip超過了最多可連接數則返回503
[root@localhost tmp]# wget http://192.168.199.228/abc/bigfile --2019-10-16 17:51:27-- http://192.168.199.228/abc/bigfile 正在連接 192.168.199.228:80... 已連接。 已發出 HTTP 請求,正在等待回應... 503 Service Temporarily Unavailable 2019-10-16 17:51:27 錯誤 503:Service Temporarily Unavailable。 [root@localhost tmp]#
方式三,也可以針對某個文件下載到固定的大小后再進行限速
http { limit_conn_zone $binary_remote_addr zone=addr:10m; ... server { ... location /download/ { limit_conn addr 1; #通過這個限制鏈接數,單個ip最多的可連接數 limit_rate 100k; limit_rate_after 200M; } } }
這樣下載文件時,就會對這個文件的前200m不限速,200m之后的開始限速。
總結一下:
-
要想實現限速,單個連接帶寬限制是必須的。
-
在生產環境中,建議不要使用連接數限制
-
單個連接的帶寬限制不易過低
參考資料
[1]https://www.cnblogs.com/CarpenterLee/p/8084533.html
[2]https://www.cnblogs.com/yyxianren/p/10837424.html
[3]https://www.cnblogs.com/liushijie/p/5376372.html
[4]http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate