天天看點

Nginx 限流很難配置嗎?帶你從頭到尾重新捋一遍,不信你學不會!

作者:民工哥技術之路

1、概述

限流(Rate Limitting)是服務降級的一種方式,通過限制系統的輸入和輸出流量以達到保護系統的目的。

比如我們的網站暴露在公網環境中,除了使用者的正常通路,網絡爬蟲、惡意攻擊或者大促等突發流量都可能都會對系統造成壓力,如果這種壓力超出了伺服器的處理能力,會造成響應過慢甚至系統崩潰的問題。

是以,當并發請求數過大時,我們通過限制一部分請求(比如限制同一IP的頻繁請求)來保證伺服器可以正确響應另一部分的請求。

nginx 提供了兩種限流方式,一種是限制請求速率,一種是限制連接配接數量。

另外還提供了對下載下傳/上傳速度的限制。

2、限制請求速率

nginx 的 ngx_http_limit_req_module 子產品提供限制請求處理速率的能力,使用了漏桶算法(leaky bucket algorithm)。我們可以想像有一隻上面進水、下面勻速出水的桶,如果桶裡面有水,那剛進去的水就要存在桶裡等下面的水流完之後才會流出,如果進水的速度大于水流出的速度,桶裡的水就會滿,這時水就不會進到桶裡,而是直接從桶的上面溢出。

對應到處理網絡請求,水代表從用戶端來的請求,而桶代表一個隊列,請求在該隊列中依據先進先出(FIFO)算法等待被處理。漏的水代表請求離開緩沖區并被伺服器處理,溢出代表了請求被丢棄并且永不被服務。

Nginx 限流很難配置嗎?帶你從頭到尾重新捋一遍,不信你學不會!

2.1、正常限流

nginx 中有兩個主要的指令可以用來配置限流:limit_req_zone 和 limit_req。

下面是一個最簡單的限流的例子:

limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;

server {
    location / {
        limit_req zone=test;
    }
} 
           

imit_req_zone 用于設定限流和共享記憶體區域的參數,格式為:limit_req_zone key zone rate。

  • key: 定義限流對象,$binary_remote_addr 是 nginx 中的變量,表示基于 remote_addr(用戶端IP) 來做限流,binary_ 是二進制存儲。使用 $binary_remote_addr 而不是 $remote_addr 是因為二進制存儲可以壓縮記憶體占用量。$remote_addr 變量的大小從7到15個位元組不等,而 $binary_remote_addr變量的大小對于 IPv4 始終為4個位元組,對于 IPv6 位址則為16個位元組。
  • zone: 定義共享記憶體區來存儲通路資訊,通路資訊包括每個 IP 位址狀态和通路受限請求 URL 的頻率等。zone 的定義又分為兩個部分:由 zone= 關鍵字辨別的區域名稱,以及冒号後面的區域大小。test:10m 表示一個大小為10M,名字為 test 的記憶體區域。1M 能存儲16000個 IP 位址的通路資訊,test 大概可以存儲約160000個位址。nginx 建立新記錄的時候,會移除前60秒内沒有被使用的記錄,如果釋放的空間還是存儲不了新的記錄,會傳回503的狀态碼。
  • rate: 設定最大的通路速率。rate=2r/s(為了好模拟,rate 設定的值比較小),表示每秒最多處理 2個請求。事實上 nginx 是以毫秒為粒度追蹤請求的,rate=2r/s 實際上是每500毫秒1個請求,也就是說,上一個請求完成後,如果500毫秒内還有請求到達,這些請求會被拒絕(預設傳回503,如果想修改傳回值,可以設定limit_req_status)。

limit_req_zone 隻是設定限流參數,如果要生效的話,必須和 limit_req 配合使用。limit_req 的格式為:limit_req zone=name [burst=number] [nodelay]。

上面的例子隻簡單指定了 zone=test,表示使用 test 這個區域的配置,在請求 html 檔案時進行限流。我們可以了解為這個桶目前沒有任何儲存水滴的能力,到達的所有不能立即漏出的請求都會被拒絕。如果我1秒内發送了10次請求,其中前500毫秒1次,後500毫秒9次,那麼隻有前500毫秒的請求和後500毫秒的第一次請求會響應,其餘請求都會被拒絕。

2.2、處理突發流量

上面的配置保證了 nginx 以固定的速度提供服務(2r/s),但是這種情況不适用于有突發流量的情況,我們希望可以盡可能的緩存請求并處理它們,此時需要在 limit_req 上增加 burst 參數:

limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;

server {
    location / {
        limit_req zone=test burst=5;
    }
} 
           

burst 表示在超過設定的通路速率後能額外處理的請求數。當 rate=2r/s 時,表示每500ms 可以處理一個請求。burst=5時,如果同時有10個請求到達,nginx 會處理第1個請求,剩餘9個請求中,會有5個被放入隊列,剩餘的4個請求會直接被拒絕。然後每隔500ms從隊列中擷取一個請求進行處理,此時如果後面繼續有請求進來,如果隊列中的請求數目超過了5,會被拒絕,不足5的時候會添加到隊列中進行等待。我們可以了解為現在的桶可以存5滴水:

Nginx 限流很難配置嗎?帶你從頭到尾重新捋一遍,不信你學不會!

配置 burst 之後,雖然同時到達的請求不會全部被拒絕,但是仍需要等待500ms 一次的處理時間,放入桶中的第5個請求需要等待500ms * 4的時間才能被處理,更長的等待時間意味着使用者的流失,在許多場景下,這個等待時間是不可接受的。此時我們需要增加 nodelay 參數,和 burst 配合使用。

limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;

server {
    location / {
        limit_req zone=test burst=5 nodelay;
    }
} 
           

nodelay 表示不延遲。設定 nodelay 後,第一個到達的請求和隊列中的請求會立即進行處理,不會出現等待的請求。

Nginx 限流很難配置嗎?帶你從頭到尾重新捋一遍,不信你學不會!

需要注意的是,雖然隊列中的5個請求立即被處理了,但是隊列中的位置依舊是按照500ms 的速度依次被釋放的。後面的4個請求依舊是被拒絕的,長期來看并不會提高吞吐量的上限,長期吞吐量的上限是由設定的 rate 決定的。

2.3、設定白名單

如果遇到不需要限流的情況,比如測試要壓測,可以通過配置白名單,取消限流的設定。白名單要用到 nginx 的 ngx_http_geo_module 和 ngx_http_map_module 子產品。

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=mylimit:10m rate=2r/s;
           

geo 指令可以根據 IP 建立變量 $limit。$limit 的預設值是1,如果比對到了下面的 IP,則傳回對應的值(這裡傳回的是0)。

之後通過 map 指令,将 $limit 的值映射為$limit_key:在白名單内的,$limit_key 為空字元串,不在白名單内的,則為 $binary_remote_addr。當limit_req_zone指令的第一個參數是一個空字元串,限制不起作用,是以白名單的 IP 位址(在10.0.0.0/8和192.168.0.0/24子網中)沒有被限制,其它 IP 位址都被限制為 2r/s

2.4、limit_req重複

如果同一個 location 下配置了多條 limit_req 的指令,這些指令所定義的限制都會被使用。

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=mylimit:10m rate=2r/s;
limit_req_zone $binary_remote_addr zone=myLimit2:10m rate=10r/s;
server {
    location ~* \.(html)$ {
        limit_req zone=mylimit burst=5 nodelay;
        limit_req zone=myLimit2 burst=5 nodelay;
    }
} 
           

上面的例子配置了兩條規則,myLimit 和 myLimit2。白名單使用者雖然沒有比對到mylimit的規則,但是根據規則 mylimit2,被限制為10r/s。對于不在白名單的使用者,則需要同時比對mylimit 和 mylimit2,兩者中最嚴格的條件 2r/s 會起作用。

3、限制連接配接數

nginx 的 ngx_http_limit_conn_module 子產品提供限制連接配接數的能力,包含兩個指令limit_conn_zone 和 limit_conn,格式為limit_conn_zone key zone。

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
    location ~* \.(html)$ {
        limit_conn perip 10;
        limit_conn perserver 100;
    }
} 
           
  • limit_conn perip 10: key 是$binary_remote_addr,表示限制單個IP同時最多能持有10個連接配接。
  • limit_conn perserver 100: key 是 $server_name,表示虛拟主機(server) 同時能處理并發連接配接的總數為100。
需要注意的是:隻有當 request header 被後端server處理後,這個連接配接才進行計數。

4、上傳/下載下傳速率限制

limit_rate主要用于限制使用者和伺服器之間傳輸的位元組數,最常用的場景可能就是下載下傳/上傳限速。limit_rate并沒有單獨的一個子產品,而是在ngx_http_core_module中,同時它的相關指令也比較少,隻有limit_rate和limit_rate_after這兩個指令。

4.1、limit_rate

server {
    location / {
        limit_rate 4k;
    }
} 
           
  • limit_rate的用法非常簡單,後面跟随的rate就是具體限速的門檻值
  • 注意預設的機關是bytes/s,也就是每秒傳輸的位元組數Bytes而不是比特數bits
  • rate可以設定為變量,進而可以實作動态限速
  • 限速指令的生效範圍是根據每個連接配接确定的,例如上面限定每個連接配接的速率為4k,也就是當用戶端發起兩個連接配接的時候,速率就可以變為8k

4.2、limit_rate_after

server {
    location / {
     limit_rate_after 500k;
        limit_rate 4k;
    }
} 
           

limit_rate_after允許在傳輸了一部分資料之後再進行限速,例如上面的配置中就是傳輸的前500k資料不限速,500k之後再進行限速。比較常見的應用場景如分段下載下傳限速,超過指定大小的部分再進行限速;又或者是流媒體視訊網站一般為了保證使用者體驗而不會對第一個畫面進行限速,確定其能夠盡快加載出來,等使用者開始觀看視訊之後,再把帶寬限制在合理的範圍内,進而降低因用戶端網速過快導緻提前加載過多内容帶來的額外成本。

4.3、proxy_limit_rate

proxy_limit_rate的基本原理和用法與limit_rate幾乎一樣,唯一不同的是proxy_limit_rate是限制nginx和後端upstream伺服器之間的連接配接速率而limit_rate限制的是nginx和用戶端之間的連接配接速率。需要注意的是proxy_limit_rate需要開啟了proxy_buffering這個指令才會生效。

#文法:
Syntax: proxy_limit_rate rate;
Default:    proxy_limit_rate 0;
Context:    http, server, location
This directive appeared in version 1.7.7.
           

4.4、動态限速

limit_rate的一大特點就是能夠使用變量,這就意味着和map指令之類的進行組合就可以實作動态限速功能,這裡隻列幾個簡單的示範

4.4.1、基于時間動态限速

這裡引入了nginx内置的一個ssi子產品,這個子產品有兩個比較有意思的時間變量:$date_local和$date_gmt,分别對應目前時間和GMT時間

這裡使用變量和map指令組合的方式,利用正規表達式比對不同的時間段,再結合map變量将不同時間段和不同的限速對應起來。

map $date_local $limit_rate_time {
     default 4K;
     ~(00:|01:|02:|03:|04:|05:|06:|07:).*:.* 16K;
     ~(08:|12:|13:|18:).*:.* 8K;
     ~(19:|20:|21:|22:|23:).*:.* 16K;
 }
 
 limit_rate $limit_rate_time
           

4.2、基于變量動态限速

有些服務可能會對不用的使用者進行不同的限速,例如VIP使用者的速度要更快一些等,例如下面可以針對不同的cookie進行限速

map $cookie_User $limit_rate_cookie {
     gold 64K;
     silver 32K;
     copper 16K;
     iron 8K;
 }
 
 limit_rate $limit_rate_cookie
           
來源:blog.csdn.net/cold___play/article/details/132094865

今天的分享就到這裡了,如有幫助,歡迎一鍵三連(點贊、評論、轉發)支援一下!

讀者專屬群:誠邀你加入技術交流群,一起卷!

如有錯誤或其它問題,捐迎小夥伴留言評論、指正。如有幫助,歡迎點贊+轉發分享。更多相關開源技術文章,請持續關注!資源分享(小編為你精心準備了2048G的各類學習資料。包括系統運維、資料庫、redis、MogoDB、電子書、Java基礎課程、Java實戰項目、架構師綜合教程、架構師實戰項目、大資料、Docker容器、ELK Stack、機器學習、BAT面試精講視訊等。)

繼續閱讀