實戰:使用 Nginx 限流

Nginx 不僅可以做 Web 服務器、做反向代理、負載均衡,還可以做限流系統。此處我們就 Nginx 爲例,介紹一下如何配置一個限流系統。

Nginx 使用的限流算法是漏桶算法。

(1)是安裝 Nginx。Nginx 的安裝我們在 8.5.7 中已經詳細敘述過,此處簡單再提一下:

如果你的 Linux 是 Ubuntu 或 Debian,使用 apt-get 安裝,在命令行中輸入以下命令:

$ sudo apt-get update
$ sudo apt-get install nginx

如果是 CentOS,使用 yum 安裝,在命令行中輸入以下命令:

$ sudo yum install epel-release
$ sudo yum update
$ sudo yum install nginx

(2)找到 Nginx 所使用的配置文件所在的位置。在 Ubuntu 和 Debian 是在如下位置:

$ cd /etc/nginx/sites-available/

而 CentOS 則是在如下位置:

$ cd /etc/nginx/conf.d/

(3)在 http 塊中,配置基礎的限流配置:

 http 
{     limit_req_zone$binary_remote_addr zone=mylimit:10m rate=10r/s;
     server {
         location /test/ {
             limit_reqzone=mylimit;
             proxy_passhttp://backend;
         }
     }
 }

其中 4 到 8 行定義的是一個服務器接口。而第 2 行和第 6 行配合完成了一個限流設置,下面解釋一下這兩行做的事情:

 limit_req_zone 命令在 Nginx 的配置文件中專門用於定義限流,它必須被放在 http 塊中,否則無法生效,因爲該命令只在 http 中被定義。

該字段包含三個參數

第一個參數,就是鍵(key),即值 $binary_remote_addr 所在的位置,它代表的是我們的限流系統限制請求所用的鍵。

此處,我們使用了 $binary_remote_addr,它是 Nginx 內置的一個值,代表的是客戶端的 IP 地址的二進制表示。因此換言之,我們的示例配置,是希望限流系統以客戶端的 IP 地址爲鍵進行限流。

對 Nginx 有經驗的讀者可能還知道有一個 Nginx 內置值爲 $remote_addr,它同樣表示客戶端的 IP 地址,因此我們也可以使用這個值。$binary_remote_addr 是 Nginx 的社區推薦用值,因爲它是二進制表達,佔用的空間一般比字符串表達的 $remote_addr 要短一些,在寸土寸金的限流系統中尤爲重要。

第二個參數是限流配置的共享內存佔用(zone)。爲了性能優勢,Nginx 將限流配置放在共享內存中,供所有 Nginx 的進程使用,因爲它佔用的是內存,所以我們希望開發者能夠指定一個合理的、既不浪費又能存儲足夠信息的空間大小。根據實踐經驗,1MB 的空間可以儲存 16000 個 IP 地址。

該參數的語法是用冒號隔開的兩個部分,第一部分是給該部分申請的內存一個名字,第二部分是我們希望申請的內存大小。

因此,在該聲明中,我們聲明瞭一個名叫 mylimit(我的限制)的內存空間,然後它的大小是 10M,即可以存儲 160000 個 IP 地址,對於實驗來說足夠了。

第三個配置就是訪問速率(rate)了,格式是用左斜槓隔開的請求數和時間單位。這裏的訪問速率就是最大速率,因此 10r/s 就是每秒 10 個請求。通過這臺 Nginx 服務器訪問後端服務器的請求速率無法超過每秒 10 個請求。

注意到第 5 行聲明瞭一個資源位置 /test/,因此我們第 6 行的配置就是針對這個資源的,通俗地說,我們在第 6 行的配置是針對特定 API 的,這個 API 就是路徑爲 /test/ 的 API,而其真正路徑就是第 8 行聲明的 http://backend。注意,這個 URL 是不存在的,實際操作中,讀者需要將它換成你已經開發好的業務邏輯所在的位置,Nginx 在這裏的作用只是一個反向代理,它自己本身沒有資源。

第 6 行中,我們使用 limit_req 命令,聲明該 API 需要一個限流配置,而該限流配置所在位置(zone)就是 mylimit。

這樣一來,所有發往該 API 的請求會先讀到第 6 行的限流配置,然後根據該限流配置 mylimit 的名稱找到聲明在第 2 行的參數,然後決定該請求是否應該被拒絕。

但是這樣還不夠。不要忘了,Nginx 使用的漏桶算法,不是時間窗口算法,我們前文介紹中說過,漏桶算法是有兩個參數可以配置的!

(4)配置峯值。Nginx 漏桶算法的峯值屬性在 API 中設置。參數名爲 burst。如下:

 http {
     limit_req_zone$binary_remote_addr zone=mylimit:10m rate=10r/s;
     server {
         location /test/ {
             limit_reqzone=mylimit burst=20;
             proxy_passhttp://backend;
         }
     }
 }

在第 6 行中,我們只需要在聲明 limit_req 的同時,指定 burst 就可以了,此處我們指定 burst 爲 20,即漏桶算法中我們的 “桶” 最多可以接受 20 個請求。

這樣一個 Nginx 的限流系統就配置完畢了,但實際操作中,我們還可能需要很多別的功能,下面筆者就介紹幾個很有用的配置技巧。

1. 加快 Nginx 轉發速度

相對於傳統的漏桶算法慢吞吞地轉發請求的缺陷,Nginx 實現了一種漏桶算法的優化版,允許開發者指定快速轉發,而且還不影響正常的限流功能。開發者只需要在指定 limit_req 的一行中指定 burst 之後指定另一個參數 nodelay,就可以在請求總數沒有超過 burst 指定值的情況下,迅速轉發所有請求了。如下所示:

 http {
     limit_req_zone$binary_remote_addr zone=mylimit:10m rate=10r/s;
     server {
         location /test/ {
             limit_reqzone=mylimit burst=20 nodelay;
             proxy_passhttp://backend;
         }
     }
 }

讀者可能會擔憂:這種情況下,會不會出現所有請求都被快速轉發,然後接下來又有沒有超過 burst 數量的請求出現,再次被快速轉發,就好像固定窗口算法的漏洞一樣,從而超過我們本來希望它能限制到的上限數量呢?答案是不會。Nginx 的快速轉發是這樣實現的:

·       當有沒有超過 burst 上限的請求數量進入系統時,快速轉發,然後將當前桶中可以填充的數量標爲 0;

·       按照我們設置的 rate 在 1 秒中內緩慢增加桶中餘額,以我們的配置爲例,每隔 100 毫秒,增加 1 個空位;

·       在增加空位的過程中,進來超過空位數量的請求,直接拒絕。

舉例而言,配置如上所示,假如在某個瞬時有 25 個請求進入系統,Nginx 會先轉發 20 個(或 21 個,取決於瞬時情況),然後拒絕剩下的 4 個請求,並將當前桶中數量標爲 0,然後接下來的每 100 毫秒,緩慢恢復 1 個空位。

這樣我們可以看到,Nginx 既做到了快速轉發消息,又不會讓後端服務器承擔過多的流量。

2. 爲限流系統配置日誌級別

限流系統會提前拒絕請求,因此,我們在業務服務器上是肯定看不到這些請求的。假如我們收到一個報告說某用戶在使用網站的時候出現錯誤,但是我們在業務服務器上又找不到相關的日誌,我們如何確定是不是限流造成的呢?

只有限流系統的日誌才能說明問題。因此,我們需要 Nginx 打印出它拒絕掉的請求的信息。但同時,Nginx 打印的限流日誌默認是錯誤(error),如果我們設置了一個基於日誌錯誤掃描的警報,它掃到的限流錯誤,真的是我們希望給自己發警報的情況嗎?

配置請求的位置就在資源中,使用的命令是 limit_req_log_level,如下:

 http {
     limit_req_zone$binary_remote_addr zone=mylimit:10m rate=10r/s;
     server {
         location /test/ {
             limit_reqzone=mylimit burst=20;
            limit_req_log_level warn;
             proxy_passhttp://backend;
         }
     }
 }

在第 7 行中,我們將 Nginx 的日誌改爲了警告(warn)。

3. 更換 Nginx 的限流響應狀態碼

前文我們就說過,從語義上來說,限流的 HTTP 標準響應狀態碼是 429,但是如果讀者拿上述的配置文件直接去測試,會發現 Nginx 返回的是 503(服務不可用)。到底應該返回什麼狀態碼,是一個偏程序哲學的問題,此處我們不討論,我們只討論:如何讓 Nginx 返回我們指定的狀態碼?

答案也是在同一個資源中,它的配置命令是 limit_req_status,然後我們指定它爲 429 即可:

http {
   limit_req_zone$binary_remote_addr zone=mylimit:10m rate=10r/s;
     server {
         location /test/ {
             limit_reqzone=mylimit burst=20;
            limit_req_log_level warn;
            limit_req_status 429;
             proxy_passhttp://backend;
         }
     }
 }

除了以上功能以外,Nginx 還支持很多複雜先進的限流功能。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/asG0fzTkfNKV9WOZge6Usg