Nginx-負載均衡實現解讀
負載均衡在服務端開發中算是一個比較重要的特性。因為Nginx除了作為常規的Web服務器外,還會被大規模的用於反向代理前端,因為Nginx的異步框架可以處理很大的並發請求,把這些並發請求hold住之後就可以分發給後臺服務端(backend servers,也叫做服務池, 後面簡稱backend)來做復雜的計算、處理和響應,這種模式的好處是相當多的:隱藏業務主機更安全,節約了公網IP地址,並且在業務量增加的時候可以方便地擴容後臺服務器。
負載均衡可以分為硬件負載均衡和軟件負載均衡,前者一般是專用的軟件和硬件相結合的設備,設備商會提供完整成熟的解決方案,通常也會更加昂貴。軟件的復雜均衡以Nginx占據絕大多數,本文也是基於其手冊做相應的學習研究的。
一、基本簡介
負載均衡涉及到以下的基礎知識。
(1) 負載均衡算法
a. Round Robin: 對所有的backend輪訓發送請求,算是最簡單的方式了,也是默認的分配方式;
b. Least Connections(least_conn): 跟蹤和backend當前的活躍連接數目,最少的連接數目說明這個backend負載最輕,將請求分配給他,這種方式會考慮到配置中給每個upstream分配的weight權重信息;
c. Least Time(least_time): 請求會分配給響應最快和活躍連接數最少的backend;
d. IP Hash(ip_hash): 對請求來源IP地址計算hash值,IPv4會考慮前3個octet,IPv6會考慮所有的地址位,然後根據得到的hash值通過某種映射分配到backend;
e. Generic Hash(hash): 以用戶自定義資源(比如URL)的方式計算hash值完成分配,其可選consistent關鍵字支持一致性hash特性;
(2) 會話一致性
用戶(瀏覽器)在和服務端交互的時候,通常會在本地保存一些信息,而整個過程叫做一個會話(Session)並用唯一的Session ID進行標識。會話的概念不僅用於購物車這種常見情況,因為HTTP協議是無狀態的,所以任何需要邏輯上下文的情形都必須使用會話機制,此外HTTP客戶端也會額外緩存一些數據在本地,這樣就可以減少請求提高性能了。如果負載均衡可能將這個會話的請求分配到不同的後臺服務端上,這肯定是不合適的,必須通過多個backend共享這些數據,效率肯定會很低下,最簡單的情況是保證會話一致性——相同的會話每次請求都會被分配到同一個backend上去。
(3) 後臺服務端的動態配置
出問題的backend要能被及時探測並剔除出分配群,而當業務增長的時候可以靈活的添加backend數目。此外當前風靡的Elastic Compute雲計算服務,服務商也應當根據當前負載自動添加和減少backend主機。
(4) 基於DNS的負載均衡
通常現代的網絡服務者一個域名會關連到多個主機,在進行DNS查詢的時候,默認情況下DNS服務器會以round-robin形式以不同的順序返回IP地址列表,因此天然將客戶請求分配到不同的主機上去。不過這種方式含有固有的缺陷:DNS不會檢查主機和IP地址的可訪問性,所以分配給客戶端的IP不確保是可用的(Google 404);DNS的解析結果會在客戶端、多個中間DNS服務器不斷的緩存,所以backend的分配不會那麽的理想。
二、Nginx中的負載均衡
Nginx中的負載均衡配置在手冊中描述的極為細致,此處就不流水帳了。對於常用的HTTP負載均衡,主要先定義一個upstream作為backend group,然後通過proxy_pass/fastcgi_pass等方式進行轉發操作,其中fastcgi_pass幾乎算是Nginx+PHP站點的標配了。
2.1 會話一致性
Nginx中的會話一致性是通過sticky開啟的,會話一致性和之前的負載均衡算法之間並不沖突,只是需要在第一次分配之後,該會話的所有請求都分配到那個相同的backend上面。目前支持三種模式的會話一致性:
(1). Cookie Insertion
在backend第一次response之後,會在其頭部添加一個session cookie,即由負載均衡器向客戶端植入 cookie,之後客戶端接下來的請求都會帶有這個cookie值,Nginx可以根據這個cookie判斷需要轉發給哪個backend了。
sticky cookie srv_id expires=1h domain=.example.com path=/;
上面的srv_id代表了cookie的名字,而後面的參數expires、domain、path都是可選的。
(2). Sticky Routes
也是在backend第一次response之後,會產生一個route信息,route信息通常會從cookie/URI信息中提取。
sticky route $route_cookie $route_uri;
這樣Nginx會按照順序搜索routecookie、route_uri參數並選擇第一個非空的參數用作route,而如果所有的參數都是空的,就使用上面默認的負載均衡算法決定請求分發給哪個backend。
(3). Learn
較為的復雜也較為的智能,Nginx會自動監測request和response中的session信息,而且通常需要回話一致性的請求、應答中都會帶有session信息,這和第一種方式相比是不用增加cookie,而是動態學習已有的session。
這種方式需要使用到zone結構,在Nginx中zone都是共享內存,可以在多個worker process中共享數據用的。(不過其他的會話一致性怎麽沒用到共享內存區域呢?)
sticky learn
create=$upstream_cookie_examplecookie
lookup=$cookie_examplecookie
zone=client_sessions:1m
timeout=1h;
2.2 Session Draining
主要是有需要關閉某些backend以便維護或者升級,這些關鍵性的服務都講求gracefully處理的:就是新的請求不會發送到這個backend上面,而之前分配到這個backend的會話的後續請求還會繼續發送給他,直到這個會話最終完成。
讓某個backend進入draining的狀態,既可以直接修改配置文件,然後按照之前的方式通過向master process發送信號重新加載配置,也可以采用Nginx的on-the-fly配置方式。
$ curl http://localhost/upstream_conf?upstream=backend
$ curl http://localhost/upstream_conf?upstream=backend\&id=1\&drain=1
通過上面的方式,先列出各個bacnkend的ID號,然後drain指定ID的backend。通過在線觀測backend的所有session都完成後,該backend就可以下線了。
2.3 backend健康監測
backend出錯會涉及到兩個參數,max_fails=1 fail_timeout=10s;意味著只要Nginx向backend發送一個請求失敗或者沒有收到一個響應,就認為該backend在接下來的10s是不可用的狀態。
通過周期性地向backend發送特殊的請求,並期盼收到特殊的響應,可以用以確認backend是健康可用的狀態。通過health_check可以做出這個配置。
match server_ok {
status 200-399;
header Content-Type = text/html;
body !~ "maintenance mode";
}
server {
location / {
proxy_pass http://backend;
health_check interval=10 fails=3 passes=2 match=server_ok;
}
}
上面的health_check是必須的,後面的參數都是可選的。尤其是後面的match參數,可以自定義服務器健康的條件,包括返回狀態碼、頭部信息、返回body等,這些條件是&&與關系。默認情況下Nginx會相隔interval的間隔向backend group發送一個”/“的請求,如果超時或者返回非2xx/3xx的響應碼,則認為對應的backend是unhealthy的,那麽Nginx會停止向其發送request直到下次改backend再次通過檢查。
在使用了health_check功能的時候,一般都需要在backend group開辟一個zone,在共享backend group配置的同時,所有backend的狀態就可以在所有的worker process所共享了,否則每個worker process獨立保存自己的狀態檢查計數和結果,兩種情況會有很大的差異哦。
2.4 通過DNS設置HTTP負載均衡
Nginx的backend group中的主機可以配置成域名的形式,如果在域名的後面添加resolve參數,那麽Nginx會周期性的解析這個域名,當域名解析的結果發生變化的時候會自動生效而不用重啟。
http {
resolver 10.0.0.1 valid=300s ipv6=off;
resolver_timeout 10s;
server {
location / {
proxy_pass http://backend;
}
}
upstream backend {
zone backend 32k;
least_conn;
...
server backend1.example.com resolve;
server backend2.example.com resolve;
}
}
如果域名解析的結果含有多個IP地址,這些IP地址都會保存到配置文件中去,並且這些IP都參與到自動負載均衡。
2.5 TCP/UDP流量的負載均衡
通常,HTTP和HTTPS的負載均衡叫做七層負載均衡,而TCP和UDP協議的負載均衡叫做四層負載均衡。因為七層負載均衡通常都是HTTP和HTTPS協議,所以這種負載均衡相當於是四層負載均衡的特例化,均衡器可以根據HTTP/HTTPS協議的頭部(User-Agent、Language等)、響應碼甚至是響應內容做額外的規則,達到特定條件特定目的的backend轉發的需求。
除了Nginx所專長的HTTP負載均衡,Nginx還支持TCP和UDP流量的負載均衡,適用於LDAP/MySQL/RTMP和DNS/syslog/RADIUS各種應用場景。這類情況的負載均衡使用stream來配置,Nginx編譯的時候需要支持–with-stream選項。查看手冊,其配置原理和參數和HTTP負載均衡差不多。
因為TCP、UDP的負載均衡都是針對通用程序的,所以之前HTTP協議支持的match條件(status、header、body)是沒法使用的。TCP和UDP的程序可以根據特定的程序,采用send、expect的方式來進行動態健康檢測。
match http {
send "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n";
expect ~* "200 OK";
}
2.6 其他特性
slow_start=30s:防止新添加/恢復的主機被突然增加的請求所壓垮,通過這個參數可以讓該主機的weight從0開始慢慢增加到設定值,讓其負載有一個緩慢增加的過程。
max_conns=30:可以設置backend的最大連接數目,當超過這個數目的時候會被放到queue隊列中,同時隊列的大小和超時參數也可以設置,當隊列中的請求數大於設定值,或者超過了timeout但是backend還不能處理請求,則客戶端將會收到一個錯誤返回。通常來說這還是一個比較重要的參數,因為Nginx作為反向代理的時候,通常就是用於抗住並發量的,如果給backend過多的並發請求,很可能會占用後端過多的資源(比如線程、進程非事件驅動),最終反而會影響backend的處理能力。
Nginx-負載均衡實現解讀