最近,有很多讀者說看了個人文章後,學到了不少知識,其實我本人聽到後是很是開心的,本身寫的東西可以爲你們帶來幫助,確實是一件值得高興的事情。最近,也有很多小夥伴,看了個人文章後,順利拿到了大廠Offer,也有很多小夥伴一直在刷個人文章,提高本身的內功,最終成爲本身公司的核心業務開發人員。在此,冰河確實爲大家高興,但願小夥伴們可以一如既往的學習,保持一顆持續學習的心態,在技術的道路上越走越遠。node
今天寫些什麼呢?想來想去,寫一篇關於高併發實戰的文章吧,對,就寫一下如何使用Nginx實現限流的文章吧。小夥伴們想看什麼文章,能夠在微信上給我留言,或者直接在公衆號留言。算法
若是看過我寫的《【高併發】高併發秒殺系統架構解密,不是全部的秒殺都是秒殺!》一文的話,相信小夥伴們都會記得我說過的: 網上不少的文章和帖子中在介紹秒殺系統時,說是在下單時使用異步削峯來進行一些限流操做,那都是在扯淡!由於下單操做在整個秒殺系統的流程中屬於比較靠後的操做了,限流操做必定要前置處理,在秒殺業務後面的流程中作限流操做是沒啥卵用的。緩存
Nginx做爲一款高性能的Web代理和負載均衡服務器,每每會部署在一些互聯網應用比較前置的位置。此時,咱們就能夠在Nginx上進行設置,對訪問的IP地址和併發數進行相應的限制。bash
Nginx官方版本限制IP的鏈接和併發分別有兩個模塊:服務器
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;
複製代碼
limit_req zone=one burst=5 nodelay;
複製代碼
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 之間。併發
這個模塊用來限制單個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
複製代碼
設置拒絕請求的返回值。
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開始才容許經過第二個請求。
咱們看到,咱們短期內發送了大量請求,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設置的速度,以毫秒級精確的速度慢慢處理。
在使用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 返回值的狀態:
好了,我們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一塊兒學習一塊兒進步!!
若是你以爲冰河寫的還不錯,請微信搜索並關注「 冰河技術 」微信公衆號,跟冰河學習高併發、分佈式、微服務、大數據、互聯網和雲原生技術,「 冰河技術 」微信公衆號更新了大量技術專題,每一篇技術文章乾貨滿滿!很多讀者已經經過閱讀「 冰河技術 」微信公衆號文章,成功跳槽到大廠;也有很多讀者實現了技術上的飛躍,成爲公司的技術骨幹!若是你也想像他們同樣提高本身的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 冰河技術 」微信公衆號吧,天天更新超硬核技術乾貨,讓你對如何提高技術能力再也不迷茫!