【高並發】面試官問我如何使用Nginx實現限流,我如此回答輕松拿到了Offer!


寫在前面

最近,有不少讀者說看了我的文章后,學到了很多知識,其實我本人聽到后是非常開心的,自己寫的東西能夠為大家帶來幫助,確實是一件值得高興的事情。最近,也有不少小伙伴,看了我的文章后,順利拿到了大廠Offer,也有不少小伙伴一直在刷我的文章,提升自己的內功,最終成為自己公司的核心業務開發人員。在此,冰河確實為你們高興,希望小伙伴們能夠一如既往的學習,保持一顆持續學習的心態,在技術的道路上越走越遠。

今天寫些什么呢?想來想去,寫一篇關於高並發實戰的文章吧,對,就寫一下如何使用Nginx實現限流的文章吧。小伙伴們想看什么文章,可以在微信上給我留言,或者直接在公眾號留言。

限流措施

如果看過我寫的《【高並發】高並發秒殺系統架構解密,不是所有的秒殺都是秒殺!》一文的話,相信小伙伴們都會記得我說過的: 網上很多的文章和帖子中在介紹秒殺系統時,說是在下單時使用異步削峰來進行一些限流操作,那都是在扯淡!因為下單操作在整個秒殺系統的流程中屬於比較靠后的操作了,限流操作一定要前置處理,在秒殺業務后面的流程中做限流操作是沒啥卵用的。

Nginx作為一款高性能的Web代理和負載均衡服務器,往往會部署在一些互聯網應用比較前置的位置。此時,我們就可以在Nginx上進行設置,對訪問的IP地址和並發數進行相應的限制。

Nginx官方的限流模塊

Nginx官方版本限制IP的連接和並發分別有兩個模塊:

  • limit_req_zone 用來限制單位時間內的請求數,即速率限制,采用的漏桶算法 "leaky bucket"。
  • limit_req_conn 用來限制同一時間連接數,即並發限制。

limit_req_zone 參數配置

limit_req_zone參數說明

Syntax: limit_req zone=name [burst=number] [nodelay];
Default:    —
Context:    http, server, location
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 nodelay;
  • 第一個參數:zone=one 設置使用哪個配置區域來做限制,與上面limit_req_zone 里的name對應。
  • 第二個參數:burst=5,重點說明一下這個配置,burst爆發的意思,這個配置的意思是設置一個大小為5的緩沖區當有大量請求(爆發)過來時,超過了訪問頻次限制的請求可以先放到這個緩沖區內。
  • 第三個參數:nodelay,如果設置,超過訪問頻次而且緩沖區也滿了的時候就會直接返回503,如果沒有設置,則所有請求會等待排隊。

limit_req_zone示例

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
    server {
        location /search/ {
            limit_req zone=one burst=5 nodelay;
        }
}

下面配置可以限制特定UA(比如搜索引擎)的訪問:

limit_req_zone  $anti_spider  zone=one:10m   rate=10r/s;
limit_req zone=one burst=100 nodelay;
if ($http_user_agent ~* "googlebot|bingbot|Feedfetcher-Google") {
    set $anti_spider $http_user_agent;
}

其他參數

Syntax: limit_req_log_level info | notice | warn | error;
Default:    
limit_req_log_level error;
Context:    http, server, location

當服務器由於limit被限速或緩存時,配置寫入日志。延遲的記錄比拒絕的記錄低一個級別。例子:limit_req_log_level notice延遲的的基本是info。

Syntax: limit_req_status code;
Default:    
limit_req_status 503;
Context:    http, server, location

設置拒絕請求的返回值。值只能設置 400 到 599 之間。

ngx_http_limit_conn_module 參數配置

ngx_http_limit_conn_module 參數說明

這個模塊用來限制單個IP的請求數。並非所有的連接都被計數。只有在服務器處理了請求並且已經讀取了整個請求頭時,連接才被計數。

Syntax: limit_conn zone number;
Default:    —
Context:    http, server, location
limit_conn_zone $binary_remote_addr zone=addr:10m;
 
server {
    location /download/ {
        limit_conn addr 1;
    }

一次只允許每個IP地址一個連接。

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
 
server {
    ...
    limit_conn perip 10;
    limit_conn perserver 100;
}

可以配置多個limit_conn指令。例如,以上配置將限制每個客戶端IP連接到服務器的數量,同時限制連接到虛擬服務器的總數。

Syntax: limit_conn_zone key zone=name:size;
Default:    —
Context:    http
limit_conn_zone $binary_remote_addr zone=addr:10m;

在這里,客戶端IP地址作為關鍵。請注意,不是$ remote_addr,而是使用$ binary_remote_addr變量。 $ remote_addr變量的大小可以從7到15個字節不等。存儲的狀態在32位平台上占用32或64字節的內存,在64位平台上總是占用64字節。對於IPv4地址,$ binary_remote_addr變量的大小始終為4個字節,對於IPv6地址則為16個字節。存儲狀態在32位平台上始終占用32或64個字節,在64位平台上占用64個字節。一個兆字節的區域可以保持大約32000個32字節的狀態或大約16000個64字節的狀態。如果區域存儲耗盡,服務器會將錯誤返回給所有其他請求。

Syntax: limit_conn_log_level info | notice | warn | error;
Default:    
limit_conn_log_level error;
Context:    http, server, location

當服務器限制連接數時,設置所需的日志記錄級別。

Syntax: limit_conn_status code;
Default:    
limit_conn_status 503;
Context:    http, server, location

設置拒絕請求的返回值。

Nginx限流實戰

限制訪問速率

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit;
    }
}

上述規則限制了每個IP訪問的速度為2r/s,並將該規則作用於根目錄。如果單個IP在非常短的時間內並發發送多個請求,結果會怎樣呢?

我們使用單個IP在10ms內發並發送了6個請求,只有1個成功,剩下的5個都被拒絕。我們設置的速度是2r/s,為什么只有1個成功呢,是不是Nginx限制錯了?當然不是,是因為Nginx的限流統計是基於毫秒的,我們設置的速度是2r/s,轉換一下就是500ms內單個IP只允許通過1個請求,從501ms開始才允許通過第二個請求。

burst緩存處理

我們看到,我們短時間內發送了大量請求,Nginx按照毫秒級精度統計,超出限制的請求直接拒絕。這在實際場景中未免過於苛刻,真實網絡環境中請求到來不是勻速的,很可能有請求“突發”的情況,也就是“一股子一股子”的。Nginx考慮到了這種情況,可以通過burst關鍵字開啟對突發請求的緩存處理,而不是直接拒絕。

來看我們的配置:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4;
    }
}

我們加入了burst=4,意思是每個key(此處是每個IP)最多允許4個突發請求的到來。如果單個IP在10ms內發送6個請求,結果會怎樣呢?

相比實例一成功數增加了4個,這個我們設置的burst數目是一致的。具體處理流程是:1個請求被立即處理,4個請求被放到burst隊列里,另外一個請求被拒絕。通過burst參數,我們使得Nginx限流具備了緩存處理突發流量的能力。

但是請注意:burst的作用是讓多余的請求可以先放到隊列里,慢慢處理。如果不加nodelay參數,隊列里的請求不會立即處理,而是按照rate設置的速度,以毫秒級精確的速度慢慢處理。

nodelay降低排隊時間

在使用burst緩存處理中,我們看到,通過設置burst參數,我們可以允許Nginx緩存處理一定程度的突發,多余的請求可以先放到隊列里,慢慢處理,這起到了平滑流量的作用。但是如果隊列設置的比較大,請求排隊的時間就會比較長,用戶角度看來就是RT變長了,這對用戶很不友好。有什么解決辦法呢?nodelay參數允許請求在排隊的時候就立即被處理,也就是說只要請求能夠進入burst隊列,就會立即被后台worker處理,請注意,這意味着burst設置了nodelay時,系統瞬間的QPS可能會超過rate設置的閾值。nodelay參數要跟burst一起使用才有作用。

延續burst緩存處理的配置,我們加入nodelay選項:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4 nodelay;
    }
}

單個IP 10ms內並發發送6個請求,結果如下:

跟burst緩存處理相比,請求成功率沒變化,但是總體耗時變短了。這怎么解釋呢?在burst緩存處理中,有4個請求被放到burst隊列當中,工作進程每隔500ms(rate=2r/s)取一個請求進行處理,最后一個請求要排隊2s才會被處理;這里,請求放入隊列跟burst緩存處理是一樣的,但不同的是,隊列中的請求同時具有了被處理的資格,所以這里的5個請求可以說是同時開始被處理的,花費時間自然變短了。

但是請注意,雖然設置burst和nodelay能夠降低突發請求的處理時間,但是長期來看並不會提高吞吐量的上限,長期吞吐量的上限是由rate決定的,因為nodelay只能保證burst的請求被立即處理,但Nginx會限制隊列元素釋放的速度,就像是限制了令牌桶中令牌產生的速度。

看到這里你可能會問,加入了nodelay參數之后的限速算法,到底算是哪一個“桶”,是漏桶算法還是令牌桶算法?當然還算是漏桶算法。考慮一種情況,令牌桶算法的token為耗盡時會怎么做呢?由於它有一個請求隊列,所以會把接下來的請求緩存下來,緩存多少受限於隊列大小。但此時緩存這些請求還有意義嗎?如果server已經過載,緩存隊列越來越長,RT越來越高,即使過了很久請求被處理了,對用戶來說也沒什么價值了。所以當token不夠用時,最明智的做法就是直接拒絕用戶的請求,這就成了漏桶算法。

自定義返回值

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4 nodelay;
        limit_req_status 598;
    }
}

默認情況下 沒有配置 status 返回值的狀態:

好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

寫在最后

如果你覺得冰河寫的還不錯,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習高並發、分布式、微服務、大數據、互聯網和雲原生技術,「 冰河技術 」微信公眾號更新了大量技術專題,每一篇技術文章干貨滿滿!不少讀者已經通過閱讀「 冰河技術 」微信公眾號文章,吊打面試官,成功跳槽到大廠;也有不少讀者實現了技術上的飛躍,成為公司的技術骨干!如果你也想像他們一樣提升自己的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 冰河技術 」微信公眾號吧,每天更新超硬核技術干貨,讓你對如何提升技術能力不再迷茫!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM