Nginx負載均衡——擴展功能(NGINX Plus)
本文主要是介紹了NGINX Plus的相關功能,橫跨了NGINX Plus R5/R6/R7/R9等各個不同版本的更新。
什麽是NGINX Plus?
顧名思義,就是Nginx的加強版或者擴展版。我們知道Nginx是開源的、免費的,但是NGINX Plus的很多功能就需要收費了。Nginx Plus 可以作為一個負載均衡器,一個web服務器,還可以作為一個內容緩存。既然是Nginx的加強版,那無疑功能會比Nginx更加強大。NGINX Plus在開源Nginx已有的功能基礎上,提供了許多適合生產環境的專有功能,包括session一致性、實時更新API配置、有效的健康檢查等。
NGINX Plus的版本更新
NGINX Plus R5 和更新的版本可以支持基於TCP應用的負載均衡(比如MySQL)。這就不僅僅限制於Http的負載均衡,而是大大擴充了Nginx作為負載均衡器的作用範圍。R6中TCP負載均衡功能得到很大的擴充,加入了健康檢查、動態更新配置、SSL終端等。等到了R7,TCP負載均衡功能就基本和Http負載均衡差不多了。z再到了R9,就可以支持UDP了。通過這些更新,NGINX Plus 遠遠超過了web應用的層面,成為了一款意義更為廣泛的負載均衡器。畢竟協議是基礎層面的東西,支持的協議越多,應用面也越廣。從最初的Http/SMTP到TCP再到UDP,NGINX Plus一步步的變得越來越強大。
開源Nginx和NGINX Plus 都支持HTTP, TCP, 和UDP應用的負載均衡。但NGINX Plus 提供了一些企業級別的功能,這些功能是收費的,包括session一致性,健康檢查,動態更新配置等。
HTTP負載均衡
NGINX Plus對Http負載均衡做了很多功能優化,諸如HTTP 升級、長連接優化、內容壓縮和響應緩存等。在NGINX Plus中Http負載均衡的實現也非常簡單:
http { upstream my_upstream { server server1.example.com; server server2.example.com; } server { listen 80; location / { proxy_set_header Host $host; proxy_pass http://my_upstream; } } }
可以通過proxy_set_header
指令來設置Host,而proxy_pass
將請求轉發到上遊的my_upstream
中。
Http長連接(HTTP Keepalives)
HTTP協議是用的底層TCP協議來傳輸請求,接收響應的。HTTP1.1支持TCP的長連接或者重利用,以免反復的創建和銷毀TCP連接所帶來的開銷。
我們看看Http的長連接:
NGINX是一個完全意義的反向代理,在長連接上也毫不含糊。它管理所以來從客戶端到Nginx的長連接,同樣也會管理從Nginx到上遊服務器的長連接,二者是完全獨立的。
Nginx管理的長連接:
NGINX 將連接上遊服務器的空閑連接做了“緩存”,並不直接關掉它們。如果有請求過來,NGINX先從緩存的活躍連接中去拿一個使用,而不是立馬創建一個新的,如果緩存為空那麽NGINX 再去新建一個連接。這種操作這降低了Nginx和上遊服務器之間的延遲並減少的臨時端口的利用率,所以NGINX能處理大的並發。這種技術加上別的負載均衡技術,有時候可以被稱為連接池,或者連接復用。
為了配置閑置長連接緩存,你需要指定幾個指令:proxy_http_version,proxy_set_header,keepalive
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_http_version 1.1; # 只有Http1.1/2.0才能支持長連接
proxy_set_header Connection "";
}
}
upstream backend {
server webserver1;
server webserver2;
# maintain a maximum of 20 idle connections to each upstream server
keepalive 20; # 閑置長連接緩存時間為20
}
TCP 和 UDP的負載均衡
作為對Http協議的擴展,NGINX Plus可以直接支持基於TCP和UDP協議的應用。基於TCP的如MySQL,支持UDP的如DNS 和RADIUS應用。對於TCP請求來說,NGINX Plus接收了客戶端的TCP請求,然後再創建一個TCP請求對上遊服務器發起訪問。
stream {
upstream my_upstream {
server server1.example.com:1234;
server server2.example.com:2345;
}
server {
listen 1123 [udp];
proxy_pass my_upstream; #註意這裏沒有http://了
}
}
對TCP請求的支持出現在NGINX Plus R5,R6和R7版本主要是在優化這個功能,到R7時TCP請求的負載均衡已經強大到足夠媲美Http負載均衡了,到了R9,則可以支持UDP了。這裏先有個印象,後面會更加詳細介紹TCP負載均衡功能。
連接數限制(Connection Limiting)
你還可以為負載均衡做連接數量限制。這裏說的連接是指NGINX Plus發給上遊服務器的Http/TCP/UDP請求連接(對於UDP則是會話)。有了連接數限制的功能,當上遊服務器的Http/TCP連接數量,或者UDP的會話數量超過一定的值時,NGINX Plus就不再創建新的連接或者會話。客戶端多出的請求連接可以被放進隊列等候,也可以不被處理。可以通過max_conns,queue
指令來實現這一點:
upstream backend {
zone backends 64k;
queue 750 timeout=30s;
server webserver1 max_conns=250;
server webserver2 max_conns=150;
}
server
指令表示webserver1 最多承載250個連接而webserver2 最多150個,多出來的可以放在隊列queue當中等候。在隊列queue中等候的連接數量和等候時間也是有限制的。當webserver1 和webserver2 連接數降低到各自最大連接數以下時,等候在隊列queue中的連接隨時就補上去。
queue 750 timeout=30s
表示總共可以有750個連接排隊等候,每個連接等候30s。
Limiting connections 是十分有用的,可以為客戶端提供可持續可預見的服務——不必因為某臺server負載過大導致掛掉。一般來說一臺server大概能承載多少負荷是可以通過某些手段測試出來的,因此把這個可承受的上線作為max_conns指令的值便可以保證server的相對安全。
Least Time 負載均衡算法
在NGINX Plus R6中增加了一種新的均衡算法——Least Time,將相應時間也考慮進去,算得上對Least Connections的擴展。
這種算法同時考慮當前連接數和連接池裏各個節點的平均響應時間。目的是使得當前請求選擇當下響應更快、連接更少的服務器,而不是選擇響應更慢、連接更多的。
當連接池的各個服務器節點有著明顯不同的響應延時時,這種算法就要優於其他的幾種(round-robin/ip-hash/lease connections)。一個典型的應用場景是,如果有兩個分布在不同的地域的數據中心,那麽本地的數據中心就要比異地的數據中心延時要少得多,這個時候就不能僅僅考慮當下連接數了,這個響應的延時也要被計入考量。Least Time算法就更傾向於選擇本地的,當然這只是“更傾向於”的問題,並不能代替Nginx最基本的錯誤轉移功能,哪怕本地的數據中心響應再快,如果它掛掉了Nginx Plus也能馬上切換到遠端數據中心。
“最少時間”可以有兩種計算方式,一種是從請求發出到上流服務器接返回響應頭部算的時間,另一種是從請求發出到接收到全部請求體的時間,分別以header_time
和response_time
來表示。
Session一致性(Session Persistence)
Session一致性問題除了可以通過指定ip-hash的均衡算法來實現,還有更為通用的實現方式,這是在NGINX Plus 中實現的。
NGINX Plus可以識別用戶Session,從而能夠鑒別不同的客戶端,並且可以將來自同一個客戶端的請求發往同一個上遊服務器。這在當應用保存了用戶狀態的情況下非常有用,可以避免負載均衡器按照某個算法將請求發到別的服務器上去。另外,在共享用戶信息的集群服務器這種方式也非常有用。
session一致性的要求同一個客戶端每次的請求都選擇同一個服務器,而負載均衡要求我們利用一種算法去服務器連接池裏面去選擇下一個,那麽這兩種矛盾的方式可以共存麽?可以的,NGINX Plus按照如下的步驟決策到底選用哪一種:
- 如果request匹配某個Session一致性的規則,那麽根據這個規則選取上遊服務器;
- 如果沒有匹配上或者匹配的服務器無法使用,那麽使用負載均衡算法選擇上遊服務器;
為了能保證session一致性,Nginx Plus提供了sticky cookie,sticky learn和sticky route幾種規則。
sticky cookie 規則
對於 sticky cookie 規則,當客戶端的第一個請求選擇了某個上遊服務器,並從這個上遊服務器返回響應時,NGINX Plus 為這個響應添加一個session cookie,用來鑒別這個上遊服務器。當後面的請求再過來時,NGINX Plus取出這個cookie,分析是哪一臺服務器,再把請求發往這臺相同的服務器。
使用指令sticky cookie
,配置如下:
upstream backend {
server webserver1;
server webserver2;
sticky cookie srv_id expires=1h domain=.example.com path=/;
}
cookie的名字就叫srv_id,用來“記住”是哪一個server;過期時間1h,domain為.example.com
;path為/
NGINX Plus在第一次響應中,插入一個名稱為srv_id
的cookie
,用來“記住”這第一次請求是發個哪個上遊的,後面的請求帶上這個cookie
,同樣再被NGINX Plus甄別一下,再發往同一個的服務器。這樣就能保證session的一致了。
sticky route 規則
和sticky cookie
規則類似,只不過“記住”上遊服務器的方式不同而已。
在客戶端發起第一次請求時,接收它的服務器為其分配一個route,此後這個客戶端發起的所有請求都要帶上這個route信息,或者在cookie中或者在uri中。然後和server指令中的route參數做對比,決定選取哪個server。如果指定的服務器無法處理,那交給負載均衡算法去選擇下一個服務器。
map $cookie_jsessionid $route_cookie {
~.+\.(?P<route>\w+)$ $route;
}
map $request_uri $route_uri {
~jsessionid=.+\.(?P<route>\w+)$ $route;
}
upstream backend {
server backend1.example.com route=a;
server backend2.example.com route=b;
# select first non-empty variable; it should contain either ‘a‘ or ‘b‘
sticky route $route_cookie $route_uri;
}
在這裏,route在JSESSIONID
的cookie
中選擇,如其包含a那麽選擇服務器backend1
;如其包含b則選擇backend2
,如果都不包含那麽在$request_uri
中再做類似的選擇,以此類推。
不管是選哪種方式保持session一致,如果選擇出的server無法使用,那麽將會按照負載均衡算法(如round-robin)在服務器列表中的選擇下一臺server繼續處理。
實時健康檢查(Active Health Checks)
前面提到過,Nginx有兩大功能:一個是擴展,增加更多的server以滿足更大的並發;二是檢測失效server,以便及時排除。那麽,如何定義一個“失效server”(failed server)就變得非常重要。這一節就是來討論這個問題。這是NGINX Plus 才有的功能,並且是收費的。
開源版本NGINX 可以提供簡單的健康檢查,並且可以自動做故障轉移。但是如何定義一個上遊server“失效”開源NGINX 卻做的很簡單。NGINX Plus為此提供了一個可以自定義的、綜合式的評判標準,除此之外NGINX Plus還可以平緩的添加新的服務器節點到集群當中。這個功能使得NGINX Plus可能甄別更為多元化的服務器錯誤,十分有效的增加了HTTP/TCP/UDP
應用的可靠性。
這裏要用到的指令有:health_check,match
等指令:
upstream my_upstream {
zone my_upstream 64k;
server server1.example.com slow_start=30s;
}
server {
# ...
location /health {
internal;
health_check interval=5s uri=/test.php match=statusok;
proxy_set_header HOST www.example.com;
proxy_pass http://my_upstream
}
}
match statusok {
# 在/test.php 做健康檢查
status 200;
header Content-Type = text/html;
body ~ "Server[0-9]+ is alive";
}
health_check
中interval=5s
表示每隔5s檢測一次;uri=/test.php
表示在/test.php
裏進行健康檢查,NGINX Plus自動發起uri的請求,uri可以自定義,你在裏面具體執行檢查的邏輯,比如mysql/redis這些是否正常,然後作出一定的響應;然後在match指令中,就通過一些規則來匹配/test.php
的響應。/test.php
的響應可以包括status,header,body
這些,供後面這些指令做匹配。全部檢查通過,就算健康,server被標記為活躍;如果一項匹配未通過,比如Content-Type = text/json
或者status = 201
那都算檢測失敗,server不健康,被標記為不活躍。
使用DNS發現新的服務
Nginx Plus一啟動就會進行DNS解析並且自動永久緩存解析出的域名和IP,但是某些情形下需要重新解析下,這時候可以使用下面的指令來實現:
resolver 127.0.0.11 valid=10s;
upstream service1 {
zone service1 64k;
server www.example.com service=http resolve;
}
127.0.0.11是默認的DNS服務器的地址,此例中NGINX Plus每10s中DNS服務器發起一次重新解析的請求。
訪問控制(Access Controls)
NGINX Plus Release 7主要給增加了TCP負載均衡的安全性。比如Access Controls和DDoS保護。
你現在可以允許或者拒絕對做反向代理的或者做負載均衡的TCP服務器的訪問,僅僅通過配置簡單的IP或者一個IP範文就能實現:
server {
# ...
proxy_set_header Host www.example.cn;
proxy_pass http://test;
deny 72.46.166.10;
deny 73.46.156.0/24;
allow all;
}
第一個deny指令拒絕一個IP的訪問,第二個拒絕一個IP範圍,除去這兩個剩下的都是被允許訪問的。被拒絕訪問的IP,會被返回403錯誤。
連接數限制(Connection Limiting)
使用NGINX Plus R7你可以限制客戶端發往由NGINX Plus代理的TCP應用的請求數量,防止對TCP的請求數量過多。在你的應用中,可能一部分的比另一部分要慢一些。比如說,請求你的應用的某塊,將會產生大量的MySQL請求,或者fork出一大堆的work進程。那麽攻擊者將會利用這點產生成千上萬個請求,致使你的服務器負載過重而癱瘓。
但是有了連接數限制功能,你可以通過配置limit_conn my_limit_conn
指令限制同一個客戶端(IP)所能發起的最大請求數,以此將上述的攻擊風險降到最低。
stream {
limit_conn_zone $binary_remote_addr zone=my_limit_conn:10m;
# ...
server {
limit_conn my_limit_conn 1;
# ...
}
}
這條指令限定了每個IP同時只能有一個連接。
帶寬限制(Bandwidth Limiting)
R7 還新增了一項功能——限制每個連接的上傳和下載的最大帶寬。
server {
# ...
proxy_download_rate 100k;
proxy_upload_rate 50k;
}
有了這個配置,客戶端最多只能以100kbytes/s的速度下載,以50kbytes/s的速度上傳。因為客戶端可以開多個連接,因此如果要限制總的上傳/下載速度,同時還得限制下單個客戶端的連接數。
支持無緩沖的上傳
這是在R6中增加的功能。你可以在R6和以後的版本中使用無緩沖的上傳,意味Nginx Plus可以通過更大的Http請求比如上傳。無緩沖的上傳可以在這些請求一過來便進行上傳,而不是像之前那樣先是緩沖所有的上傳內容,再將其轉發給你上遊服務器。
默認情況下,Nginx 在上傳時,接收到數據時會先放進緩沖區進行緩沖,以避免將資源和基於worker進程的後端腳本綁定,但是針對事件驅動的後端語言如Node.js,緩沖是幾乎沒有必要的。這個修改改進了服務器對上傳大文件的響應性,因為應用可以一接收到數據就馬上對做出響應,使得上傳進度條變成實時的和準確的。同樣,這個改進也減少了磁盤I/O。
SSL/TLS優化
在R6中,可以在和上遊的HTTPS 或者 uwSGI 服務器打交道時為客戶端提供一個證書。這大大提高了安全性,尤其是在和不受保護網絡上的安全服務進行通信的時候。R6 支持IMAP, POP3, 和SMTP的SSL/TLS 客戶端認證。
緩存優化
proxy_cache 指令可以支持變量了,這個簡單的改進以為著你可以定義幾個基於磁盤的緩存,並且根據請求數據做自由的選擇。當你打算創建巨大的內容緩存,並且將其保存到不同的磁盤時是非常有用的。
API功能
upstreem模塊的一些指令,不光可以通過手動去修改,還可以通過restful api的方式去修改,並且馬上自動更新。有了這個功能,NGINX Plus的一些功能,你都可以通過API的方式去改變。應用性得到很大提升。當然這也是收費的:
upstream backend {
zone backends 64k;
server 10.10.10.2:220 max_conns=250;
server 10.10.10.4:220 max_conns=150;
}
server {
listen 80;
server_name www.example.org;
location /api {
api write=on;
}
}
有了API,你就可以使用curl工具來動態修改配置了,比如用POST命令來增加一個集群的節點:
$ curl -iX POST -d ‘{"server":"192.168.78.66:80","weight":"200","max_conns":"150"}‘ http://localhost:80/api/1/http/upstreams/backend/servers/
相當於添加了一個這樣的配置:
upstream backend {
zone backends 64k;
server 10.10.10.2:220 max_conns=250;
server 10.10.10.4:220 max_conns=150;
#此處是通過api添加的
server 192.168.78.66:80 weight=200 max_conns=150;
}
如果需要修改一個節點配置,你可以用服務器節點在連接池中的自然順序(從0開始)作為它們各自唯一的ID,然後使用PATCH/DELETE方法去操作它們:
$ curl -iX PATCH -d ‘{"server":"192.168.78.55:80","weight":"500","max_conns":"350"}‘ http://localhost:80/api/1/http/upstreams/backend/servers/2
這條命令是修改以上連接池中的第三個server 192.168.78.66:80 max_conns=200;
為:
server 192.168.78.55:80 weight=500 max_conns=350;
如果要返回所有的節點信息,可以使用:
$ curl -s http://localhost:80/api/1/http/upstreams/backend/servers/
返回的是一個JSON字符串。
{
"backup": false,
"down": false,
"fail_timeout": "10s",
"id": 0,
"max_conns": 250,
"max_fails": 1,
"route": "",
"server": "10.10.10.2:220",
"slow_start": "0s",
"weight": 1
},
{
"backup": false,
"down": false,
"fail_timeout": "10s",
"id": 1,
"max_conns": 150,
"max_fails": 1,
"route": "",
"server": "10.10.10.4:220",
"slow_start": "0s",
"weight": 1
},
{
"backup": false,
"down": false,
"fail_timeout": "10s",
"id": 2,
"max_conns": 200,
"max_fails": 1,
"route": "",
"server": "192.168.78.66:80",
"slow_start": "0s",
"weight": 200
}
}
配置的最佳實踐
為不同個應用配置創建各自的目錄和文件,並用include
指令再合並到一起是個非常好的習慣。標準的 NGINX Plus配置是將各個應用的配置文件放到各自的conf.d directory目錄下:
http {
include /etc/nginx/conf.d/*.conf;
}
stream {
include /etc/nginx/stream.d/*.conf;
}
http 和 stream 模塊的各自分屬不同的目錄,而在http 下的都是http請求的配置,stream 都是TCP/UDP請求的配置。沒有統一的標準,主要是看開發者自己能便於識別和修改。
Nginx負載均衡——擴展功能(NGINX Plus)