5、Nginx HTTP負載均衡和反向代理的配置與優化

5.1 什麼是負載均衡和反向代理

隨着網站訪問量的快速增加,單臺服務器已經沒法承擔大量用戶的併發訪問,必須採用多臺服務器協同工做,以提升計算機系統的處理能力和計算強度,知足當前業務量的需求。而如何在完成一樣的功能的多個網絡設備之間實現合理的業務量的分配,使之不會出現一臺設備過忙、而其餘的設備卻沒有充分使用的狀況。要解決這一問題,能夠採用負載均衡的方法。javascript

5.1.1 負載均衡

負載均衡是由多臺服務器以對稱的方式組成一個服務器集合,每臺服務器都具備等價的地位,均可以單獨對外提供服務而無須其餘服務器的輔助。經過負載均衡分擔技術,將外部發送來的請求均勻分配到對稱結構中的某一臺服務器上,而接收到請求的服務器獨立的迴應客戶的請求。負載均衡可以平均分配客戶請求到服務器陣列,藉此快速獲取重要數據,解決大量併發訪問服務問題。這種羣集技術能夠用最少的投資得到接近於大型主機的性能。css

5.1.2 反向代理

反向代理(Revserse Proxy)是指以代理服務器來接受Internet上的鏈接請求,而後將請求轉發給內部網絡上的服務器,並將從服務器上獲得的結果返回給Internet上請求鏈接的客戶端,此時代理服務器對外就表現爲一個服務器。java

一般的代理服務器,只用於代理內部網絡對Internet的鏈接請求,客戶端必須指定代理服務器,並將原本要直接發送到Web服務器上的Http請求發送到代理服務器中。因爲外部網絡上的主機並不會配置並使用這個代理服務器,普通代理服務器也被設計爲在Internet上搜尋多個不肯定的服務器,而不是針對Internet上多個客戶端的請求訪問某一個固定的服務器,所以普通的Web代理服務器不支持外部對內部網絡的訪問請求,當一個代理服務器可以代理外部網絡上的主機訪問內部網絡時,這種代理服務器的方式成爲反向代理服務。此時代理服務器對外就表現爲一個Web服務器,外部網絡就能夠簡單把它當作一個標準的Web服務器而不須要特定的配置。不一樣之處在於,這個服務器沒有保存任何網頁的真實數據,全部的靜態網頁或CGI程序,都保存在內部的Web服務器上。所以對反向代理服務器的攻擊並不會使網頁信息遭到破壞,這樣就加強了Web服務器的安全性。nginx

反向代理方式和包過濾方式或普通代理方式並沒有衝突,所以能夠再防火牆設備中同時使用這兩種方式,其中反向代理用於外部網絡訪問內部網絡時使用,正向代理或包過濾方式用於拒絕其餘外部訪問方式並提供內部網絡對外部網絡的訪問能力。所以,能夠結合這些方式提供最佳的安全訪問方式。web

5.2  Nginx負載均衡與反向代理的配置實例

5.2.1 完整的nginx反向代理示例

#負責壓縮數據流
gzip              on;  
gzip_min_length   1000;  
gzip_types        text/plain text/css application/x-javascript;

#設定負載均衡的服務器列表
#weigth參數表示權值,權值越高被分配到的概率越大
upstream hello{
    server 192.168.68.43:8080 weight=1;
    server 192.168.68.45:8080 weight=1;            
}
   
server {
    #偵聽的80端口
    listen       80;
    server_name  localhost;
    #設定查看Nginx狀態的地址
    location /nginxstatus{
         stub_status on;
         access_log on;
         auth_basic "nginxstatus";
         auth_basic_user_file htpasswd;
    }
    #匹配以jsp結尾的,tomcat的網頁文件是以jsp結尾
    location / {
        index index.jsp;
        proxy_pass   http://hello;    #在這裏設置一個代理,和upstream的名字同樣
        #如下是一些反向代理的配置可刪除
        proxy_redirect             off; 
        #後端的Web服務器能夠經過X-Forwarded-For獲取用戶真實IP
        proxy_set_header           Host $host; 
        proxy_set_header           X-Real-IP $remote_addr; 
        proxy_set_header           X-Forwarded-For $proxy_add_x_forwarded_for; 
        client_max_body_size       10m; #容許客戶端請求的最大單文件字節數
        client_body_buffer_size    128k; #緩衝區代理緩衝用戶端請求的最大字節數
        proxy_connect_timeout      300; #nginx跟後端服務器鏈接超時時間(代理鏈接超時)
        proxy_send_timeout         300; #後端服務器數據回傳時間(代理髮送超時)
        proxy_read_timeout         300; #鏈接成功後,後端服務器響應時間(代理接收超時)
        proxy_buffer_size          4k; #設置代理服務器(nginx)保存用戶頭信息的緩衝區大小
        proxy_buffers              4 32k; #proxy_buffers緩衝區,網頁平均在32k如下的話,這樣設置
        proxy_busy_buffers_size    64k; #高負荷下緩衝大小(proxy_buffers*2)
        proxy_temp_file_write_size 64k; #設定緩存文件夾大小,大於這個值,將從upstream服務器傳
    }
}

經過上述實例,咱們已經看到nginx對於多個域名的負載均衡是如何配置的。Upstream指令用於設置一組能夠在Proxy_pass和fastcgi_pass指令中使用的代理服務器,默認的負載均衡方式爲輪詢。Upstream模塊中的Server指令用於制定後端服務器的名稱和參數,服務器的名稱可使一個域名、一個IP地址、端口號或UNIX Socket算法

而在Server{…}虛擬主機內,能夠經過proxy_pass和fastcgi_pass指令設置進行反向代理的upstream服務器集羣。shell

proxy_set_header指令用於在向反向代理的後端Web服務器發起請求時添加指定的Header頭信息。後端

當後端Web服務器上有多個基於域名的虛擬主機時,要經過添加Header頭信息Host,用於指定請求的域名,這樣後端Web服務器才能識別核反向代理訪問請求由哪個虛擬主機來處理緩存

使用反向代理以後,後端Web服務器(以PHP爲例)就不能直接經過$_SERVER[「REMOTE_ADDR」]變量來獲取用戶的真實IP了,經過$_SERVER[「REMOTE_ADDR」]獲取的將是Nginx負載均衡服務器的IP。這時,就要經過在nginx反向代理時添加Header頭信息X-Forwarded-For,讓後端Web服務器可以經過$_SERVER9[「HTTP_X_FORWARDED_FOR」]獲取用戶的真實IP。tomcat

5.2.2 Nginx負載均衡與反向代理實現動、靜態網頁分離

動、靜態網頁分離,就是讓動態JSP等程序網頁去訪問JSP WEB服務器,讓緩存頁、圖片、JavaScript、CSS、Flash去訪問Squid的緩存服務器。

5.3 Nginx負載均衡的HTTP Upstream模塊

upstream模塊是Nginx負載均衡的主要模塊,它提供了一個簡單方法來實現輪詢和客戶端之間的後端服務器負載均衡,並能夠對後端服務器進行健康檢查。如代碼:

upstream backend{
server backend1.example.com   weight = 5;
server backend2.example.com:8080;
server unix:/tmp/backend3;
}
server{
location / {
proxy_pass http://backend;
}
}

5.3.1 ip_hash指令

ip_hash;

語法:ip_hash
默認值:none
使用環境:upstream

當對後端的多臺動態應用服務器作負載均衡時,ip_hash指令可以將某個客戶端IP的請求經過哈希算法定位到同一臺後端服務器上。這樣,當來自某個IP的用戶在後端Web服務器A上登陸後,在訪問其餘站點的其餘URL,能保證其訪問的仍是後端Web服務器A。若是不採用ip_hash指令,假設來自某個,IP的用戶在後端Web服務器A上登錄後,在訪問該站點的其餘URL,有可能被定向到後端Web服務器B,C….上,因爲用戶登陸後SESSION信息是記錄在服務器A上的,B,C上沒有,這時就會提示用戶未登陸。使用ip_hash指令沒法保證後端服務器的負載均衡,可能有些後端服務器接收到的請求多,有些後端服務器接收到的請求少,並且設置後端服務器權重等方法將不起做用。因此,若是後端的動態應用服務器可以作到SESSION共享,仍是建議採用後端服務器的SESSION共享方式來代替Nginx的ip_hash方式。

若是後端服務器有時要從Nginx負載均衡(已使用ip_hash)中摘除一段時間,你必須將其標記爲「down」,而不是直接從配置文件中刪除或註釋掉該後端服務器的信息。如代碼:

upstream backend {
ip_hash;
server backend1.example.com;
server backend2.example.com;
server backend3.example.com down;
}

這樣,當原來爲4臺後端服務器時,摘除backend3.example.com(標記爲「down」)後,nginx仍然會按4臺服務器進行哈希。若是直接註釋掉「server backend3.example.com」這行,nginx就會按照3臺服務器進行從新哈希,原來被哈希到的backend1.example.com的客戶端IP有可能被哈希到backend2.example.com服務器上,原有的SESSION就會失效。

5.3.2 server指令

server

語法:server name [parameters]
默認值:none
使用環境:upstream

該指令用於指定後端服務器的名稱和參數。服務器名稱能夠是一個域名,一個IP地址、端口號或UNIX Socket。

在後端服務器名稱以後,能夠跟如下參數:

weight=NUMBER—設置服務器的權重,權重值越高,被分配到的客戶端請求數越多。若是沒有設置權重,則爲默認權重1.

max_fails=NUMBER—在參數fail_timeout指定的時間內對後端服務器請求的失敗次數,若是檢測到後端服務器沒法連接及發生服務器錯誤(404除外),則標記爲失敗。若是沒有設置,則爲默認值1.設爲數值0將關閉這項檢查。

fail_timeout=Time—在經歷參數max_fails設置次數後,暫停的時間。

down—標記服務器爲永久離線狀態,用於ip_hash指令。

backup—僅在非backup服務器所有宕機或繁忙時的時候才啓用。

示例以下:

upstream backend{
server backend1.example.com weight=5;
server 127.0.0.1:8080 max_fails=3  fail_timeout=30s;
server unix:/tmp/backend3;
}

5.3.3 upstream指令

upstream

語法:upstream name{……}
默認值:none
使用環境:http

該指令用於設置一組能夠在proxy_pass和fastcgi_pass指令中使用代理服務器,默認的負載均衡方式爲輪詢。示例以下:

upstream backend{
server backend1.example.com weight=5;
server 127.0.0.1:8080  max_fails=3   fail_timeout=30s;
server unix:/tmp/backend3;
}

5.3.4 upstream 相關變量

從nginx0.5.18版本開始,能夠支持用log_format指令設置日誌格式,日誌格式中可使用變量,例如:

log_format timing ‘$remote_addr - $remote_user [$time_local]         $request ‘
    ‘upstream_response_time $upstream_response_time ‘
    ‘msec $msec request_time $request_time’;
log_format up_head ‘$remote_addr - $remote_user [$time_local] $request ‘
‘upstream_http_content_type $upstream_http_content_type’;

upstream模塊擁有如下變量:

$upstream_addr  :  處理請求的upstream服務器地址

$upstream_status  :  Upstream服務器的應答狀態。

$upstream_response_time  :  upstream服務器響應時間(毫秒),多個響應以逗號和冒號分割。

$upstream_http_$HEADER:任意的HTTP協議頭信息,例如:$upstream_http_host

5.4 Nginx負載均衡服務器的雙機高可用

     若是將Web服務器集羣當作一個城池,那麼負載均衡服務器則至關於城門,重要性不言而喻。若是「城門」關閉了,與外界的通道也就掐斷了。若是隻有一臺Nginx負載均衡服務器,當該服務器發生故障時,則會致使整個網站沒法訪問。所以,咱們須要兩臺以上的Nginx負載均衡服務器,實現故障轉移與高可用。

      雙機高可用通常是經過虛擬IP(也稱漂移IP)方式來實現的,基於LInux/Unix的IP別名技術,雙機高可用方式目前可分爲兩種:第一種方式爲一臺主服務器加一臺熱備服務器,正常狀況下主服務器綁定一個公網虛擬IP,提供負載均衡服務,熱備服務器處於空閒狀態,當主服務器發生故障時,熱備服務器接管主服務器的虛擬IP,提供負載均衡服務;第二種方式爲兩臺負載均衡服務器都處於活動狀態,各自綁定一個公網虛擬IP,提供負載均衡服務,當其中一臺服務器發生故障時,另外一臺服務器接管發生故障的虛擬IP。第一種方式較爲常見,但始終有一臺服務器處於空閒狀態,浪費了一臺服務器的負載均衡處理能力。第二種方式須要多用一個公網IP。

第一種方式

(1)www.yourdomain.com域名解析到虛擬IP 61.1.1.2上

(2)正常狀況下,主機61.1.1.4綁定虛擬IP61.1.1.2

/sbin/ifconfig eth0:1 61.1.1.2 broadcast 61.1.1.255 netmask 255.255.255.0 up
/sbin/route add –host 61.1.1.2 dev eth0:1
/sbin/arping -I eth0 –c 3  -s 61.1.1.2 61.1.1.1

(3)用戶訪問www.yourdomain.com(虛擬IP61.1.1.2)實際訪問的是61.1.1.4,而備機61.1.1.5則處於空閒狀態

(4)若是主機61.1.1.4發生故障,備機61.1.1.5將在幾秒鐘內接管虛擬iP61.1.1.2,與本身綁定,併發送ARPing給IDC的公網網關刷新MAC地址。

/sbin/ifconfig eth0:1 61.1.1.2 broadcast 61.1.1.255 netmask 255.255.255.0 up

/sbin/route add –host 61.1.1.2 dev eth0:1

/sbin/arping –I eth0 –c 3 –s 61.1.1.2 61.1.1.1

(5)這時,用戶訪問www.yourdomain(虛擬IP 61.1.1.2)實際上訪問的是備機 61.1.15,從而實現故障轉移與高可用,避免了單點故障。

第二種方式

(1)www.yourdomain.com    域名經過DNS輪詢解析到虛擬IP61.1.1.2和        61.1.1.3上

(2)正常狀況下,服務器1:61.1.1.4綁定虛擬IP61.1.1.2,服務器2:61.1.1.5綁定虛擬IP61.1.1.3。

(3)在服務器1:61.1.1.4上執行如下命令:

/sbin/ifconfig eth0:1 61.1.1.2 broadcast 61.1.1.255 netmask 255.255.255.0 up
/sbin/route add –host 61.1.1.2 dev eth0:1
/sbin/arping –I eth0 –c 3 –s 61.1.1.2 61.1.1.1

在服務器2:61.1.1.5上執行如下命令:

/sbin/ifconfig  eth0:1  61.1.1.3 broadcast 61.1.1.255 netmask 255.255.255.0 up
/sbin/route add –host 61.1.1.3 dev eth0:1
/sbin/arping –I eth0 –c 3 –s 61.1.1.3 61.1.1.1

(3)用戶訪問www.yourdomain.com(虛擬IP61.1.1.2和61.1.1.3)其實是根據DNS輪詢訪問兩臺負載均衡服務器的,兩臺服務器均處於活動狀態。

(4)若是服務器1發生故障,服務器2將在幾秒鐘內接管服務器1的虛擬iP61.1.1.2,與本身綁定,併發送ARPing包給IDC的公網網關刷新MAC地址。這時,服務器2同時綁定61.1.1.2和61.1.1.3兩個虛擬IP。

在服務器2:61.1.1.5上執行如下命令:

/sbin/ifconfig eth0:1 61.1.1.2 broadcast 61.1.1.255 netmask 255.255.255.0 up
/sbin/route add –host 61.1.1.2 dev eth0:1
/sbin/arping –I eth0 –c 3 –s 61.1.1.2  61.1.1.1

咱們能夠寫兩個shell腳本,來實現第二種方式的自動故障轉移。

如下附件爲腳本1(nginx_ha1.sh),部署在nginx負載均衡服務器1:

#!/bin/sh
LANG=C
date=$(date -d "today" +"%Y-%m-%d %H:%M:%S")

Function_bind_vip1()
{
/sbin/ifconfig eth0:ha1 61.1.1.2 broadcast 219.232.254.255 netmask 255.255.255.0 up
/sbin/route add -host 61.1.1.2 dev eth0:ha1
}

Function_bind_vip2()
{
/sbin/ifconfig eth0:ha2 61.1.1.3 broadcast 219.232.254.255 netmask 255.255.255.0 up
/sbin/route add -host 61.1.1.3 dev eth0:ha2
}

Function_restart_nginx()
{
kill -USR1 'cat /usr/local/webserver/nginx/nginx.pid'
}

Function_remove_vip1()
{
/sbin/ifconfig eth0:ha1 61.1.1.2 broadcast 219.232.254.255 netmask 255.255.255.192 down
}

Function_remove_vip2()
{
/sbin/ifconfig eth0:ha2 61.1.1.3 broadcast 219.232.254.255 netmask 255.255.255.0 down
}

Function_vip_arping1()
{
/sbin/arping -I eth0 -c 3 -s 61.1.1.2 61.1.1.1 > /dev/null 2>&1
}

Function_vip_arping2()
{
/sbin/arping -I eth0 -c 3 -s 61.1.1.3 61.1.1.1 > /dev/null 2>&1
}

bind_time_vip1="N";
bind_time_vip2="N";

while true
do
  httpcode_rip1='/usr/bin/curl -o /dev/null -s -w %{http_code} http://61.1.1.4'
  httpcode_rip2='/usr/bin/curl -o /dev/null -s -w %{http_code} http://61.1.1.5'
if [x$httpcode_rip1 == "x200" ];
then
   if [$bind_time_vip1 == "N" ];
   then
      function_bind_vip1
      function_vip_arping1
      function_restart_nginx
      bind_time_vip1="Y"

    fi
    function_vip_arping1
   else
     if [$bind_time_vip1 == "Y" ];
     then
        function_remove_vip1
        bind_time_vip1="N"
      fi
   fi
 if [x$httpcode_rip2 == "x200" ];
 then
      if [ $bind_time_vip2 == "Y" ];
      then
         function_remove_vip2
         bind_time_vip2="N"
      fi
   else
      if [ $bind_time_vip2 == "N" ];
      then
          function_bind_vip2
          function_vip_arping2
          function_restart_nginx
          bind_time_vip="Y"
      fi
      function_vip_arping2
      fi
      
      sleep 5

done

在nginx負載均衡服務器1將腳本駐留後臺運行:

nohup /bin/sh ./nginx_ha1.sh 2>&1 > /dev/null &

如下代碼爲腳本2(server2.sh),部署在nginx負載均衡服務器2:

#!/bin/sh
LANG=C
date=$(date -d "today" +"%Y-%m-%d %H:%M:%S")

function_bind_vip1()
{
/sbin/ifconfig eth0:ha1 61.1.1.3 broadcast 219.232.254.255 netmask 255.255.255.192 up
/sbin/route add -host 61.1.1.3 dev eth0:ha1
}

function_bind_vip2()
{
/sbin/ifconfig eth0:ha2 61.1.1.2 broadcast 219.232.254.255 netmask 255.255.255.0 up
/sbin/route add -host 61.1.1.2 dev eth0:ha2
}

function_restart_nginx()
{
kill -USR1 'cat /usr/local/webserver/nginx/nginx.pid'
}

function_remove_vip1()
{
/sbin/ifconfig eth0:ha1 61.1.1.3 broadcast 219.232.254.255 netmask 255.255.255.192 down
}

function_remove_vip2()
{
/sbin/ifconfig eth0:ha2 61.1.1.2 broadcast 219.232.254.255 netmask 255.255.255.192 down
}

function_vip_arping1()
{
/sbin/arping -I eth0 -c 3 -s 61.1.1.3 61.1.1.1 > /dev/null 2>&1
}

function_vip_arping2()
{
/sbin/arping -I eth0 -c 3 -s 61.1.1.2 61.1.1.1 > /dev/null 2>&1
}

bind_time_vip1="N";
bind_time_vip2="N";

while true
do
      httpcode_rip1='/usr/bin/curl -o /dev/null -s -w %{http_code} http://61.1.1.5'
      httpcode_rip2='/usr/bin/curl -o /dev/null -s -w %{http_code} http://61.1.1.4'


      if [x$httpcode_rip1 == "x200" ];
      then
         if [ $bind_time_vip1 == "N" ];
         then
            function_bind_vip1
            function_vip_arping1
            function_restart_nginx
            bind_time_vip1="Y"
         fi
         function_vip_arping1
       else
         if [ $bind_time_vip1 == "Y" ];
         then
            function_remove_vip1
            bind_time_vip1="N"
         fi
       fi
       if [ x$httpcode_rip2 == "x200" ];
       then
          if [ $bind_time_vip2 == "Y" ];
          then
          function_remove_vip2
          bind_time_vip2="N"
        fi
      else
        if [ $bind_time_vip2 == "N" ];
      then
           function_bind_vip2
           function_vip_arping2
           function_restart_nginx
           bind_time_vip2="Y"
         fi
           function_vip_arping2
       fi

       sleep 5

done

在nginx負載均衡服務器2將腳本駐留後臺運行:

nohup /bin/sh ./nginx_ha2.sh  2>&1 >/dev/null  &
相關文章
相關標籤/搜索