HaProxy系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.htmlphp
官方站點:http://www.haproxy.orghtml
haproxy是一款負載均衡軟件,它工做在7層模型上,能夠分析數據包中的應用層協議,並按規則進行負載。一般這類7層負載工具也稱爲反向代理軟件,nginx是另外一款著名的反向代理軟件。前端
haproxy支持使用splice()系統調用,它能夠將數據在兩個套接字之間在內核空間直接使用管道進行傳遞,無需再在kernel buffer-->app buffer-->kernel
之間來回複製數據,實現零複製轉發(Zero-copy forwarding),還能夠實現零複製啓動(zero-starting)。haproxy默認對客戶端的請求和對服務端的響應數據都開啓了splice功能,它自身對數據狀態進行判斷,決定此數據是否啓用splice()進行管道傳遞,這能極大提升性能。mysql
先說明說明HTTP協議事務模型。nginx
http協議是事務驅動的,意味着每一個request產生且僅產生一個response。客戶端發送請求時,將創建一個從客戶端到服務端的TCP鏈接,客戶端發送的每個request都通過此鏈接傳送給服務端,而後服務端發出response報文。隨後這個TCP鏈接將關閉,下一個request將從新打開一個tcp鏈接進行傳送。git
[conn1][req1]......[resp1][close1][conn2][req2]......[resp2][close2]......
這種模式稱爲"http close"模式。這種模式下,有多少個http事務就有多少個鏈接,且每發出一個response就關閉一次tcp鏈接。這種狀況下,客戶端不知道response中body的長度。github
若是"http close"能夠避免"tcp鏈接隨response而關閉",那麼它的性能就能夠獲得必定程度的提高,由於頻繁創建和關閉tcp鏈接消耗的資源和時間是較大的。正則表達式
那麼如何進行提高?在server端發送response時給出content-length
的標記,讓客戶端知道還有多少內容沒有接收到,沒有接收完則tcp鏈接不關閉。這種模式稱爲"keep-alive"。redis
[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後客戶端和服務端都關閉鏈接。任何一個反向代理軟件,都必須具有這個基本的功能。這主要針對後端是應用服務器的狀況,若是後端是靜態服務器或緩存服務器,無需實現會話保持,由於它們是"無狀態"的。
若是反向代理的後端提供的是"有狀態"的服務或協議時,必須保證請求過一次的客戶端能被引導到同義服務端上。只有這樣,服務端才能知道這個客戶端是它曾經處理過的,能查到並獲取到和該客戶端對應的上下文環境(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上。
做爲負載均衡軟件,通常都會提供一種稱爲"源地址hash"的調度算法,將客戶端的IP地址結合後端服務器數量和權重作散列計算,每次客戶端請求時都會進行一樣的hash計算,這樣同一客戶端總能獲得相同的hash值,也就能調度到同一個服務端上。
通常來講,除非無路可選,都不該該選擇相似源地址hash這樣的算法。由於只要後端服務器的權重發生任何一點改變,全部源IP地址的hash值幾乎都會改變,這是很是大的動盪。
做爲反向代理軟件,通常還提供一種cookie綁定的功能實現會話保持。反向代理軟件爲客戶端A單獨生成一個cookie1,或者直接修改應用服務器爲客戶端設置的cookie2,最後將cookie經過在響應報文中設置"Set-Cookie"字段響應給客戶端。與此同時,反向代理軟件會在內存中維持一張cookie表,這張表記錄了cookie1或修改後的cookie2對應的服務端。只要客戶端請求報文中的"Cookie"字段中攜帶了cookie1或cookie2屬性的請求到達反向代理軟件時,反向代理軟件根據cookie表就能檢索到對應的服務端是誰。
須要注意的是,客戶端收到的cookie可能來源有兩類:一類是反向代理軟件增長的,這時客戶端收到的響應報文中將至少有兩個"Set-Cookie"字段,其中一個是反代軟件的,其餘是應用服務器設置的;一類是反向代理軟件在應用服務器設置的Cookie基礎上修改或增長屬性。
例如,當配置haproxy插入cookie時,客戶端從第一次請求到第二次請求被後端應用程序處理的過程大體以下圖所示:
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掉也不會讓客戶端和對應的服務端失去聯繫。
不管反向代理軟件實現的會話保持能力有多強,功能有多多,只要後端是應用服務器,就必定是"有狀態"的。有狀態對於某些業務邏輯來講是必不可少的,但對架構的伸縮和高可用帶來了不便。咱們沒法在架構中隨意添加新的代理節點,甚至沒法隨意添加新的應用服務器,高可用的時候還必須考慮狀態或者某些緩存內容是否會丟失。
若是將全部應用服務器的session信息所有存儲到一臺服務器上(通常放在redis或數據庫中)進行共享,每臺應用服務器在須要獲取上下文的時候從這臺服務器上取,那麼應用服務器在取session消息以前就是"無狀態"的。
例如,下面是一個後端使用session共享的示意圖:
使用session share後,調度器不管將請求調度到哪一個後端上,這個後端都能從session share服務器上獲取到對應的session上下文。這樣無狀態的請求徹底能夠被任意負載,負載軟件無需記住後端服務器,從而達到四層負載的效果。若是沒有特殊需求(如處理7層協議),這時可使用LVS替代haproxy,由於在負載性能上,LVS比haproxy高好幾個級別。
session共享給架構帶來的好處很是多,正如上面所說的,可使用LVS進行極其高效的負載(前提是沒有LVS沒法實現的需求),不管是負載節點仍是應用服務器節點均可以隨意增刪服務器。而惟一須要保證的就是session共享服務器的高可用。
任何一個負載均衡軟件,都應該提供後端服務器健康情況檢查的功能,即便自身沒有,也必須可以藉助其餘第三方工具來實現。只有具有後端健康檢查的功能,在後端某服務器down掉的時候,調度器才能將它從後端服務器組中踢出去,保證客戶端的請求不會被調度到這臺down掉的服務器上。
haproxy爲多種協議類型提供了健康情況檢查的功能,除了最基本的基於tcp的檢查,據我從官方手冊上根據關鍵詞的統計,還爲如下幾種協議提供健康檢查:
若是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"。
一個合格的反向代理軟件,必須可以處理流入的請求報文和流出的響應報文。具有這些能力後,不只能夠按照需求改造報文,還能篩選報文,防止被惡意攻擊。
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層協議。
做爲反向代理,必須具有查看自身和後端服務器的狀態信息。
haproxy提供了多種獲取狀態信息的方法:
stats enable
指令啓用狀態報告功能,這樣就能夠在瀏覽器中輸入特定的url訪問狀態信息。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
能夠說,支持ACL的軟件都是好軟件,好比haproxy、varnish。
ACL本意是access control list(訪問控制列表),用來定義一組黑名單或白名單。但顯然,它毫不僅僅是爲了黑白名單而存在的,有了ACL,能夠隨意按條件定製一組或多組列表。ACL存在的意義,就像是正則表達式存在的意義同樣,極大程度上簡化了軟件在管理上的複雜度。
在haproxy中,只要能在邏輯意義上進行分組的,幾乎均可以使用ACL來定製。好比哪些IP屬於A組,後端哪些節點是靜態組,後端節點少於幾個時屬於dead狀態等等。
haproxy支持後端鏈接重用的功能。
在默認狀況下(不使用鏈接重用),當某客戶端的請求到來後,haproxy爲了將請求轉發給後端,會和後端某服務器創建一個TCP鏈接,並將請求調度到該服務器上,該客戶端後續的請求也會經過該TCP鏈接轉發給後端(假設沒有采用關閉後端鏈接的http事務模型)。但在響應後和該客戶端的下一個請求到來前,這個鏈接是空閒的。
其實仔細想一想,和後端創建的TCP鏈接僅僅只是爲了調度轉發,免去後續再次創建tcp鏈接的消耗。徹底能夠爲其它客戶端的請求調度也使用這個TCP鏈接,保證TCP鏈接資源不浪費。可使用http-reuse strategy_name
指令設置鏈接重用的策略,而默認策略禁用鏈接重用。
該指令有4個值:
never
:這是默認設置。表示禁用鏈接重用,由於老版本的haproxy認爲來源不一樣的請求不該該共享同一個後端鏈接。safe
:這是建議使用的策略。"安全"策略下,haproxy爲客戶端的每一個第一個請求都單獨創建一個和後端的TCP鏈接,可是後續的請求則會重用和該後端的空閒TCP鏈接。這樣的轉發不只提升了資源使用率,還保持了keep-alive的功能。所以,safe策略配合http-keep-alive事務模式比http-server-close事務模式更高效,不管後端是靜態、緩存仍是動態應用服務器。aggressive
:一種激進的策略,該策略的haproxy會重用空閒TCP鏈接來轉發大多數客戶端的第一次請求。之因此是大多數而不是全部,是由於haproxy會挑選那些已經被重用過至少一次的鏈接(即從創建開始轉發過至少兩次,無論源是不是同一客戶端)進行重用,由於haproxy認爲只有這樣的鏈接才具備重用能力。always
:它將老是爲第一個請求重用空閒鏈接。當後端是緩存服務器時,這種策略比safe策略的性能要高許多,由於這樣的請求行爲都是同樣的,且能夠共享同一鏈接來獲取資源。不過不建議使用這種策略,由於大多數狀況下,它和aggressive的性能是同樣的,可是卻帶來了不少風險。所以,爲了性能的提高,將它設置爲safe或aggressive吧,同時再將http事務模型設置爲http-keep-alive,以避免後端鏈接在響應後當即被關閉。