1. 程式人生 > 實用技巧 >Nginx的limit模組

Nginx的limit模組

Nginx的limit模組主要包括:ngx_http_limit_req_modulengx_http_limit_conn_modulengx_stream_limit_conn_module

以及ngx_http_core_module中limit_rate選項,由於stream主要用來實現四層協議(網路層和傳輸層)的轉發、代理、負載均衡等,並且ngx_stream_limit_conn_modulengx_http_limit_conn_module配置基本相同,所以本文不做介紹。

ngx_http_limit_req_module

該模組用來限制某個特定鍵的請求處理的速率,這個特定鍵一般為某個ip。該模組主要用到了"漏桶"演算法。有關漏桶演算法可以

點選此處詳細檢視。

指令

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

server {
   location /search/ {
       limit_req zone=one burst=5 [nodelay|delay=num];
   }

首先使用limit_req_zone定義一個記憶體區域(定義在http段中):

$binary_remote_addr:特定用於該模組的變數,用來存取客戶端ip地址。

zone=one:10m:定義一個大小為10m的共享記憶體區域,名稱為one。對於ipv4地址佔用4個位元組,ipv6佔用16個位元組。儲存一個連線狀態在32位機器上佔用64個位元組,在64位機器上佔用128個位元組。1MB的空間可以儲存16k個64位元組狀態或者是8k個128位元組狀態。所以10m的空間可以儲存大約8w個狀態,對於一般的中小型站點已經足夠了。

rate=1r/s:定義請求速率為每秒僅接受一個請求

在server配置段中引用上述配置(第5行):

zone=one:引用上面limit_req_zone定義的記憶體區域one

burst:定義請求快取的長度,意思是如果某ip1秒內發來了10個請求,那麼除了正在處理的1個請求,其他的請求會把暫時放置到burst中排隊。超過rate+burst請求將會被全部丟棄

nodelay:一次處理burst+rate個請求,其餘全部丟棄。如果不希望在請求受到限制時延遲過多的請求應當使用這個引數

delay=num:瞬時處理num+rate個請求,總共快取burst個,剩餘的burst-num按rate定義處理。丟棄多餘請求。

其他指令

limit_req_dry_run
語法:   limit_req_dry_run on | off
預設值: limit_req_dry_run off;
語境:   http server location

該指令開啟"幹跑"模式。開啟該配置,請求處理的速率不再被限制。但是在共享記憶體區域,過量的請求的數值會像之前一樣計算。

limit_req_log_level
語法:	  limit_req_log_level info | notice | warn | error;
預設值: limit_req_log_level error;
語境:	  http server location

該指令設定rate超過限值或者延時請求處理的日誌級別,預設為error級別。

limit_req_status
語法:	  limit_req_status code;
預設值:  limit_req_status 503;
語境:	   http server location

該指令定義拒絕響應請求的http狀態碼,預設返回*503

測試

1、不開啟burst,不開啟nodelay

配置如下所示:

 http {
    include       mime.types;
    default_type  application/octet-stream;

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

    log_format  main  '$remote_addr "$request"'
                      '$status'
                      '"$http_user_agent"';

    server {
        listen       80;
        server_name  localhost;

        charset utf-8;

        location / {
            limit_req zone=one;
            root   /usr/share/nginx/html/;
            index  index.html index.htm;
        }

    }

}

在另一臺虛擬機器使用Apahce Benchmark進行壓力測試

ab -c 10 -n10 http://192.168.0.106/index.html

結果如下:

192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:20:58:41 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"

嚴格按照rate定義處理請求,除了第一個請求外其餘所有的請求全部丟棄並返回503。

2、在18行末尾新增burst=5

192.168.0.107 - - [05/May/2020:21:53:35 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:53:35 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:53:35 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:53:35 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:53:35 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:53:36 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:53:37 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:53:38 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:53:39 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:53:40 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"

按照定義,1秒內來了10個請求,burst=5,rate=1。總共快取5個請求,處理1個請求,其餘全部丟棄。這就是21:53:35內丟棄了4個請求。隨後快取的請求按照rate定義值處理。

3、在18行末尾繼續新增nodelay

192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:21:58:19 +0800] "GET /index.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"

瞬間提供rate+burst個請求處理能力,丟棄其他請求返回503。

4、將18行的nodelay改成delay=3

192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:42:05 +0800] "GET / HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:42:06 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:42:07 +0800] "GET / HTTP/1.0" 200 70 "-" "ApacheBench/2.3"

可以看出nginx瞬間處理了4(rate+delay)個請求,快取了2個請求,其餘請求返回503。快取下來的請求每秒處理一個。

ngx_http_limit_conn_module

ngx_http_limit_conn_module與ngx_http_limit_req_module主要區別在於,conn限制的連線的數量,req限制的是請求的數量。一個連線的生命週期中,會存在一個或者多個請求,這是為了加快效率,避免每次請求都要三次握手建立連線,現在的HTTP/1.1協議都支援這種特性,叫做keepalive。

官方配置示例:

 http {
    limit_conn_zone $binary_remote_addr zone=addr:10m;

    server {
        
        location /download/ {
           limit_conn addr 1;
        }

limit_conn_zone:設定共享區域的引數,該區域將保留各種鍵的狀態,與ngx_http_limit_req_module不同的是,此模組僅定義了區域了大小。鍵裡可以儲存文字,變數或者兩者的組合。

limit_conn:設定共享記憶體區域以及鍵定義的最大允許的連線數量。超過限值時,伺服器將會發送錯誤給客戶端。limit_conn addr 1允許一個ip一次最多建立一次連線。如果是HTTP/2,那麼每個併發的請求都會被視作是一個單獨的連線。

limit_conn_dry_run,limit_conn_log_level,limit_conn_statusngx_http_limit_req_module中一致不再說明。

測試

192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:54:27 +0800] "GET /test.html HTTP/1.0" 503 197 "-" "ApacheBench/2.3"
192.168.0.107 - - [05/May/2020:23:54:29 +0800] "GET /test.html HTTP/1.0" 200 536870912 "-" "ApacheBench/2.3"

test.html為50M,測試1秒內僅能建立一個連線其餘全部丟棄。但index.html測試失敗,不知道和檔案的大小存在什麼關聯?

limit_rate

指令

limit_rate是ngx_http_core_module這個核心模組自帶的一個配置選項。可以用來限制單個連線的下載速率。對於資原始檔下載伺服器來說,有必要限制這個值,防止單個ip過高的速率影響他人的正常使用。

語法:	   limit_rate rate;
預設值:  limit_rate 0;
語境:	   http, server, location, if in location

預設情況下,這個值是0,也就是不限制下載速率。官方推薦使用map來靈活定義下載速率。如:

map $slow $rate {
    0     40k;
    1     80k;
    default 120k;
}
limit_rate $rate; #http,server,location,if in location

當然$slow不是nginx自帶的變數,所以直接使用會報錯。需要根據ngx_http_geo_module模組自帶的geo指令來定義$slow變數。

geo $remote_addr $slow {
	default 0;
	47.103.215.250 1;
}

以上是這樣的過程:$remote_addr是nginx自帶的變數,geo指令拿到這個變數對應的ip地址去和下面的default47.103.215.250匹配,如果滿足$remote_addr=47.103.215.250,則把$slow變數設定為1,否則就設定為0。再拿0或者1去map裡匹配得到$rate,最後在limit_rate中應用$rate來達到限制速率的要求。

注:geo 和 map都是應用在http段中

測試

本地下載測試為40KB/s

47.103.215.250上測試為80KB/s

當然limit_rate只能限制單個ip的一個請求:

The limit is set per a request, and so if a client simultaneously opens two connections, the overall rate will be twice as much as the specified limit.

如果客戶端同時發起了兩個連結,這個下載速度會變成限值的2倍。所以對於迅雷這種多執行緒的下載器,設定這樣的限值的無效的。

解決的方法就是配合ngx_http_limit_conn_module模組,讓伺服器每秒只響應一個ip的一個請求,其餘請求全部丟棄。

    limit_conn_zone $binary_remote_addr zone=one:10m;
    server {
        listen       80;
        server_name  localhost;
        
        location / {
            limit_conn one 1;
            limit_rate 80k;
            root   /usr/share/nginx/html/;
        }

    }

在測試端下載test.html

[root@k8s-node ~]# wget http://192.168.0.106/test.html
--2020-05-06 00:00:43--  http://192.168.0.106/test.html
Connecting to 192.168.0.106:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 536870912 (512M) [text/html]
Saving to: ‘test.html.2’

test.html.2                        
0%[                                                    ]   1.29M  79.6KB/s    eta 1h 48m 

複製終端會話再次請求:

Last login: Tue May  5 23:40:24 2020 from 192.168.0.101
[root@k8s-node ~]# wget http://192.168.0.106/test.html
--2020-05-06 00:01:18--  http://192.168.0.106/test.html
Connecting to 192.168.0.106:80... connected.
HTTP request sent, awaiting response... 503 Service Temporarily Unavailable
2020-05-06 00:01:19 ERROR 503: Service Temporarily Unavailable.

nginx直接返回了503,這樣就達到了限制單個ip的請求速率的目的。