負載均衡在服務端開發中算是一個比較重要的特性。因為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了。
1 |
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資訊中提取。
1 |
sticky route $route_cookie $route_uri; |
這樣Nginx會按照順序搜尋$route_cookie、$route_uri引數並選擇第一個非空的引數用作route,而如果所有的引數都是空的,就使用上面預設的負載均衡演算法決定請求分發給哪個backend。
(3). Learn
較為的複雜也較為的智慧,Nginx會自動監測request和response中的session資訊,而且通常需要回話一致性的請求、應答中都會帶有session資訊,這和第一種方式相比是不用增加cookie,而是動態學習已有的session。
這種方式需要使用到zone結構,在Nginx中zone都是共享記憶體,可以在多個worker process中共享資料用的。(不過其他的會話一致性怎麼沒用到共享記憶體區域呢?)
1 2 3 4 5 |
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配置方式。
1 2 |
$ 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可以做出這個配置。
1 2 3 4 5 6 7 8 9 10 11 |
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會週期性的解析這個域名,當域名解析的結果發生變化的時候會自動生效而不用重啟。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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的方式來進行動態健康檢測。
1 2 3 4 |
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的處理能力。
本文完!