反向代理與負載均衡配置
接下來介紹Nginx的重要功能:反向代理+負載均衡。單體Nginx的性能雖然不錯,但也是有瓶頸的。打個比方:用戶請求發起一個請求,網站顯示的圖片量比較大,如果這個時候有大量用戶同時訪問,全部的工作量都集中到了一台服務器上,服務器不負重壓,可能就崩潰了。高並發場景下,自然需要多台服務器進行集群,既能防止單個節點崩潰導致平台無法使用,又能提高一些效率。一般來說,Nginx完成10萬多用戶同時訪問,程序就相對容易崩潰。
要做到高並發和高可用,肯定需要做Nginx集群的負載均衡,而Nginx負載均衡的基礎之一就是反向代理。

演示環境說明
為了較好地演示反向代理的效果,本小節調整一下演示的環境:
不再通過瀏覽器發出HTTP請求,而是使用curl指令從筆者的CentOS虛擬機192.168.233.128向Windows宿主機器192.168.233.1上的Nginx發起請求。
為了完成演示,在宿主機Nginx的配置文件nginx-proxydemo.conf中配置兩個server虛擬主機,一個端口為80,另一個端口為8080。具體如下:
#模擬目標主機
server {
listen 8080 ;
server_name localhost;
default_type 'text/html';
charset utf-8;
location / {
echo "-uri= $uri"
"-host= $host"
"-remote_addr= $remote_addr"
"-proxy_add_x_forwarded_for= $proxy_add_x_forwarded_for"
"-http_x_forwarded_for= $http_x_forwarded_for" ;
}
}
#模擬代理主機
server {
listen 80 default;
server_name localhost;
default_type 'text/html';
charset utf-8;
location / {
echo "默認根路徑匹配: /";
}
...
}本節用到的配置文件為源碼工程nginx-proxy-demo.conf文件。運行本小節的實例前需要修改openresty-start.bat(或openrestystart.sh)腳本中的PROJECT_CONF變量的值,將其改為nginx-proxydemo.conf,然後重啟OpenRestry/Nginx。

proxy_pass反向代理指令
這裡介紹的proxy_pass反向代理指令處於ngx_http_proxy_module模塊,並且註冊在HTTP請求11個階段的content階段。
proxy_pass反向代理指令的格式如下:
proxy_pass 目標URL前綴;
當proxy_pass後面的目標URL格式為”協議”+”IP[:port]”+”/”根路徑的格式時,表示最終的結果路徑會把location指令的URI前綴也給加上,這裡稱為不帶前綴代理。如果目標URL為”協議”+”IP[:port]”,而沒有「/根路徑」,那麼Nginx不會把location的URI前綴加到結果路徑中,這裡稱為帶前綴代理。
1.不帶location前綴的代理
proxy_pass後面的目標URL前綴加「/根路徑」,實例如下:
#不帶location前綴的代理類型
location /foo_no_prefix {
proxy_pass http://127.0.0.1:8080/;
}通過CentOS的curl指令發出請求
http://192.168.233.1/foo_no_prefix/bar.html,結果如下:
[root@localhost ~]#curl http://192.168.233.1/foo_no_prefix/bar.html
-uri= /bar.html -host= 127.0.0.1 -remote_addr= 127.0.0.1 -proxy_add_x_forwarded_for= 127.0.0.1 -http_x_forwarded_for=可以看到,$uri變量輸出的代理URI為/bar.html,並沒有在結果URL中看到location配置指令的前綴/foo_no_prefix。
2.帶location前綴的代理
proxy_pass後面的目標URL前綴不加「/根路徑」,實例如下:
#帶location前綴代理
location /foo_prefix {
proxy_pass http://127.0.0.1:8080;
}通過CentOS的curl指令發出請求
http://192.168.233.1/foo_prefix/bar.html,結果如下:
[root@localhost ~]#curl http://192.168.233.1/foo_prefix/bar.html
-uri= /foo_prefix/bar.html -host= 127.0.0.1 -remote_addr= 127.0.0.1 -proxy_add_x_forwarded_for= 127.0.0.1 -http_x_forwarded可以看到,$uri變量輸出的代理URI為/foo_prefix/bar.html,也就是說,在結果URL中看到了location配置指令的前綴/foo_prefix。
除了以上兩種代理(帶location前綴的代理和不帶location前綴的代理)之外,還有一種帶部分URI路徑的代理。
3.帶部分URI路徑的代理
如果proxy_pass的路徑參數中不止有IP和端口,還有部分目標URI的路徑,那麼最終的代理URL由兩部分組成:第一部分為配置項中的目標URI前綴;第二部分為請求URI中去掉location中前綴的剩餘部分。
下面是兩個實例:
#帶部分URI路徑的代理,實例1
location /foo_uri_1 {
proxy_pass http://127.0.0.1:8080/contextA/;
}
#帶部分URI路徑的代理,實例2
location /foo_uri_2 {
proxy_pass http://127.0.0.1:8080/contextA-;
}通過CentOS的curl指令發出兩個請求分別匹配到這兩個location配置,結果如下:
[root@localhost ~]#curl http://192.168.233.1/foo_uri_1/bar.html
-uri= /contextA/bar.html -host= 127.0.0.1 -remote_addr= 127.0.0.1 -proxy_add_x_forwarded_for= 127.0.0.1 -http_x_forwarded_fo
[root@localhost ~]#curl http://192.168.233.1/foo_uri_2/bar.html
-uri= /contextA-bar.html -host= 127.0.0.1 -remote_addr= 127.0.0.1 -proxy_add_x_forwarded_for= 127.0.0.1 -http_x_forwarded_fo從輸出結果可以看出,無論是例子中的目標URI前綴/contextA/,還是目標URI前綴/contextA-,都加在了最終的代理路徑上,只是在代理路徑中去掉了location指令的匹配前綴。
新的問題來了:僅僅使用proxy_pass指令進行請求轉發,發現很多原始請求信息都丟了。明顯的是客戶端IP地址,前面的例子中請求都是從192.168.233.128 CentOS機器發出去的,經過代理服務器之後,服務端返回的remote_addr客戶端IP地址並不是192.168.233.128,而是變成了代理服務器的IP 127.0.0.1。
如何解決原始信息的丟失問題呢?使用proxy_set_header指令。

proxy_set_header請求頭設置指令
在反向代理之前,proxy_set_header指令能重新定義/添加字段傳遞給代理服務器的請求頭。請求頭的值可以包含文本、變量和它們的組合。它的格式如下:
#head_field表示請求頭,field_value表示值
proxy_pass_header head_field field_value;前面講到,由於經過反向代理後,對於目標服務器來說,客戶端在本質上已經發生了變化,因此後端的目標Web服務器無法直接拿到客戶端的IP。假設後端的服務器是Tomcat,那麼在Java中request.getRemoteAddr()取得的是Nginx的地址,而不是客戶端的真實IP。
如果需要取得真實IP,那麼可以通過proxy_set_header指令在發生反向代理調用之前將保持在內置變量$remote_addr中的真實客戶端地址保持到請求頭中(一般為X-real-ip),代碼如下:
#不帶location前綴的代理
location /foo_no_prefix/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header X-real-ip $remote_addr;
}在Java端使用request.getHeader(”X-real-ip”)獲取X-real-ip請求頭的值就可以獲得真正的客戶端IP。
在整個請求處理的鏈條上可能不僅一次反向代理,可能會經過N多次反向代理。為了獲取整個代理轉發記錄,也可以使用proxy_set_header指令來完成,在配置文件中進行如下配置:
#帶location前綴的代理
location /foo_prefix {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080;
}這裡使用了$proxy_add_x_forwarded_for內置變量,它的作用就是記錄轉發歷史,其值的第一個地址就是真實地址$remote_addr,然後每經過一個代理服務器就在後面累加一次代理服務器的地址。
上面的演示程序中,如果在Java服務器程序中通過如下代碼獲取代理轉發記錄:
request.getHeader(“X-Forwarded-For”)
那麼Java程序獲得的返回值為「192.168.233.128,127.0.0.1」,表示最初的請求客戶端的IP為192.168.233.128,經過了127.0.0.1代理服務器。每經過一次代理服務器,都會在後邊追加上它的IP,並且使用逗號隔開。
為了不丟失信息,反向代理的設置如下:
location /hello {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-real-ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
}設置了請求頭Host、X-real-ip、X-Forwarded-For,分別將當前的目標主機、客戶端IP、轉發記錄保存在請求頭中。
proxy_redirect指令的作用是修改從上游被代理服務器傳來的應答頭中的Location和Refresh字段,尤其是當上游服務器返回的響應碼是重定向或刷新請求(如HTTP響應碼是301或者302)時,proxy_redirect可以重設HTTP頭部的location或refresh字段值。off參數表示禁止所有的proxy_redirect指令。
upstream上游服務器組
假設Nginx只有反向代理沒有負載均衡,它的價值會大打折扣。
Nginx在配置反向代理時可以通過負載均衡機制配置一個上游服務器組(多台上游服務器)。當組內的某台服務器宕機時仍能保持系統可用,從而實現高可用。
Nginx的負載均衡配置主要用到upstream(上游服務器組)指令,其格式如下:
語法:upstream name { … }
上下文:http配置塊
upstream指令後面的name參數是上游服務器組的名稱;upstream塊中將使用server指令定義組內的上游候選服務器。
upstream指令的作用與server有點類似,其功能是加入一個特殊的虛擬主機server節點。特殊之處在於這是上游server服務組,可以包含一個或者多個上游server。
一個upstream負載均衡主機節點的配置實例如下:
#upstream負載均衡虛擬節點
upstream balanceNode {
server "192.168.1.2:8080"; #上游候選服務1
server "192.168.1.3:8080"; #上游候選服務2
server "192.168.1.4:8080"; #上游候選服務3
server "192.168.1.5:8080"; #上游候選服務4}實例中配置的balanceNode相當於一個主機節點,不過這是一個負載均衡類型的特定功能虛擬主機。當請求過來時,balanceNode主機節點的作用是按照默認負載均衡算法(帶權重的輪詢算法)在4個上游候選服務中選取一個進行請求轉發。
實戰案例:在隨書源碼的nginx-proxy-demo.conf配置文件中配置3個server主機和一個upstream負載均衡主機組。此處配置了一個location塊,將目標端口為80的請求反向代理到upstream主機組,以方便負載均衡主機的行為。
實戰案例的配置代碼節選如下:
#負載均衡主機組,給虛擬主機1與虛擬主機2做負載均衡
upstream balance {
server "127.0.0.1:8080"; #虛擬主機1
server "127.0.0.1:8081"; #虛擬主機2
}
#虛擬主機1
server {
listen 8080;
server_name localhost;
location / {
echo "server port:8080" ;
}
}
#虛擬主機2
server {
listen 8081 ;
server_name localhost;
location / {
echo "server port:8081" ;
}
}
#虛擬主機3:默認虛擬主機
server {
listen 80 default;
...
#負載均衡測試連接
location /balance {
proxy_pass http://balance; #反向代理到負載均衡節點
}
}運行本小節的實例前需要修改啟動腳本openresty-start.bat(或openresty-start.sh)中的PROJECT_CONF變量的值,將其改為nginx-proxy-demo.conf,然後重啟OpenRestry/Nginx。
在CentOS服務器中使用curl命令請求
http://192.168.233.1/balance鏈接地址(IP根據Nginx情況而定),並且多次發起請求,就會發現虛擬主機1和虛擬主機2被輪流訪問到,具體的輸出如下:
[root@localhost ~]#curl http://192.168.233.1/balance
server port:8080
[root@localhost ~]#curl http://192.168.233.1/balance
server port:8081
[root@localhost ~]#curl http://192.168.233.1/balance
server port:8080
[root@localhost ~]#curl http://192.168.233.1/balance
server port:8081
[root@localhost ~]#curl http://192.168.233.1/balance
server port:8080通過結果可以看出,upstream負載均衡指令起到了負載均衡的效果。默認情況下,upstream會依照帶權重的輪詢方式進行負載分配,每個請求按請求順序逐一分配到不同的上游候選服務器。
upstream的上游服務器配置
upstream塊中將使用server指令定義組內的上游候選服務器。內部server指令的語法如下:
語法:server address [parameters];
上下文:upstream配置塊
此內嵌的server指令用於定義上游服務器的地址和其他可選參數,它的地址可以指定為域名或IP地址帶有可選端口,如果未指定端口,就使用端口80。
內嵌的server指令的可選參數大致如下:
(1)weight=number(設置上游服務器的權重):默認情況下,upstream使用加權輪詢(Weighted Round Robin)負載均衡方法在上游服務器之間分發請求。weight值默認為1,並且各上游服務器的weight值相同,表示每個請求按先後順序逐一分配到不同的上游服務器,如果某個上游服務器宕機,就自動剔除。
如果希望改變某個上游節點的權重,就可以使用weight顯式進行配置,參考實例如下:
#負載均衡主機組
upstream balance {
server "127.0.0.1:8080" weight=2; #上游虛擬主機1,權重為2
server "127.0.0.1:8081" weight=1; #上游虛擬主機2,權重為1
}權重越大的節點,將被分發到更多請求。
(2)max_conns=number(設置上游服務器的最大連接數):
max_conns參數限制到上游節點的最大同時活動連接數。默認值為零,表示沒有限制。如果upstream服務器組沒有通過zone指令設置共享內存,那麼在單個Worker工作進程範圍內對上游服務的最大連接數進行限制;如果upstream服務器組通過zone指令設置了共享內存,那麼在全體的Worker工作進程範圍內對上游服務進行統一的最大連接數限制。
(3)backup(可選參數):backup參數標識該server是備份的上游節點,當普通的上游服務(非backup)不可用時,請求將被轉發到備份的上游節點;當普通的上游服務(非backup)可用時,備份的上游節點不接受處理請求。
(4)down(可選參數):down參數標識該上游server節點為不可用或者永久下線的狀態。
(5)max_fails=number(最大錯誤次數):如果上游服務不可訪問了,如何判斷呢?max_fails參數是其中之一,該參數表示請求轉發最多失敗number次就判定該server為不可用。max_fails參數的默認次數為1,表示轉發失敗1次,該server即不可用。如果此參數設置為0,就會禁用不可用的判斷,一直不斷地嘗試連接後端server。
(6)fail_timeout=time(失敗測試的時間長度):這是一個失效監測參數,一般與上面的參數max_fails協同使用。fail_timeout的意思是失敗測試的時間長度,指的是在fail_timeout時間範圍內最多嘗試max_fails次,就判定該server為不可用。fail_timeout參數的默認值為10秒。
server指令在進行max_conns連接數配置時,Nginx內部會涉及共享內存區域的使用,配置共享內存區域的指令為zone,其具體語法如下:語法:zone name [size];
上下文:upstream配置塊
zone的name參數設置共享內存區的名稱,size可選參數用於設置共享內存區域的大小。如果配置了upstream的共享內存區域,那麼其運行時狀態(包括最大連接數)在所有的Worker工作進程之間是共享的。在name相同的情況下,不同的upstream組將共享同一個區,這種情況下,size參數的大小值只需設置一次。
下面是一個server指令和zone指令的綜合使用實例:
upstream zuul {
zone upstream_zuul 64k; //名稱為upstream_zuul,大小為64KB的共享內存區域
server "192.168.233.128:7799" weight=5 max_conns=500;
server "192.168.233.129:7799" fail_timeout=20s max_fails=2; //默認權重為1
server "192.168.233.130:7799" backup; //後備服務
}upstream的負載分配方式
upstream大致有3種負載分配方式,下面一一介紹。
1.加權輪詢
默認情況下,upstream使用加權輪詢(Weighted Round Robin)負載均衡方法在上游服務器之間分發請求,默認的權重weight值為1,並且各上游服務器weight值相同,表示每個請求按到達的先後順序逐一分配到不同的上游服務器,如果某個上游服務器宕機,就自動剔除。
指定權重weight值,weight和分配比率成正比,用於後端服務器性能不均的情況。下面是一個簡單的例子:
upstream backend {
server 192.168.1.101 weight=1;
server 192.168.1.102 weight=2;
server 192.168.1.103 weight=3;
}2.hash指令
基於hash函數值進行負載均衡,hash函數的key可以包含文本、變量或二者的組合。hash函數負載均衡是一個獨立的指令,指令的格式如下:
語法:hash key [consistent];
上下文:upstream配置塊注意,如果upstream組中摘除掉一個server,就會導致hash值重
新計算,即原來的大多數key可能會尋址到不同的server上。若配置有consistent參數,則hash一致性將選擇Ketama算法。這個算法的優勢是,如果有server從upstream組裡摘除掉,那麼只有少數的key會重新映射到其他的server上,即大多數key不受server摘除的影響,還走到原來的server。這對提高緩存server命中率有很大幫助。下面是一個簡單的通過請求的$request_uri的hash值進行負載均衡的例子:
upstream backend {
hash $request_uri consistent;
server 192.168.1.101 ;
server 192.168.1.102 ;
server 192.168.1.103 ;
}3.ip_hash指令
基於客戶端IP的hash值進行負載平衡,這樣每個客戶端固定訪問同一個後端服務器,可以解決類似session不能跨服務器的問題。如果上游server不可用,就需要手工摘除或者配置down參數。ip_hash是一條獨立的指令,其使用的示例如下:
upstream backend {
ip_hash;
server 192.168.1.101:7777;
server 192.168.1.102:8888;
server 192.168.1.103:9999;
}原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/284415.html
微信掃一掃
支付寶掃一掃