1. 程式人生 > >Nginx負載均衡與反向代理——擴充套件功能(NGINX Plus)

Nginx負載均衡與反向代理——擴充套件功能(NGINX Plus)

本文主要是介紹了NGINX Plus的相關功能,橫跨了NGINX Plus R5/R6/R7/R9等各個不同版本的更新。涉及的是Nginx反向代理和負載均衡的更為高階的用法。主要包含:HTTP負載均衡,HTTP長連線,TCP和UDP的負載均衡,上游連線數限制,最短時間的均衡演算法,Session一致性,實時健康檢查,DNS重解析,訪問控制,客戶端連線數限制,客戶端頻寬限制,無緩衝上傳檔案,SSL/TLS優化,,快取優化,API功能,配置的最佳實踐等。

什麼是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長連線——HTTP Keepalives,是指NGINX PLus和上游伺服器建立的長連線。客戶端和NGINX PLus建立長連線的話,可以在客戶端指定Http協議為1.1/2.0。

HTTP協議是用的底層TCP協議來傳輸請求,接收響應的。HTTP1.1/2.0支援TCP的長連線或者重利用,以免反覆的建立和銷燬TCP連線所帶來的開銷。

我們看看客戶端到NGINX PLus之間的Http長連線:

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負載均衡功能。

上游連線數限制

你還可以為負載均衡做連線數量限制。這裡說的連線是指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的相對安全。

最少時間的均衡演算法

在NGINX Plus R6中增加了一種新的均衡演算法——Least Time,將相應時間也考慮進去,算得上對最少連線均衡演算法(Least Connections)的擴充套件。

這種演算法同時考慮當前連線數和連線池裡各個節點的平均響應時間。目的是使得當前請求選擇當下響應更快、連線更少的伺服器,而不是選擇響應更慢、連線更多的。

當連線池的各個伺服器節點有著明顯不同的響應延時時,這種演算法就要優於其他的幾種(round-robin/ip-hash/lease connections)。一個典型的應用場景是,如果有兩個分佈在不同的地域的資料中心,那麼本地的資料中心就要比異地的資料中心延時要少得多,這個時候就不能僅僅考慮當下連線數了,這個響應的延時也要被計入考量。Least Time演算法就更傾向於選擇本地的,當然這只是“更傾向於”的問題,並不能代替Nginx最基本的錯誤轉移功能,哪怕本地的資料中心響應再快,如果它掛掉了Nginx Plus也能馬上切換到遠端資料中心。

這裡寫圖片描述

“最少時間”可以有兩種計算方式,一種是從請求發出到上流伺服器接返回響應頭部算的時間,另一種是從請求發出到接收到全部請求體的時間,分別以header_timeresponse_time來表示。

Session一致性

Session一致性(Session Persistence)問題除了可以通過指定ip-hash的均衡演算法來實現,還有更為通用的實現方式,這是在NGINX Plus 中實現的。

NGINX Plus可以識別使用者Session,從而能夠鑑別不同的客戶端,並且可以將來自同一個客戶端的請求發往同一個上游伺服器。這在當應用儲存了使用者狀態的情況下非常有用,可以避免負載均衡器按照某個演算法將請求發到別的伺服器上去。另外,在共享使用者資訊的叢集伺服器這種方式也非常有用。

session一致性的要求同一個客戶端每次的請求都選擇同一個伺服器,而負載均衡要求我們利用一種演算法去伺服器連線池裡面去選擇下一個,那麼這兩種矛盾的方式可以共存麼?可以的,NGINX Plus按照如下的步驟決策到底選用哪一種:

  • 如果request匹配某個Session一致性的規則,那麼根據這個規則選取上游伺服器;
  • 如果沒有匹配上或者匹配的伺服器無法使用,那麼使用負載均衡演算法選擇上游伺服器;

為了能保證session一致性,Nginx Plus提供了sticky cookie,sticky learn和sticky route幾種規則。

對於 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_idcookie,用來“記住”這第一次請求是發個哪個上游的,後面的請求帶上這個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在JSESSIONIDcookie中選擇,如其包含a那麼選擇伺服器backend1;如其包含b則選擇backend2,如果都不包含那麼在$request_uri 中再做類似的選擇,以此類推。

不管是選哪種方式保持session一致,如果選擇出的server無法使用,那麼將會按照負載均衡演算法(如round-robin)在伺服器列表中的選擇下一臺server繼續處理。

實時健康檢查

前面提到過,Nginx有兩大功能:一個是擴充套件,增加更多的server以滿足更大的併發;二是檢測失效server,以便及時排除。那麼,如何定義一個“失效server”(failed server)就變得非常重要。這一節就是來討論這個問題——實時健康檢查(Active Health Checks)。這是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_checkinterval=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伺服器發起一次重新解析的請求。

訪問控制

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錯誤。

客戶端連線數限制

除了可以限定上游伺服器連線數,還可以限定客戶端連線數,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同時只能有一個連線。

客戶端頻寬限制

R7 還新增了一項功能——限制每個客戶端連線的上傳和下載的最大頻寬(Bandwidth Limiting) 。

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請求的配置。沒有統一的標準,主要是看開發者自己能便於識別和修改。