HaProxy系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.html
1.簡介
haproxy是一款負載均衡軟件,它工作在7層模型上,可以分析數據包中的應用層協議,並按規則進行負載。通常這類7層負載工具也稱為反向代理軟件,nginx是另一款著名的反向代理軟件。
haproxy支持使用splice()系統調用,它可以將數據在兩個套接字之間在內核空間直接使用管道進行傳遞,無需再在kernel buffer-->app buffer-->kernel
之間來回復制數據,實現零復制轉發(Zero-copy forwarding),還可以實現零復制啟動(zero-starting)。haproxy默認對客戶端的請求和對服務端的響應數據都開啟了splice功能,它自身對數據狀態進行判斷,決定此數據是否啟用splice()進行管道傳遞,這能極大提高性能。
2.haproxy的特性(1):連接保持和連接關閉
先說明說明HTTP協議事務模型。
http協議是事務驅動的,意味着每個request產生且僅產生一個response。客戶端發送請求時,將建立一個從客戶端到服務端的TCP連接,客戶端發送的每一個request都經過此連接傳送給服務端,然后服務端發出response報文。隨后這個TCP連接將關閉,下一個request將重新打開一個tcp連接進行傳送。
[conn1][req1]......[resp1][close1][conn2][req2]......[resp2][close2]......
這種模式稱為"http close"模式。這種模式下,有多少個http事務就有多少個連接,且每發出一個response就關閉一次tcp連接。這種情況下,客戶端不知道response中body的長度。
如果"http close"可以避免"tcp連接隨response而關閉",那么它的性能就可以得到一定程度的提升,因為頻繁建立和關閉tcp連接消耗的資源和時間是較大的。
那么如何進行提升?在server端發送response時給出content-length
的標記,讓客戶端知道還有多少內容沒有接收到,沒有接收完則tcp連接不關閉。這種模式稱為"keep-alive"。
[conn][req1]...[resp1][req2]...[resp2][close]
另一種提升"http close"的方式是"pipelining"模式。它仍然使用"keep-alive"模式,但是客戶端不需要等待收到服務端的response后才發送后續的request。這在請求一個含有大量圖片的頁面時很有用。這種模式類似於累積報文數量成一批或完成后才一次性發送,能很好的提升性能。
[conn][req1][req2]...[resp1][resp2][close]...
很多http代理不支持pipelining,因為它們無法將response和相應的request在一個http協議中聯系起來,而haproxy可以在pipelinign模式下對報文進行重組。
默認haproxy操作在keep-alive模式:對於每一個tcp連接,它處理每一個request和response,並且在發送response后連接兩端都處於空閑狀態一段時間,如果該連接的客戶端發起新的request,則繼續使用此連接。
haproxy支持5種連接模式:
keep alive
:分析並處理所有的request和response(默認),后端為靜態或緩存服務器建議使用此模式。tunnel
:僅分析處理第一個request和response,剩余所有內容不進行任何分析直接轉發。1.5版本之前此為默認,現在不建議設置為此模式。passive close
:在請求和響應首部加上"connection:close"標記的tunnel,在處理完第一個request和response后嘗試關閉兩端連接。server close
:處理完第一個response后關閉和server端的連接,但和客戶端的連接仍然保持,后端為動態應用程序服務器組建議使用此模式。forced close
:傳輸完一個response后客戶端和服務端都關閉連接。
3.haproxy的特性(2):會話保持
任何一個反向代理軟件,都必須具備這個基本的功能。這主要針對后端是應用服務器的情況,如果后端是靜態服務器或緩存服務器,無需實現會話保持,因為它們是"無狀態"的。
如果反向代理的后端提供的是"有狀態"的服務或協議時,必須保證請求過一次的客戶端能被引導到同義服務端上。只有這樣,服務端才能知道這個客戶端是它曾經處理過的,能查到並獲取到和該客戶端對應的上下文環境(session上下文),有了這個session環境才能繼續為該客戶端提供后續的服務。
如果不太理解,簡單舉個例子。客戶端A向服務端B請求將C商品加入它的賬戶購物車,加入成功后,服務端B會在某個緩存中記錄下客戶端A和它的商品C,這個緩存的內容就是session上下文環境。而識別客戶端的方式一般是設置session ID(如PHPSESSID、JSESSIONID),並將其作為cookie的內容交給客戶端。客戶端A再次請求的時候(比如將購物車中的商品下訂單)只要攜帶這個cookie,服務端B就可以從中獲取到session ID並找到屬於客戶端A的緩存內容,也就可以繼續執行下訂單部分的代碼。
假如這時使用負載均衡軟件對客戶端的請求進行負載,如果這個負載軟件只是簡單地進行負載轉發,就無法保證將客戶端A引導到服務端B,可能會引導到服務端X、服務端Y,但是X、Y上並沒有緩存和客戶端A對應的session內容,當然也無法為客戶端A下訂單。
因此,反向代理軟件必須具備將客戶端和服務端"綁定"的功能,也就是所謂的提供會話保持,讓客戶端A后續的請求一定轉發到服務端B上。
3.1 源地址hash算法實現會話保持
作為負載均衡軟件,一般都會提供一種稱為"源地址hash"的調度算法,將客戶端的IP地址結合后端服務器數量和權重做散列計算,每次客戶端請求時都會進行同樣的hash計算,這樣同一客戶端總能得到相同的hash值,也就能調度到同一個服務端上。
一般來說,除非無路可選,都不應該選擇類似源地址hash這樣的算法。因為只要后端服務器的權重發生任何一點改變,所有源IP地址的hash值幾乎都會改變,這是非常大的動盪。
3.2 cookie實現會話保持
作為反向代理軟件,一般還提供一種cookie綁定的功能實現會話保持。反向代理軟件為客戶端A單獨生成一個cookie1,或者直接修改應用服務器為客戶端設置的cookie2,最后將cookie通過在響應報文中設置"Set-Cookie"字段響應給客戶端。與此同時,反向代理軟件會在內存中維持一張cookie表,這張表記錄了cookie1或修改后的cookie2對應的服務端。只要客戶端請求報文中的"Cookie"字段中攜帶了cookie1或cookie2屬性的請求到達反向代理軟件時,反向代理軟件根據cookie表就能檢索到對應的服務端是誰。
需要注意的是,客戶端收到的cookie可能來源有兩類:一類是反向代理軟件增加的,這時客戶端收到的響應報文中將至少有兩個"Set-Cookie"字段,其中一個是反代軟件的,其他是應用服務器設置的;一類是反向代理軟件在應用服務器設置的Cookie基礎上修改或增加屬性。
例如,當配置haproxy插入cookie時,客戶端從第一次請求到第二次請求被后端應用程序處理的過程大致如下圖所示:
3.3 stick table實現會話粘性
haproxy還提供另一種stick table功能實現會話粘性(stickiness)。這張stick-table表非常強大,它可以根據抽取客戶端請求報文中的內容或者源IP地址或者抽取響應報文中的內容(例如應用服務器設置的Session ID這個cookie)作為這張表的key,將后端服務器的標識符ID作為key對應的value。只要客戶端再次請求,haproxy就能對請求進行匹配(match),無論是源IP還是cookie亦或是其它字符串作為key,總能匹配到對應的記錄,而且匹配速度極快,再根據value轉發給對應的后端服務器。
例如,下圖是一張最簡單的stick table示意圖:
該stick-table存儲的key是客戶端的源IP地址,當客戶端第一次請求到達haproxy后,haproxy根據調度算法為其分配一個后端appserver1,當請求轉發到達后端后,haproxy立即在stick table表中建立一條ip1和appserver1的粘性(stickiness)記錄。之后,無論是否使用cookie,haproxy都能為該客戶端找到它對應的后端服務器。
stick table的強大遠不止會話粘性。還可以根據需要定制要記錄的計數器和速率統計器,例如在一個時間段內總共流入了多少個連接、平均每秒流入多少個連接、流入流出的字節數和平均速率、建立會話的數量和平均速率等。
更強大的是,stick table可以在"主主模型"下進行stick記錄復制(replication),它不像session復制(copy),節點一多,無論是節點還是網絡帶寬的壓力都會暴增。haproxy每次推送的是stick table中的一條記錄,而不是整表整表地復制,而且每條記錄占用的空間很小(最小時每條記錄50字節),使得即使在非常繁忙的情況下,在幾十台haproxy節點之間復制都不會占用太多網絡帶寬。借助stick table的復制,可以完完整整地實現haproxy"主主模型",保證所有粘性信息都不會丟失,從而保證haproxy節點down掉也不會讓客戶端和對應的服務端失去聯系。
3.4 session共享
無論反向代理軟件實現的會話保持能力有多強,功能有多多,只要后端是應用服務器,就一定是"有狀態"的。有狀態對於某些業務邏輯來說是必不可少的,但對架構的伸縮和高可用帶來了不便。我們無法在架構中隨意添加新的代理節點,甚至無法隨意添加新的應用服務器,高可用的時候還必須考慮狀態或者某些緩存內容是否會丟失。
如果將所有應用服務器的session信息全部存儲到一台服務器上(一般放在redis或數據庫中)進行共享,每台應用服務器在需要獲取上下文的時候從這台服務器上取,那么應用服務器在取session消息之前就是"無狀態"的。
例如,下面是一個后端使用session共享的示意圖:
使用session share后,調度器無論將請求調度到哪個后端上,這個后端都能從session share服務器上獲取到對應的session上下文。這樣無狀態的請求完全可以被任意負載,負載軟件無需記住后端服務器,從而達到四層負載的效果。如果沒有特殊需求(如處理7層協議),這時可以使用LVS替代haproxy,因為在負載性能上,LVS比haproxy高好幾個級別。
session共享給架構帶來的好處非常多,正如上面所說的,可以使用LVS進行極其高效的負載(前提是沒有LVS無法實現的需求),無論是負載節點還是應用服務器節點都可以隨意增刪服務器。而唯一需要保證的就是session共享服務器的高可用。
4.haproxy特性(3):后端健康狀況檢查和被檢查
任何一個負載均衡軟件,都應該提供后端服務器健康狀況檢查的功能,即使自身沒有,也必須能夠借助其他第三方工具來實現。只有具備后端健康檢查的功能,在后端某服務器down掉的時候,調度器才能將它從后端服務器組中踢出去,保證客戶端的請求不會被調度到這台down掉的服務器上。
haproxy為多種協議類型提供了健康狀況檢查的功能,除了最基本的基於tcp的檢查,據我從官方手冊上根據關鍵詞的統計,還為以下幾種協議提供健康檢查:
- HTTP
- ldap
- mysql
- pgsql
- redis
- SPOP
- smtp
如果haproxy沒有指定基於哪種協議進行檢查,默認會使用tcp協議進行檢查,這種檢查的健康判斷方式就是能否連上后端。例如:
backend static_group
server staticsrv1 192.168.100.62:80 check rise 1 server staticsrv2 192.168.100.63:80 check rise 1
在server指令中的check設置的是是否開啟健康檢查功能,以及檢查的時間間隔、判斷多少次不健康后就認為后端下線了以及成功多少次后認為后端重新上線了。
如果要基於其它協議檢查,需要使用協議對應的option指令顯式指定要檢查的對象。且前提是server中必須指定check,這是控制檢查與否的開關。例如,基於http協議檢查:
backend dynamic_group
option httpchk GET /index.php
server appsrv1 192.168.100.60:80 check server appsrv2 192.168.100.61:80 check
對於基於http協議的檢查,haproxy提供了多種判斷健康與否的方式,可以通過返回狀態碼或拿狀態碼來進行正則匹配、通過判斷響應體是否包含某個字符串或者對響應體進行正則匹配。
例如:
backend dynamic_group1
option httpchk GET /index.php
http-check expect status 200 server appsrv1 192.168.100.60:80 check server appsrv2 192.168.100.61:80 check backend dynamic_group2 option httpchk GET /index.php http-check expect ! string error server appsrv1 192.168.100.60:80 check server appsrv2 192.168.100.61:80 check
上面兩個后端組都指定了使用http協議進行檢查,並分別使用http-check expect
指定了要檢查到狀態碼200、響應體中不包含字符串"error"才認為健康。如果不指定http-check expect
指令,那么基於http協議檢查的時候,只要狀態碼為2xx或3xx都認為是健康的。
haproxy除了具備檢查后端的能力,還支持被檢查,只需要使用monitor
類的指令即可。所謂被檢查,指的是haproxy可以指定一個檢查自己的指標,自己獲取檢查結果,並將檢查狀態上報給它的前端或高可用軟件,讓它們很容易根據上報的結果(200或503狀態碼)判斷haproxy是否健在。
以下是兩個被檢查的示例:
frontend www
mode http
monitor-uri /haproxy_test
frontend www
mode http
acl site_dead nbsrv(dynamic) lt 2
acl site_dead nbsrv(static) lt 2
monitor-uri /site_alive
monitor fail if site_dead
第一個示例中,"/haproxy_test"是它的前端指定要檢查的路徑,此處haproxy對該uri路徑進行監控,當該路徑正常時,haproxy會告訴前段"HTTP/1.0 200 OK",當不正常時,將"HTTP/1.0 503 Service unavailable"。
第二個示例中,不僅監控了"/site_alive",還監控了后端健康節點的數量。當dynamic或static后端組的健康節點數量少於2時,haproxy立即主動告訴前端"HTTP/1.0 503 Service unavailable",否則返回給前端"HTTP/1.0 200 OK"。
5.haproxy的特性(4):處理請求和響應報文
一個合格的反向代理軟件,必須能夠處理流入的請求報文和流出的響應報文。具備這些能力后,不僅可以按照需求改造報文,還能篩選報文,防止被惡意攻擊。
haproxy提供了很多處理請求、響應報文的功能性指令,還有一些所謂"函數"。
大多數處理請求報文的函數都以"req"或"capture.req."開頭,處理響應報文的函數都以"res."或"capture.res."開頭,這樣的函數非常多,幾乎可以實現任何想達到的功能。完整的指令集見官方手冊:https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#7.3.2。
以下是幾個比較具有代表性的函數或指令:
capture request header:捕獲請求報文。
capture response header:捕獲響應報文。
reqadd:在請求首部添加字段。
rspadd:在響應首部添加字段。
req.cook(name):獲取Cookie字段中的name屬性的值。
res.cook(name):獲取"Set-Cookie"字段中name屬性的值。
。。。。。。
以上函數都是對7層協議進行處理。除此之外,haproxy還有非常多的函數可以分別處理4層、5層、6層協議。
6.haproxy的特性(5):狀態查看
作為反向代理,必須具備查看自身和后端服務器的狀態信息。
haproxy提供了多種獲取狀態信息的方法:
- 使用
stats enable
指令啟用狀態報告功能,這樣就可以在瀏覽器中輸入特定的url訪問狀態信息。 - 提供了很多對前段狀態和后端節點狀態取樣調查的函數。例如某指定后端或所有后端有多少個節點存活、某后端或所有后端已建立多少連接、后端還有多少連接槽位可以繼續提供連接、前段建立了多少連接等等。指令集合參見官方手冊:https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#7.3.2
- 提供了套接字狀態查看、管理功能。也許很多人都不知道,默認配置文件中的
stat socket
指令是干嗎用的,其實這就是為系統管理員提供的接口。global log 127.0.0.1 local2 chroot /var/lib/haproxy pidfile /var/run/haproxy.pid maxconn 2000 user haproxy group haproxy daemon stats socket /var/lib/haproxy/stats
我們安裝"socat"包(socket cat,在epel源提供該包)后,就可以通過socat命令來查看/var/lib/haproxy/stats這個狀態套接字。例如,執行下面的命令可以獲取到所有可執行的命令。
echo "help" | socat unix:/var/lib/haproxy/stats -
例如,其中一條命令是"show backend"用來列出所有的backend,可以這樣使用:
echo "show backend" | socat unix:/var/lib/haproxy/stats -
或者也可以進入交互式操作模式:
socat readline unix:/var/lib/haproxy/stats
7.haproxy的特性(6):ACL
可以說,支持ACL的軟件都是好軟件,比如haproxy、varnish。
ACL本意是access control list(訪問控制列表),用來定義一組黑名單或白名單。但顯然,它絕不僅僅是為了黑白名單而存在的,有了ACL,可以隨意按條件定制一組或多組列表。ACL存在的意義,就像是正則表達式存在的意義一樣,極大程度上簡化了軟件在管理上的復雜度。
在haproxy中,只要能在邏輯意義上進行分組的,幾乎都可以使用ACL來定制。比如哪些IP屬於A組,后端哪些節點是靜態組,后端節點少於幾個時屬於dead狀態等等。
8.haproxy的特性(7):連接重用功能
haproxy支持后端連接重用的功能。
在默認情況下(不使用連接重用),當某客戶端的請求到來后,haproxy為了將請求轉發給后端,會和后端某服務器建立一個TCP連接,並將請求調度到該服務器上,該客戶端后續的請求也會通過該TCP連接轉發給后端(假設沒有采用關閉后端連接的http事務模型)。但在響應后和該客戶端的下一個請求到來前,這個連接是空閑的。
其實仔細想想,和后端建立的TCP連接僅僅只是為了調度轉發,免去后續再次建立tcp連接的消耗。完全可以為其它客戶端的請求調度也使用這個TCP連接,保證TCP連接資源不浪費。可以使用http-reuse strategy_name
指令設置連接重用的策略,而默認策略禁用連接重用。
該指令有4個值:
- (1).
never
:這是默認設置。表示禁用連接重用,因為老版本的haproxy認為來源不同的請求不應該共享同一個后端連接。 - (2).
safe
:這是建議使用的策略。"安全"策略下,haproxy為客戶端的每個第一個請求都單獨建立一個和后端的TCP連接,但是后續的請求則會重用和該后端的空閑TCP連接。這樣的轉發不僅提高了資源使用率,還保持了keep-alive的功能。因此,safe策略配合http-keep-alive事務模式比http-server-close事務模式更高效,無論后端是靜態、緩存還是動態應用服務器。 - (3).
aggressive
:一種激進的策略,該策略的haproxy會重用空閑TCP連接來轉發大多數客戶端的第一次請求。之所以是大多數而不是所有,是因為haproxy會挑選那些已經被重用過至少一次的連接(即從建立開始轉發過至少兩次,不管源是否是同一客戶端)進行重用,因為haproxy認為只有這樣的連接才具有重用能力。 - (4).
always
:它將總是為第一個請求重用空閑連接。當后端是緩存服務器時,這種策略比safe策略的性能要高許多,因為這樣的請求行為都是一樣的,且可以共享同一連接來獲取資源。不過不建議使用這種策略,因為大多數情況下,它和aggressive的性能是一樣的,但是卻帶來了很多風險。
因此,為了性能的提升,將它設置為safe或aggressive吧,同時再將http事務模型設置為http-keep-alive,以免后端連接在響應后立即被關閉。