Nginx流量控制及白名單實(shí)現(xiàn)
流量限制 (rate-limiting),是Nginx中一個(gè)非常實(shí)用,卻經(jīng)常被錯(cuò)誤理解和錯(cuò)誤配置的功能。我們可以用來(lái)限制用戶在給定時(shí)間內(nèi)HTTP請(qǐng)求的數(shù)量。請(qǐng)求,可以是一個(gè)簡(jiǎn)單網(wǎng)站首頁(yè)的GET請(qǐng)求,也可以是登錄表單的 POST 請(qǐng)求。流量限制可以用作安全目的,比如可以減慢暴力密碼破解的速率。通過(guò)將傳入請(qǐng)求的速率限制為真實(shí)用戶的典型值,并標(biāo)識(shí)目標(biāo)URL地址(通過(guò)日志),還可以用來(lái)抵御 DDOS 攻擊。更常見(jiàn)的情況,該功能被用來(lái)保護(hù)上游應(yīng)用服務(wù)器不被同時(shí)太多用戶請(qǐng)求所壓垮。
以下將會(huì)介紹Nginx的 流量限制 的基礎(chǔ)知識(shí)和高級(jí)配置,”流量限制”在Nginx Plus中也適用。
1、Nginx如何限流
Nginx的”流量限制”使用漏桶算法(leaky bucket algorithm),該算法在通訊和分組交換計(jì)算機(jī)網(wǎng)絡(luò)中廣泛使用,用以處理帶寬有限時(shí)的突發(fā)情況。就好比,一個(gè)桶口在倒水,桶底在漏水的水桶。如果桶口倒水的速率大于桶底的漏水速率,桶里面的水將會(huì)溢出;同樣,在請(qǐng)求處理方面,水代表來(lái)自客戶端的請(qǐng)求,水桶代表根據(jù)”先進(jìn)先出調(diào)度算法”(FIFO)等待被處理的請(qǐng)求隊(duì)列,桶底漏出的水代表離開(kāi)緩沖區(qū)被服務(wù)器處理的請(qǐng)求,桶口溢出的水代表被丟棄和不被處理的請(qǐng)求。
2、配置基本的限流
準(zhǔn)備兩臺(tái)機(jī)器,關(guān)閉防火墻和selinux
localhost | Roucky_linux9.4 | 192.168.226.20 |
localhost | Roucky_linux9.4 | 192.168.226.21 |
都使用nginx官方源下載nginx并啟動(dòng)
sudo tee /etc/yum.repos.d/nginx.repo << 'EOF' [nginx-stable] name=nginx stable repo baseurl=https://nginx.org/packages/centos/$releasever/$basearch/ gpgcheck=1 enabled=1 gpgkey=https://nginx.org/keys/nginx_signing.key [nginx-mainline] name=nginx mainline repo baseurl=https://nginx.org/packages/mainline/centos/$releasever/$basearch/ gpgcheck=1 enabled=0 gpgkey=https://nginx.org/keys/nginx_signing.key EOF
yum install -y nginx systemctl enable --now nginx
這里還準(zhǔn)備了訪問(wèn)自動(dòng)測(cè)試的代碼,使用go語(yǔ)言編寫(xiě)
package main import ( "fmt" "io" "net/http" "time" ) // ANSI color codes const ( RedColor = "\033[31m" GreenColor = "\033[32m" YellowColor = "\033[33m" ResetColor = "\033[0m" ) // 記錄成功、失敗、無(wú)法響應(yīng)以及服務(wù)暫時(shí)不可用(503)的請(qǐng)求數(shù)量 var successCount int var failureCount int var unresponsiveCount int var tempUnavailableCount int // 新增變量來(lái)跟蹤503 Service Unavailable狀態(tài)的請(qǐng)求數(shù)量 // makeRequest 向給定的 URL 發(fā)送一個(gè) GET 請(qǐng)求,并根據(jù)響應(yīng)的成功與否以不同顏色打印出響應(yīng)狀態(tài)和所花費(fèi)的時(shí)間。 func makeRequest(url string, attempt int) { startTime := time.Now() resp, err := http.Get(url) elapsedTime := time.Since(startTime) if err != nil { // 如果因?yàn)榫W(wǎng)絡(luò)問(wèn)題或服務(wù)器問(wèn)題導(dǎo)致請(qǐng)求失敗,我們將其計(jì)為“無(wú)法響應(yīng)” fmt.Printf("[%d] %sRequest unresponsive: %s %.4f seconds%s\n", attempt, RedColor, err, elapsedTime.Seconds(), ResetColor) unresponsiveCount++ return } defer func(Body io.ReadCloser) { err := Body.Close() if err != nil { fmt.Printf("Error closing response body: %s\n", err) } }(resp.Body) color := GreenColor if resp.StatusCode == 503 { color = YellowColor tempUnavailableCount++ // 對(duì)返回503狀態(tài)碼的請(qǐng)求進(jìn)行統(tǒng)計(jì) } else if resp.StatusCode != 200 { color = RedColor failureCount++ } else { successCount++ } // 根據(jù)狀態(tài)碼,以對(duì)應(yīng)的顏色打印響應(yīng)狀態(tài)碼和耗時(shí) fmt.Printf("[%d] %s%d %.4f seconds%s\n", attempt, color, resp.StatusCode, elapsedTime.Seconds(), ResetColor) } func main() { url := "http://192.168.226.21" totalAttempts := 1000 startTime := time.Now() for attempt := 1; attempt <= totalAttempts; attempt++ { makeRequest(url, attempt) } totalElapsedTime := time.Since(startTime) successRate := (float64(successCount) / float64(totalAttempts)) * 100 failureRate := (float64(failureCount) / float64(totalAttempts)) * 100 unresponsiveRate := (float64(unresponsiveCount) / float64(totalAttempts)) * 100 tempUnavailableRate := (float64(tempUnavailableCount) / float64(totalAttempts)) * 100 // 計(jì)算503狀態(tài)碼比率 fmt.Printf("%sTotal elapsed time: %.4f seconds%s\n", ResetColor, totalElapsedTime.Seconds(), ResetColor) fmt.Printf("成功率: %.2f%%, 失敗率: %.2f%%, 無(wú)法響應(yīng)的請(qǐng)求比: %.2f%%, 服務(wù)暫時(shí)不可用請(qǐng)求比率(503): %.2f%%\n", successRate, failureRate, unresponsiveRate, tempUnavailableRate) }
實(shí)驗(yàn)開(kāi)始前進(jìn)行訪問(wèn)1000次測(cè)試耗時(shí)和成功率
在192.168.226.20主機(jī)操作
“流量限制”配置兩個(gè)主要的指令,limit_req_zone
和limit_req
,如下所示:
在http模塊里配置
# 定義一個(gè)限流區(qū)域 # $binary_remote_addr 使用二進(jìn)制格式的客戶端IP地址作為鍵 # zone=mylimit:10m 創(chuàng)建一個(gè)名為'mylimit'的共享內(nèi)存區(qū)域,大小為10MB # rate=10r/s 每秒最多允許1個(gè)請(qǐng)求 limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s; upstream myweb { server 192.168.226.21:80 weight=1 max_fails=1 fail_timeout=1; }
在server模塊里配置
server { listen 80; # 定義服務(wù)器監(jiān)聽(tīng)的端口為 80,這是 HTTP 協(xié)議的默認(rèn)端口。 server_name localhost; # 設(shè)置服務(wù)器名稱為 localhost,這意味著它將響應(yīng)發(fā)送到 localhost 的請(qǐng)求。 location /{ # 定義對(duì)根路徑("/")的請(qǐng)求的處理規(guī)則。 root /usr/share/nginx/html; # 設(shè)置文檔根目錄,這是 Nginx 在響應(yīng)請(qǐng)求時(shí)查找文件的地方。 index index.html index.htm; # 設(shè)置當(dāng)請(qǐng)求是一個(gè)目錄時(shí),默認(rèn)會(huì)返回的文件。在這里,如果訪問(wèn)根目錄("/"),會(huì)首先嘗試返回 index.html 文件,如果沒(méi)有找到,再嘗試 index.htm。 limit_req zone=mylimit; # 為根路徑的請(qǐng)求應(yīng)用請(qǐng)求限制,使用名為 mylimit 的限流規(guī)則。 limit_req_dry_run off; # 設(shè)置為on以在日志中查看限制的影響,而不會(huì)實(shí)際限制流量 proxy_pass http://myweb; proxy_set_header Host $host:$server_port; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
#修改默認(rèn)打開(kāi)頁(yè)面,用來(lái)區(qū)分兩個(gè)主機(jī) #修改192.168.226.20主機(jī) echo "webserver1" > /usr/share/nginx/html/index.html #修改192.168.226.21主機(jī) echo "webserver2" > /usr/share/nginx/html/index.html
#兩個(gè)主機(jī)都重啟nginx systemctl restart nginx
瀏覽器訪問(wèn)192.168.226.20實(shí)際上會(huì)看到webserver2的字樣頁(yè)面,然后刷新看,有失敗和成功頁(yè)面,就完成了基本的限流設(shè)置。跑完go代碼看結(jié)果如果(比率是有一點(diǎn)波動(dòng)的是正常的):
limit_req_zone
指令定義了流量限制相關(guān)的參數(shù),而limit_req
指令在出現(xiàn)的上下文中啟用流量限制(示例中,對(duì)于”/login/”的所有請(qǐng)求)。
limit_req_zone
指令通常在HTTP塊中定義,使其可在多個(gè)上下文中使用,它需要以下三個(gè)參數(shù):
Key - 定義應(yīng)用限制的請(qǐng)求特性。示例中的 Nginx 變量
$binary_remote_addr
,保存客戶端IP地址的二進(jìn)制形式。這意味著,我們可以將每個(gè)不同的IP地址限制到,通過(guò)第三個(gè)參數(shù)設(shè)置的請(qǐng)求速率。(使用該變量是因?yàn)楸茸址问降目蛻舳薎P地址$remote_addr
,占用更少的空間)Zone - 定義用于存儲(chǔ)每個(gè)IP地址狀態(tài)以及被限制請(qǐng)求URL訪問(wèn)頻率的共享內(nèi)存區(qū)域。保存在內(nèi)存共享區(qū)域的信息,意味著可以在Nginx的worker進(jìn)程之間共享。定義分為兩個(gè)部分:通過(guò)
zone=keyword
標(biāo)識(shí)區(qū)域的名字,以及冒號(hào)后面跟區(qū)域大小。16000個(gè)IP地址的狀態(tài)信息,大約需要1MB,所以示例中區(qū)域可以存儲(chǔ)160000個(gè)IP地址。Rate - 定義最大請(qǐng)求速率。在示例中,速率不能超過(guò)每秒10個(gè)請(qǐng)求。Nginx實(shí)際上以毫秒的粒度來(lái)跟蹤請(qǐng)求,所以速率限制相當(dāng)于每100毫秒1個(gè)請(qǐng)求。因?yàn)椴辉试S”突發(fā)情況”(見(jiàn)下一章節(jié)),這意味著在前一個(gè)請(qǐng)求100毫秒內(nèi)到達(dá)的請(qǐng)求將被拒絕。
當(dāng)Nginx需要添加新條目時(shí)存儲(chǔ)空間不足,將會(huì)刪除舊條目。如果釋放的空間仍不夠容納新記錄,Nginx將會(huì)返回 503狀態(tài)碼(Service Temporarily Unavailable)。另外,為了防止內(nèi)存被耗盡,Nginx每次創(chuàng)建新條目時(shí),最多刪除兩條60秒內(nèi)未使用的條目。
limit_req_zone
指令設(shè)置流量限制和共享內(nèi)存區(qū)域的參數(shù),但實(shí)際上并不限制請(qǐng)求速率。所以需要通過(guò)添加
limit_req
指令,將流量限制應(yīng)用在特定的location
或者server
塊。在上面示例中,我們對(duì)/login/
請(qǐng)求進(jìn)行流量限制。
現(xiàn)在每個(gè)IP地址被限制為每秒只能請(qǐng)求10次/login/
,更準(zhǔn)確地說(shuō),在前一個(gè)請(qǐng)求的100毫秒內(nèi)不能請(qǐng)求該URL。
3、處理突發(fā)
如果我們?cè)?00毫秒內(nèi)接收到2個(gè)請(qǐng)求,怎么辦?對(duì)于第二個(gè)請(qǐng)求,Nginx將給客戶端返回狀態(tài)碼503。這可能并不是我們想要的結(jié)果,因?yàn)閼?yīng)用本質(zhì)上趨向于突發(fā)性。相反地,我們希望緩沖任何超額的請(qǐng)求,然后及時(shí)地處理它們。我們更新下配置,在limit_req
中使用burst
參數(shù):
重啟nginx然后運(yùn)行g(shù)o代碼請(qǐng)求測(cè)試
burst
參數(shù)定義了當(dāng)請(qǐng)求速率超過(guò)rate
參數(shù)所設(shè)定的限制時(shí),允許額外處理的請(qǐng)求數(shù)量。這些額外的請(qǐng)求將被暫時(shí)放入一個(gè)隊(duì)列中等待處理,而不是立即返回錯(cuò)誤。但是訪問(wèn)量大的話后面的排隊(duì)時(shí)間就會(huì)太慢,并不是最優(yōu)的選擇。
burst
參數(shù)定義了超出zone指定速率的情況下(示例中的mylimit
區(qū)域,速率限制在每秒10個(gè)請(qǐng)求,或每100毫秒一個(gè)請(qǐng)求),客戶端還能發(fā)起多少請(qǐng)求。上一個(gè)請(qǐng)求100毫秒內(nèi)到達(dá)的請(qǐng)求將會(huì)被放入隊(duì)列,我們將隊(duì)列大小設(shè)置為20。
這意味著,如果從一個(gè)給定IP地址發(fā)送21個(gè)請(qǐng)求,Nginx會(huì)立即將第一個(gè)請(qǐng)求發(fā)送到上游服務(wù)器群,然后將余下20個(gè)請(qǐng)求放在隊(duì)列中。然后每100毫秒轉(zhuǎn)發(fā)一個(gè)排隊(duì)的請(qǐng)求,只有當(dāng)傳入請(qǐng)求使隊(duì)列中排隊(duì)的請(qǐng)求數(shù)超過(guò)20時(shí),Nginx才會(huì)向客戶端返回503。
4、無(wú)延遲的排隊(duì)
配置burst
參數(shù)將會(huì)使通訊更流暢,但是可能會(huì)不太實(shí)用,因?yàn)樵撆渲脮?huì)使站點(diǎn)看起來(lái)很慢。在上面的示例中,隊(duì)列中的第20個(gè)包需要等待2秒才能被轉(zhuǎn)發(fā),此時(shí)返回給客戶端的響應(yīng)可能不再有用。要解決這個(gè)情況,可以在burst
參數(shù)后添加nodelay
參數(shù):
重啟nginx運(yùn)行g(shù)o測(cè)試代碼
使用nodelay
參數(shù),Nginx仍將根據(jù)burst
參數(shù)分配隊(duì)列中的位置,并應(yīng)用已配置的速率限制,而不是清理隊(duì)列中等待轉(zhuǎn)發(fā)的請(qǐng)求。相反地,當(dāng)一個(gè)請(qǐng)求到達(dá)“太早”時(shí),只要在隊(duì)列中能分配位置,Nginx將立即轉(zhuǎn)發(fā)這個(gè)請(qǐng)求。將隊(duì)列中的該位置標(biāo)記為”taken”(占據(jù)),并且不會(huì)被釋放以供另一個(gè)請(qǐng)求使用,直到一段時(shí)間后才會(huì)被釋放(在這個(gè)示例中是,100毫秒后)。
假設(shè)如前所述,隊(duì)列中有20個(gè)空位,從給定的IP地址發(fā)出的21個(gè)請(qǐng)求同時(shí)到達(dá)。Nginx會(huì)立即轉(zhuǎn)發(fā)這個(gè)21個(gè)請(qǐng)求,并且標(biāo)記隊(duì)列中占據(jù)的20個(gè)位置,然后每100毫秒釋放一個(gè)位置。如果是25個(gè)請(qǐng)求同時(shí)到達(dá),Nginx將會(huì)立即轉(zhuǎn)發(fā)其中的21個(gè)請(qǐng)求,標(biāo)記隊(duì)列中占據(jù)的20個(gè)位置,并且返回503狀態(tài)碼來(lái)拒絕剩下的4個(gè)請(qǐng)求。
現(xiàn)在假設(shè),第一組請(qǐng)求被轉(zhuǎn)發(fā)后101毫秒,另20個(gè)請(qǐng)求同時(shí)到達(dá)。隊(duì)列中只會(huì)有一個(gè)位置被釋放,所以Nginx轉(zhuǎn)發(fā)一個(gè)請(qǐng)求并返回503狀態(tài)碼來(lái)拒絕其他19個(gè)請(qǐng)求。如果在20個(gè)新請(qǐng)求到達(dá)之前已經(jīng)過(guò)去了501毫秒,5個(gè)位置被釋放,所以Nginx立即轉(zhuǎn)發(fā)5個(gè)請(qǐng)求并拒絕另外15個(gè)。
效果相當(dāng)于每秒10個(gè)請(qǐng)求的“流量限制”。如果希望不限制兩個(gè)請(qǐng)求間允許間隔的情況下實(shí)施“流量限制”,nodelay
參數(shù)是很實(shí)用的。
注意: 對(duì)于大部分部署,我們建議使用burst
和nodelay
參數(shù)來(lái)配置limit_req
指令。
5、高級(jí)配置示例
通過(guò)將基本的“流量限制”與其他Nginx功能配合使用,我們可以實(shí)現(xiàn)更細(xì)粒度的流量限制。
1、白名單
下面這個(gè)例子需要將上面做過(guò)的刪除,然后重新配置
#排除上面實(shí)驗(yàn)的干擾,重新安裝配置nginx yum remove -y nginx yum install -y nginx systemctl enable --now nginx
下面這個(gè)例子將展示,如何對(duì)任何不在白名單內(nèi)的請(qǐng)求強(qiáng)制執(zhí)行“流量限制”:
vim /etc/nginx/nginx.conf
將下面的http塊替換nginx配置文件的里的http塊代碼,其他不用動(dòng)
#將下面的http塊替換nginx配置文件的里的http塊代碼,其他不用動(dòng) http { include /etc/nginx/mime.types; # 包括MIME類型定義,讓Nginx能根據(jù)文件擴(kuò)展名設(shè)置正確的Content-Type頭。 default_type application/octet-stream; # 設(shè)置默認(rèn)的MIME類型,當(dāng)文件類型未定義時(shí)使用。 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; # 自定義日志格式。 access_log /var/log/nginx/access.log main; # 定義access日志的存儲(chǔ)位置和使用的格式。 # 以下部分定義了基于IP地址的訪問(wèn)限制。 geo $limit { default 1; # 默認(rèn)情況下,所有IP地址的$limit值設(shè)為1。 10.0.0.0/24 0; # 對(duì)于這兩個(gè)子網(wǎng)中的IP地址,將$limit設(shè)為0。 192.168.0.0/24 0; } # 根據(jù)$limit的值,動(dòng)態(tài)地設(shè)置$limit_key變量。 map $limit $limit_key { 0 ""; # 如果$limit為0,$limit_key為空。 1 $binary_remote_addr; # 如果$limit為1,$limit_key使用二進(jìn)制格式的客戶端IP地址。 } # 定義請(qǐng)求限制區(qū)域req_zone,按$limit_key對(duì)請(qǐng)求進(jìn)行分組,每個(gè)組最多每秒處理5個(gè)請(qǐng)求。 limit_req_zone $limit_key zone=req_zone:10m rate=5r/s; server { # 定義一個(gè)服務(wù)器監(jiān)聽(tīng)在80端口。 listen 80; # 監(jiān)聽(tīng)80端口。 server_name localhost; # 服務(wù)器名稱設(shè)置為localhost。 location / { # 對(duì)于根路徑的請(qǐng)求。 limit_req zone=req_zone burst=10 nodelay; # 應(yīng)用請(qǐng)求限制,允許突發(fā)請(qǐng)求高達(dá)10個(gè)。 root /usr/share/nginx/html; # 網(wǎng)站根目錄。 index index.html index.hml; # 定義默認(rèn)首頁(yè)。 } } include /etc/nginx/conf.d/*.conf; # 導(dǎo)入/etc/nginx/conf.d/目錄下的所有.conf配置文件。 }
這個(gè)例子同時(shí)使用了geo
和map
指令。geo
塊將給在白名單中的IP地址對(duì)應(yīng)的$limit
變量分配一個(gè)值0,給其它不在白名單中的分配一個(gè)值1。然后我們使用一個(gè)映射將這些值轉(zhuǎn)為key,如下:
如果
$limit
變量的值是0,$limit_key
變量將被賦值為空字符串如果
$limit
變量的值是1,$limit_key
變量將被賦值為客戶端二進(jìn)制形式的IP地址
兩個(gè)指令配合使用,白名單內(nèi)IP地址的$limit_key
變量被賦值為空字符串,不在白名單內(nèi)的被賦值為客戶端的IP地址。當(dāng)limit_req_zone
后的第一個(gè)參數(shù)是空字符串時(shí),不會(huì)應(yīng)用“流量限制”,所以白名單內(nèi)的IP地址(10.0.0.0/24和192.168.0.0/24 網(wǎng)段內(nèi))不會(huì)被限制。其它所有IP地址都會(huì)被限制到每秒5個(gè)請(qǐng)求。
limit_req
指令將限制應(yīng)用到/的location塊,允許在配置的限制上最多超過(guò)10個(gè)數(shù)據(jù)包的突發(fā),并且不會(huì)延遲轉(zhuǎn)發(fā)。
2、location 包含多l(xiāng)imit_req指令
我們可以在一個(gè)location塊中配置多個(gè)limit_req
指令。符合給定請(qǐng)求的所有限制都被應(yīng)用時(shí),意味著將采用最嚴(yán)格的那個(gè)限制。例如,多個(gè)指令都制定了延遲,將采用最長(zhǎng)的那個(gè)延遲。同樣,請(qǐng)求受部分指令影響被拒絕,即使其他指令允許通過(guò)也無(wú)濟(jì)于事。
擴(kuò)展前面將“流量限制”應(yīng)用到白名單內(nèi)IP地址的例子:
http { # ... limit_req_zone $limit_key zone=req_zone:10m rate=5r/s; limit_req_zone $binary_remote_addr zone=req_zone_wl:10m rate=15r/s; server { # ... location / { limit_req zone=req_zone burst=10 nodelay; limit_req zone=req_zone_wl burst=20 nodelay; # ... } } } 白名單內(nèi)的IP地址不會(huì)匹配到第一個(gè)“流量限制”,而是會(huì)匹配到第二個(gè)`req_zone_wl`,并且被限制到每秒15個(gè)請(qǐng)求。 不在白名單內(nèi)的IP地址兩個(gè)限制能匹配到,所以應(yīng)用限制更強(qiáng)的那個(gè):每秒5個(gè)請(qǐng)求。
因此理解了原理,修改上面的http塊里的代碼進(jìn)行測(cè)試:
#將下面的http塊替換nginx配置文件的里的http塊代碼,其他不用動(dòng) http { include /etc/nginx/mime.types; # 包括MIME類型定義,讓Nginx能根據(jù)文件擴(kuò)展名設(shè)置正確的Content-Type頭。 default_type application/octet-stream; # 設(shè)置默認(rèn)的MIME類型,當(dāng)文件類型未定義時(shí)使用。 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; # 自定義日志格式。 access_log /var/log/nginx/access.log main; # 定義access日志的存儲(chǔ)位置和使用的格式。 # 以下部分定義了基于IP地址的訪問(wèn)限制。 geo $limit { default 1; 10.0.0.0/24 0; 192.168.0.0/24 0; } # 根據(jù)$limit的值,動(dòng)態(tài)地設(shè)置$limit_key變量。 map $limit $limit_key { 0 ""; # 如果$limit為0,$limit_key為空。 1 $binary_remote_addr; # 如果$limit為1,$limit_key使用二進(jìn)制格式的客戶端IP地址。 } # 定義請(qǐng)求限制區(qū)域req_zone,按$limit_key對(duì)請(qǐng)求進(jìn)行分組,每個(gè)組最多每秒處理5個(gè)請(qǐng)求。 limit_req_zone $limit_key zone=req_zone:10m rate=5r/s; limit_req_zone $binary_remote_addr zone=req_zone_wl:10m rate=15r/s; server { # 定義一個(gè)服務(wù)器監(jiān)聽(tīng)在80端口。 listen 80; # 監(jiān)聽(tīng)80端口。 server_name localhost; # 服務(wù)器名稱設(shè)置為localhost。 location / { # 對(duì)于根路徑的請(qǐng)求。 limit_req zone=req_zone burst=10 nodelay; limit_req zone=req_zone_wl burst=20 nodelay; root /usr/share/nginx/html; # 網(wǎng)站根目錄。 index index.html index.hml; # 定義默認(rèn)首頁(yè)。 } } include /etc/nginx/conf.d/*.conf; # 導(dǎo)入/etc/nginx/conf.d/目錄下的所有.conf配置文件。 }
重啟nginx測(cè)試:
接下來(lái)將白名單和黑名單調(diào)換
重啟nginx并測(cè)試
因此:白名單內(nèi)的IP地址不會(huì)匹配到第一個(gè)“流量限制”,而是會(huì)匹配到第二個(gè)req_zone_wl
,并且被限制到每秒15個(gè)請(qǐng)求。不在白名單內(nèi)的IP地址兩個(gè)限制能匹配到,所以應(yīng)用限制更強(qiáng)的那個(gè):每秒5個(gè)請(qǐng)求是正確的。
6、配置流量控制相關(guān)功能
1、配置日志記錄
默認(rèn)情況下,Nginx會(huì)在日志中記錄由于流量限制而延遲或丟棄的請(qǐng)求,如下所示:
2024/06/20 20:20:00 [error] 120315#0: *32086 limiting requests, excess: 1.000 by zone "mylimit", client: 192.168.226.20, server: nginx.com, request: "GET / HTTP/1.0", host: "nginx.com"
日志條目中包含的字段:
limiting requests - 表明日志條目記錄的是被“流量限制”請(qǐng)求
excess - 每毫秒超過(guò)對(duì)應(yīng)“流量限制”配置的請(qǐng)求數(shù)量
zone - 定義實(shí)施“流量限制”的區(qū)域
client - 發(fā)起請(qǐng)求的客戶端IP地址
server - 服務(wù)器IP地址或主機(jī)名
request - 客戶端發(fā)起的實(shí)際HTTP請(qǐng)求
host - HTTP報(bào)頭中host的值
Nginx 的 limit_req_log_level
指令用于定義當(dāng)請(qǐng)求速率超出 limit_req
指定的速率限制時(shí),日志記錄的級(jí)別。這允許你對(duì)因超出請(qǐng)求速率限制而被延遲或拒絕的請(qǐng)求進(jìn)行特定級(jí)別的日志記錄,從而能夠更靈活地處理這些日志信息,而不必改變?nèi)值腻e(cuò)誤日志級(jí)別。
limit_req_log_level
指令支持的日志級(jí)別包括:
- info: 記錄基礎(chǔ)信息,這是默認(rèn)設(shè)置。
- notice: 記錄正常但重要的情況。
- warn: 記錄更嚴(yán)重的警告信息。
- error: 記錄錯(cuò)誤信息,這個(gè)設(shè)置通常用于希望對(duì)超限請(qǐng)求進(jìn)行更加突出顯示的場(chǎng)景。
默認(rèn)情況下,Nginx以error
級(jí)別來(lái)記錄被拒絕的請(qǐng)求,如上面示例中的[error]
所示(Ngin以較低級(jí)別記錄延時(shí)請(qǐng)求,一般是info
級(jí)別)。如要更改Nginx的日志記錄級(jí)別,需要使用limit_req_log_level
指令。這里,我將被拒絕請(qǐng)求的日志記錄級(jí)別設(shè)置為warn
:
limit_req_zone $binary_remote_addr zone=myzone:10m rate=1r/s; server { location / { limit_req zone=myzone burst=5 nodelay; limit_req_log_level warn; ... } }
在上面的例子中,limit_req_zone
指令定義了一個(gè)名為 myzone
的速率限制區(qū)域,限制速率為每秒1個(gè)請(qǐng)求。在 server
塊的 location
部分,通過(guò) limit_req
應(yīng)用了這個(gè)速率限制,并且設(shè)置了 burst
參數(shù)為5,表示允許短時(shí)間內(nèi)超出速率限制的請(qǐng)求在不被立即拒絕的情況下排隊(duì)等待處理。更重要的是,通過(guò) limit_req_log_level warn
指令,指定了當(dāng)請(qǐng)求被延遲或拒絕時(shí),這些事件將以 warn
級(jí)別記錄在日志中。這意味著只有當(dāng)請(qǐng)求因?yàn)槌鏊俾氏拗贫谎舆t或拒絕時(shí),才會(huì)在日志中以警告級(jí)別記錄,從而使這類事件在日志文件中更加突出,便于管理員進(jìn)行分析和監(jiān)控。
擴(kuò)展日志記錄級(jí)別:
注意區(qū)別這兩個(gè)日志對(duì)應(yīng)的層級(jí)關(guān)系和定義
Nginx 的日志記錄級(jí)別用于控制日志中記錄信息的詳細(xì)程度。在 Nginx 中,你可以為錯(cuò)誤日志(error_log
指令)設(shè)置不同的日志級(jí)別,這些級(jí)別從最低到最高包括:
debug: 提供最詳盡的日志信息,包括調(diào)試信息。這個(gè)級(jí)別記錄所有的詳細(xì)操作,對(duì)于開(kāi)發(fā)或調(diào)試非常有用,但在生產(chǎn)環(huán)境中會(huì)生成大量日志信息。
info: 記錄基礎(chǔ)的信息性消息,除了
debug
級(jí)別的信息。notice: 記錄正常但重要的條件。默認(rèn)級(jí)別,標(biāo)準(zhǔn)的錯(cuò)誤和重要信息都會(huì)被記錄。
warn: 記錄警告信息,例如使用了非標(biāo)準(zhǔn)的配置指令或者有潛在的錯(cuò)誤。
error: 記錄處理請(qǐng)求時(shí)發(fā)生的錯(cuò)誤,但不會(huì)阻止服務(wù)運(yùn)行的錯(cuò)誤信息。
crit: 記錄臨界條件錯(cuò)誤,比如嚴(yán)重的硬件或軟件錯(cuò)誤。
alert: 要求立即采取行動(dòng)的情況,如系統(tǒng)運(yùn)行環(huán)境出現(xiàn)了非常嚴(yán)重的問(wèn)題。
emerg: 記錄緊急情況,如系統(tǒng)不可用的情況。
配置錯(cuò)誤日志級(jí)別的語(yǔ)法如下:
error_log /path/to/your/error.log level;
這里,/path/to/your/error.log
是想要保存錯(cuò)誤日志的文件路徑,level
是上面提到的日志級(jí)別之一。如果不指定級(jí)別,error
級(jí)別會(huì)被使用。
在配置日志時(shí),選擇適當(dāng)?shù)娜罩炯?jí)別對(duì)于優(yōu)化性能和管理日志文件大小很重要。例如,在開(kāi)發(fā)環(huán)境中,可能會(huì)使用 debug
級(jí)別以便獲取盡可能多的信息來(lái)調(diào)試應(yīng)用。然而,在生產(chǎn)環(huán)境中,可能會(huì)選擇 notice
或 warn
級(jí)別,來(lái)減少日志文件的大小并關(guān)注更重要的系統(tǒng)消息。
2、發(fā)送到客戶端的錯(cuò)誤代碼
一般情況下,客戶端超過(guò)配置的流量限制時(shí),Nginx響應(yīng)狀態(tài)碼為503(Service Temporarily Unavailable)??梢允褂?code>limit_req_status指令來(lái)設(shè)置為其它狀態(tài)碼(例如下面的412狀態(tài)碼):
可選擇范圍大約在400-599之間,并盡量避免與已存在的狀態(tài)碼產(chǎn)生沖突,造成歧義。
續(xù)接上面的配置,只需要在server模塊中的location模塊中加入一個(gè)參數(shù)為
limit_req_status 412;
添加的位置如圖說(shuō)式:
重啟nginx
systemctl restart nginx
測(cè)試查看:
這里是因?yàn)槲业膅o代碼的bug顯示黃色,因?yàn)閾Q了裝填嗎顯示成失敗對(duì)應(yīng)的紅色,但其實(shí)已經(jīng)從默認(rèn)的503狀態(tài)碼切換到指定的412狀態(tài)碼了。
7、nginx 流量控制總結(jié)
以上已經(jīng)涵蓋了Nginx和Nginx Plus提供的“流量限制”的很多功能,包括為HTTP請(qǐng)求的不同loation設(shè)置請(qǐng)求速率,給“流量限制”配置burst
和nodelay
參數(shù)。還涵蓋了針對(duì)客戶端IP地址的白名單和黑名單應(yīng)用不同“流量限制”的高級(jí)配置,闡述了如何去日志記錄被拒絕和延時(shí)的請(qǐng)求。
到此這篇關(guān)于Nginx流量控制實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Nginx流量控制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
nginx刷新頁(yè)面出現(xiàn)404解決方案(親測(cè)有效)
本文主要介紹了nginx刷新頁(yè)面出現(xiàn)404解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03Nginx使用Gzip算法對(duì)報(bào)文進(jìn)行壓縮詳解
這篇文章主要給大家介紹了關(guān)于Nginx的Gzip功能的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Nginx具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08詳解Nginx中的Rewrite的重定向配置與實(shí)踐
這篇文章主要介紹了詳解Nginx中的Rewrite的重定向配置與實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03nginx啟動(dòng)、關(guān)閉及重啟等簡(jiǎn)單命令小結(jié)
這篇文章主要介紹了使用命令行重啟Nginx的方法,包括修改配置文件后重啟以使更改生效,查看端口占用情況,以及如何關(guān)閉Nginx,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-03-03基于Nginx禁止指定IP、國(guó)外IP訪問(wèn)我的網(wǎng)站
這篇文章主要介紹了用Nginx禁止指定IP、國(guó)外IP訪問(wèn)我的網(wǎng)站,想要實(shí)現(xiàn)這個(gè)功能方法有很多種,這里基于 Nginx 的 ngx_http_geoip2 模塊來(lái)禁止國(guó)外 IP 訪問(wèn)網(wǎng)站,需要的朋友可以參考下2022-05-05Nginx解決history模式下頁(yè)面刷新404問(wèn)題示例
這篇文章主要為大家介紹了Nginx解決history模式下頁(yè)面刷新404問(wèn)題示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10