haproxy實現會話保持

HAProxy系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.htmlphp


1.反向代理爲何須要設置cookie

任何一個七層的http負載均衡器,都應該具有一個功能:會話保持。會話保持是保證客戶端對動態應用程序正確請求的基本要求。css

仍是那個被舉爛了卻最有說服力的例子:客戶端A向服務端B請求將C商品加入它的帳戶購物車,加入成功後,服務端B會在某個緩存區域中記錄下客戶端A和它的商品C,這個緩存的內容就是session上下文環境。而識別客戶端的方式通常是設置session ID(如PHPSESSID、JSESSIONID),並將其做爲cookie的內容交給客戶端。客戶端A再次請求的時候(好比將購物車中的商品下訂單)只要攜帶這個cookie,服務端B就能夠從中獲取到session ID並找到屬於客戶端A的緩存內容(商品C),也就能夠繼續執行下訂單部分的代碼。html

假如這時使用負載均衡軟件對客戶端的請求進行負載,就必需要保證能將客戶端A的請求再次引導到服務端B,而不能引導到服務端X、服務端Y,由於X、Y上並無緩存和客戶端A對應的session內容,也就沒法爲客戶端A下訂單。linux

所以,反向代理軟件必須具有將客戶端和服務端"綁定"的功能,也就是所謂的提供會話保持,讓客戶端A後續的請求必定轉發到服務端B上。nginx

這裏討論的對象是http的動態應用請求,它要求會話保持。更通用地,只要負載均衡軟件負載的不是"無狀態"的協議或服務,就應該提供會話保持能力,除非它是四層負載軟件。git

haproxy提供了3種實現會話保持的方式:github

  • (1).源地址hash;
  • (2).設置cookie;
  • (3).會話粘性表stick-table;

本文只討論haproxy在設置cookie上實現會話保持的方式,stick-table會話粘性的方式則在下一篇文章中單獨討論。而源地址hash是一種負載調度算法,沒什麼可討論的,並且除非實在沒辦法,不建議使用這種調度算法。web

2.haproxy設置cookie的幾種方式

設置cookie的方式是經過在配置文件中使用cookie指令進行配置的。因爲haproxy設置cookie的目的是爲了將某客戶端引導到以前爲其服務過的後端服務器上,簡單地說,就是和後端某服務器保持聯繫,所以cookie指令不能設置在frontend段落。redis

首先看一個設置cookie的示例。算法

backend dynamic_servers
    cookie app_cook  insert nocache server app1 192.168.100.22:80 cookie server1 server app2 192.168.100.23:80 cookie server2 

這個示例配置中,cookie指令中指定的是insert命令,表示在將響應報文交給客戶端以前,先插入一個屬性名爲"app_cook"的cookie,這個cookie在響應報文的頭部將獨佔一個"Set-Cookie"字段(由於是插入新cookie),而"app_cook"只是cookie名稱,它的值是由server指令中的cookie選項指定的,這裏是"server1"或"server2"。

所以,若是這個請求報文分配給後端app2時,響應給客戶端的響應報文中haproxy設置的"Set-Cookie"字段的樣式爲:

Set-Cookie:app_cook=server2; path=/ 

除了insert命令,cookie指令中還支持rewrite和prefix兩種設置cookie的方式,這三種cookie的操做方式只能三選一。此外,還提供一些額外對cookie的功能設置。

首先看看指令的語法:

cookie <name> [ rewrite | insert | prefix ] [ indirect ] [ nocache ] [ postonly ] [ preserve ] [ httponly ] [ secure ] [ domain <domain> ]* [ maxidle <idle> ] [ maxlife <life> ] 

本文詳細分節討論rewrite、insert、prefix的行爲,並在討論它們的時候會穿插說明indirect、nocache和preserve的行爲,若是須要了解其餘選項,請自翻官方手冊。

下圖是後文實驗時使用的環境:

其中在後端提供的index.php內容大體以下,主要部分是設置了名爲PHPSESSID的cookie

<h1>response from webapp 192.168.100.61</h1> <?php session_start(); echo "Server IP: "."<font color=red>".$_SERVER['SERVER_ADDR']."</font>"."<br>"; echo "Server Name: "."<font color=red>".$_SERVER['SERVER_NAME']."</font>"."<br>"; echo "SESSIONNAME: "."<font color=red>".session_name()."</font>"."<br>"; echo "SESSIONID: "."<font color=red>".session_id()."</font>"."<br>"; ?> 

2.1 cookie insert

insert This keyword indicates that the persistence cookie will have to be inserted by haproxy in server responses if the client did not already have a cookie that would have permitted it to access this server. When used without the "preserve" option, if the server emits a cookie with the same name, it will be remove before processing. For this reason, this mode can be used to upgrade existing configurations running in the "rewrite" mode. The cookie will only be a session cookie and will not be stored on the client's disk. By default, unless the "indirect" option is added, the server will see the cookies emitted by the client. Due to caching effects, it is generally wise to add the "nocache" or "postonly" keywords (see below). The "insert" keyword is not compatible with "rewrite" and "prefix". 

其中大體說明了如下幾個意思:

  1. 該關鍵詞表示,haproxy將在客戶端沒有cookie時(好比第一次請求),在響應報文中插入一個cookie。
  2. 當沒有使用關鍵詞"preserve"選項時,若是後端服務器設置了一個和此處名稱相同的cookie,則首先刪除服務端設置的cookie。
  3. 該cookie只能做爲會話保持使用,沒法持久化到客戶端的磁盤上(由於haproxy設置的cookie沒有maxAge屬性,沒法持久保存,只能保存在瀏覽器緩存中)。
  4. 默認狀況下,除非使用了"indirect"選項,不然服務端能夠看到客戶端請求時的全部cookie信息。
  5. 因爲緩存的影響,建議加上"nocache"或"postonly"選項。

下面使用例子來解釋insert的各類行爲。

在haproxy以下配置後端。

backend dynamic_group
    cookie app_cook insert nocache server app1 192.168.100.60:80 cookie app_server1 server app2 192.168.100.61:80 cookie app_server2 

當使用瀏覽器第一次訪問http://192.168.100.59/index.php時,響應結果和響應首部內容以下圖:

從圖中能夠知道,此次瀏覽器的請求分配給了app2,並且響應首部中有兩個"Set-Cookie"字段,其中帶有PHPSESSID的cookie是app2服務器自身設置的,另外一個是haproxy設置的,其名和其值爲"app_cook=app_server2"。

若是客戶端再次訪問(不關閉瀏覽器,cookie緩存還在),請求頭中將攜帶該cookie,haproxy發現了該cookie中"app_cook=app_server2"部分,知道這個請求要交給app_server2這個後端。以下圖:

這樣就實現了會話保持,保證被處理過的客戶端能被分配到同一個後端應用服務器上。

注意,客戶端在第一次收到響應後就會把cookie緩存下來,之後每次http://192.168.100.59/index.php(根據域名進行判斷)都會從緩存中取出該cookie放進請求首部。這樣haproxy必定會將其分配給app_server2,除非app_server2下線了。但即便如此,客戶端仍是會攜帶該cookie,只不過haproxy判斷app_server2下線後,就爲客戶端從新分配app_server1,並設置"app_cook=app_server1",該cookie會替換客戶端中的"app_cook=app_server2"。下圖是app2下線後分配給app1的結果:

但注意,即便分配給了app1,PHPSESSID也不會改變(即app1設置的PHPSESSID無效),由於haproxy判斷出這個重名cookie,會刪除app1設置的PHPSESSID。所以上圖中的PHPSESSID值和以前分配給app2時的PHPSESSID是同樣的。

這樣一來,app1不是就沒法處理該客戶端的請求了嗎?確實如此,但沒辦法,除非後端設置了session共享。

若是將配置文件中的cookie名稱也設置爲PHPSESSID,即後端應用服務器和此處設置的cookie名稱相同,那麼haproxy將首先將後端的PHPSESSID刪除,而後使用本身的值發送給客戶端。也就是說,此時將只有一個"Set-Cookie"字段響應給客戶端。

backend dynamic_group
    cookie PHPSESSID insert nocache server app1 192.168.100.60:80 cookie app_server1 server app2 192.168.100.61:80 cookie app_server2 

所以,在cookie指令中絕對不能設置cookie名稱和後端的cookie名稱相同,不然後端就至關於"盲人"。例如此處的PHPSESSID,此時後端雖然認識PHPSESSID是本身發送出去的cookie名稱,可是沒法獲取ID爲"app_server1"的session上下文。

若是不配合"indirect"選項,服務端能夠看到客戶端請求時的全部cookie信息。若是配合"indirect"選項,則haproxy在將請求轉發給後端時,將刪除本身設置的cookie,使得後端只能看到它本身的cookie,這樣對後端來講,整個過程是徹底透明的,它不知道前面有負載均衡軟件。

從新修改haproxy的cookie指令,並修改nginx配置文件中日誌格式,在其中加上"$http_cookie"變量,它表示請求報文中的cookie信息。

# haproxy cookie app_cook insert nocache # nginx log_format main '$http_cookie $remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; 

客戶端再次訪問時,nginx的日誌中將記錄如下信息(只貼出了前幾個字段)。

PHPSESSID=47d0ina2m14gg67ovdf1d972d1; app_cook=app_server1 192.168.100.59 

加上"indirect"選項,再測試。

cookie app_cook insert indirect nocache 

結果以下:

PHPSESSID=bge3bh6sksu2ie91lsp8ep9oi2 192.168.100.59 

若是insert關鍵字配合"preserve"關鍵字,那麼當後端設置了cookie時,haproxy將強制保留該cookie,不作任何修改。也就是說,若是將haproxy的cookie名稱也設置爲PHPSESSID,那麼客戶端第一次請求時收到的響應報文中將只有一個"Set-Cookie"字段,且這個字段的值是後端服務器設置的,和haproxy無關。

當客戶端和HAProxy之間存在緩存時,建議將insert配合nocache一塊兒使用,由於nocache確保若是須要插入cookie,則可緩存頁面將被標記爲不可緩存。這一點很重要,由於若是全部cookie都添加到可緩存的頁面上,則全部客戶都將從中間的緩存層(如cdn端的緩存層)獲取頁面,而且將共享同一個Cookie,從而致使某臺後端服務器接收的流量遠遠超過其餘後端服務器。

2.2 cookie prefix

prefix    This keyword indicates that instead of relying on a dedicated
          cookie for the persistence, an existing one will be completed. This may be needed in some specific environments where the client does not support more than one single cookie and the application already needs it. In this case, whenever the server sets a cookie named <name>, it will be prefixed with the server's identifier and a delimiter. The prefix will be removed from all client requests so that the server still finds the cookie it emitted. Since all requests and responses are subject to being modified, this mode doesn't work with tunnel mode. The "prefix" keyword is not compatible with "rewrite" and "insert". Note: it is highly recommended not to use "indirect" with "prefix", otherwise server cookie updates would not be sent to clients. 

大體意思是:haproxy將在已存在的cookie(例如後端應用服務器設置的)上添加前綴cookie值,這個前綴部分是server指令中的cookie設置的,表明的是服務端標識符。在客戶端再次訪問時,haproxy將會自動移除這部分前綴,使得服務端只能看到它本身發出的cookie。在一些特殊環境下,客戶端不支持多個"Set-Cookie"字段,這時可使用prefix。

使用prefix的時候,cookie指令設置的cookie名必須和後端設置的cookie同樣(在本文的環境中是PHPSESSID),不然prefix模式下的haproxy不會對響應報文作任何改變。

backend dynamic_group cookie PHPSESSID prefix server app1 192.168.100.60:80 cookie app_server1 server app2 192.168.100.61:80 cookie app_server2 

以下圖:

從後端nginx上的日誌上查看haproxy轉發過來的請求,能夠看到前綴已經被haproxy去掉了。

PHPSESSID=oses71hjr64dl6lputpkmdpg12 192.168.100.59 - - 

2.3 cookie rewrite

rewrite   This keyword indicates that the cookie will be provided by the
          server and that haproxy will have to modify its value to set the server's identifier in it. This mode is handy when the management of complex combinations of "Set-cookie" and "Cache-control" headers is left to the application. The application can then decide whether or not it is appropriate to emit a persistence cookie. Since all responses should be monitored, this mode doesn't work in HTTP tunnel mode. Unless the application behaviour is very complex and/or broken, it is advised not to start with this mode for new deployments. This keyword is incompatible with "insert" and "prefix". 

當後端服務器設置了cookie時,使用rewrite模式時,haproxy將重寫該cookie的爲後端服務器的標識符。當應用程序須要同時考慮"Set-Cookie"和"Cache-control"字段時,該模式很是方便,由於應用程序能夠決定是否應該設置一個爲了保持會話的cookie。除非後端應用程序的環境很是複雜,不然不建議使用該模式。

一樣,rewrite模式下的haproxy設置的cookie必須和後端服務器設置的cookie名稱一致,不然不會作任何改變。

backend dynamic_group cookie PHPSESSID rewrite server app1 192.168.100.60:80 cookie app_server1 server app2 192.168.100.61:80 cookie app_server2 

結果以下圖:

可是,當客戶端持着"PHPSESSID=app_server1"再去請求服務器時,haproxy將其分配給app1,app1此時收到的cookie將是重寫後的,可是app1根本就不認識這個cookie,後面的代碼可能所以而失去邏輯沒法進行正確處理。

3.haproxy如何使用cookie實現會話保持以及如何忽略會話保持

在haproxy中,haproxy會監控、修改、增長cookie,這都是經過內存中的cookie表實現的。

cookie表中記錄了它本身增、改的cookie記錄,包括cookie名和對應server的cookie值,經過這個cookie記錄,haproxy就能知道請求該交給哪一個後端。

例如,當haproxy插入一個cookie的時候。即在haproxy配置以下後端。

backend dynamic_group
    cookie app_cook insert nocache server app1 192.168.100.60:80 cookie app_server1 server app2 192.168.100.61:80 cookie app_server2 

那麼,從客戶端第一次請求到第二次請求被處理的整個過程,大體以下:

當haproxy成功修改了響應報文中的cookie時,將在cookie表中插入一條記錄,這條記錄是維持會話的依據。

其實,經過cookie表保持和後端的會話只是默認狀況,haproxy容許"即便使用了cookie也不進行會話綁定"的功能。這能夠經過ignore-persist指令來實現。當知足該指令的要求時,表示不將該cookie插入到cookie表中,所以沒法實現會話保持,即便haproxy設置了cookie也沒用。

例如,在backend中指定以下配置:

backend dynamic_group
    acl  url_dynamic   path_end  -i .php
    ignore-persist if  url_dynamic
    cookie app_cook insert nocache server app1 192.168.100.60:80 cookie app_server1 server app2 192.168.100.61:80 cookie app_server2 

這表示當請求uri以".php"結尾時,將忽略會話保持功能。這表示,對於php結尾的請求,app_cook這個cookie從頭至尾都是擺設。

固然,上面的設置是不合理的,更合理的應該是這樣的。

acl url_static  path_beg         /static /images /img /css acl url_static path_end .gif .png .jpg .css .js ignore-persist if url_static 

ignore-persist相對的是force-persist,但不建議使用該選項,由於它和option redispatch衝突。

haproxy實現會話保持(2):stick table

 
分類:  網站架構

HAProxy系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.html


在上一篇文章中,分析了haproxy如何經過cookie實現會話保持,本文討論haproxy另外一種實現會話保持的方式:stick table。

1.stickiness和stick table簡介

stick table是haproxy的一個很是優秀的特性,這個表裏面存儲的是stickiness記錄,stickiness記錄了客戶端和服務端1:1對應的引用關係。經過這個關係,haproxy能夠將客戶端的請求引導到以前爲它服務過的後端服務器上,也就是實現了會話保持的功能。這種記錄方式,俗稱會話粘性(stickiness),即將客戶端和服務端粘連起來。

stick table中使用key/value的方式映射客戶端和後端服務器,key是客戶端的標識符,可使用客戶端的源ip(50字節)、cookie以及從報文中過濾出來的部分String。value部分是服務端的標識符。

stick table實現會話粘性的過程以下圖:

除了存儲key/value實現最基本的粘性,stick table還能夠額外存儲每一個stickiness記錄對應的狀態統計數據。好比stickiness記錄1目前創建了多少和客戶端的鏈接、平均創建鏈接的速度是多少、流入流出了多少字節的數據、創建會話的數量等等。

stick table能夠在"雙主模型"下進行復制(replication)。只要設置好對端haproxy節點,haproxy就會自動將新插入的、剛更新的記錄經過TCP鏈接推送到對端節點上。這樣一來,粘性記錄不會丟失,即便某haproxy節點出現了故障,其餘節點也能將客戶端按照粘性映射關係引導到正確的後端服務器上。並且每條stickiness記錄佔用空間都很小(平均最小50字節,最大166字節,由是否記錄額外統計數據以及記錄多少來決定佔用空間大小),使得即便在很是繁忙的環境下在幾十個節點之間推送都不會出現壓力瓶頸和網絡阻塞(能夠按節點數量、stickiness記錄的大小和平均併發量來計算每秒在網絡間推送的數據流量)。

此外,stick table還能夠在haproxy重啓時,在新舊兩個進程間進行復制,這是本地複製。當haproxy重啓時,舊haproxy進程會和新haproxy進程創建TCP鏈接,將其維護的stick table推送給新進程。這樣新進程不會丟失粘性信息,和其餘節點也能最大程度地保持同步,使得其餘節點只須要推送該節點重啓過程當中新增長的stickiness記錄就能徹底保持同步。

2.使用stick table

下圖是本文測試時的環境:

2.1 建立stick table

首先看建立stick table的語法:

stick-table type {ip | integer | string [len <length>] | binary [len <length>]} size <size> [expire <expire>] [nopurge] [peers <peersect>] [store <data_type>]* 

其中

  • type ip | integer | string:使用什麼類型的key做爲客戶端標識符。能夠是客戶端的源IP,能夠是一個整數ID值,也能夠是一段從請求報文或響應報文中匹配出來的字符串。
  • size:表中容許的最大stickiness記錄數量。單位使用k、m和g表示,分別表示102四、2^20和2^30條記錄。
  • expire:stickiness記錄的過時時長。當某記錄被操做後,過了一段時間就會過時,過時的記錄會自動從stick table中移除,釋放表空間。
  • nopurge:默認狀況下,當表滿後,若是還有新的stickiness記錄要插入進來,haproxy會自動將一部分老舊的stickiness記錄flush掉,以釋放空間存儲新紀錄。指定nopurge後,將不進行flush,只能經過記錄過時來釋放表空間,所以該選項必須配合expire選項同時使用。
  • peers:指定要將stick table中的記錄replication到對端haproxy節點。
  • store:指定要存儲在stick table中的額外狀態統計數據。其中表明後端服務器的標識符server ID(即key/value的value部分)會自動插入,無需顯式指定。

注意,每一個後端組只能創建一張stick table,每一個stick table的id或名稱等於後端組名。例如在backend static_group後端建立stick table,則該表的id爲"static_group"。也有特殊方法創建多張,但無必要,可翻官方手冊找方法。

例如,建立一個以源IP地址爲key的stick table,該表容許100W條記錄,5分鐘的記錄過時時長,而且不記錄任何額外數據。

stick-table type ip size 1m expire 5m 

這張表因爲沒有記錄額外的統計數據,每條stickiness記錄在內存中只佔用50字節左右的空間,表滿後整張表在內存中佔用50MB(2^20*50/1024/1024=50MB)。看上去很大,但檢索速度是極快的,徹底不用擔憂性能問題。

若是還要存儲和客戶端創建的鏈接數量計數器(conn_cnt),則:

stick-table type ip size 1m expire 5m store conn_cnt 

conn_cnt佔用32個bit位,即4字節,所以每條stickiness記錄佔用54字節,100W條記錄佔用54M內存空間。

2.2 查看stick table

haproxy沒有直接的接口能夠顯示stick table的相關信息,只能經過stats socket進行查看。該指令表示開啓一個本地unix套接字監聽haproxy的信息,經過這個套接字能夠查看haproxy的不少信息,且能動態調整haproxy配置。

首先在haproxy的配置文件中開啓"stats socket"狀態信息,以下:

global stats socket /var/run/haproxy.sock mode 600 level admin stats timeout 2m 

默認stats timeout的過時時長爲10s,建議設置長一點。上面還設置了socket的權限級別,表示能訪問(600)這個套接字的人具備全部權限(admin)。level還有兩種權限級別更低一點的值"read"和"operator"(默認),前者表示只有讀取信息的權限,不能設置或刪除、清空某些信息,後者表示具有讀和某些設置權限。

本地套接字監聽haproxy後,能夠經過"socat"工具(socket cat,很強大的工具,在epel源中提供)從套接字來操做haproxy。

# 方式一:直接傳遞要執行的操做給套接字 echo "help" | socat unix:/var/run/haproxy.sock - # 方式二:進入交互式模式,而後在交互式模式下執行相關操做 socat readline unix:/var/run/haproxy.sock 

若是要監控某些狀態信息的實時變化,可使用watch命令。

watch -n 1 '"echo show table" | socat unix:/var/run/haproxy.sock -' 

haproxy支持如下列出的全部操做命令:

[root@xuexi ~]# echo "help" | socat unix:/var/run/haproxy.sock - help : this message prompt : toggle interactive mode with prompt quit : disconnect show tls-keys [id|*]: show tls keys references or dump tls ticket keys when id specified set ssl tls-key [id|keyfile] <tlskey>: set the next TLS key for the <id> or <keyfile> listener to <tlskey> set maxconn global : change the per-process maxconn setting set rate-limit : change a rate limiting value set timeout : change a timeout setting show env [var] : dump environment variables known to the process show resolvers [id]: dumps counters from all resolvers section and associated name servers add acl : add acl entry clear acl <id> : clear the content of this acl del acl : delete acl entry get acl : report the patterns matching a sample for an ACL show acl [id] : report available acls or dump an acl's contents add map : add map entry clear map <id> : clear the content of this map del map : delete map entry get map : report the keys and values matching a sample for a map set map : modify map entry show map [id] : report available maps or dump a map's contents show pools : report information about the memory pools usage show sess [id] : report the list of current sessions or dump this session shutdown session : kill a specific session shutdown sessions server : kill sessions on a server clear counters : clear max statistics counters (add 'all' for all counters) show info : report information about the running process show stat : report counters for each proxy and server show errors : report last request and response errors for each proxy clear table : remove an entry from a table set table [id] : update or create a table entry's data show table [id]: report table usage stats or dump this table's contents disable frontend : temporarily disable specific frontend enable frontend : re-enable specific frontend set maxconn frontend : change a frontend's maxconn setting show servers state [id]: dump volatile server information (for backend <id>) show backend : list backends in the current running config shutdown frontend : stop a specific frontend disable agent : disable agent checks (use 'set server' instead) disable health : disable health checks (use 'set server' instead) disable server : disable a server for maintenance (use 'set server' instead) enable agent : enable agent checks (use 'set server' instead) enable health : enable health checks (use 'set server' instead) enable server : enable a disabled server (use 'set server' instead) set maxconn server : change a server's maxconn setting set server : change a server's state, weight or address get weight : report a server's current weight set weight : change a server's weight (deprecated) 

其中和stick table相關的命令有:

clear table    : remove an entry from a table
  set table [id] : update or create a table entry's data show table [id]: report table usage stats or dump this table's contents 

例如:

# on haproxy backend static_group stick-table type ip size 5k expire 1m backend dynamic_group stick-table type ip size 5k expire 1m [root@xuexi ~]# echo "show table" | socat unix:/var/run/haproxy.sock - # table: static_group, type: ip, size:5120, used:0 # table: dynamic_group, type: ip, size:5120, used:0 

本文只是引入stats socket的操做方式,至於各命令的做用,參見官方手冊:http://cbonte.github.io/haproxy-dconv/1.7/management.html#9.3

2.3 使用客戶端源IP做爲客戶端標識符

配置文件部份內容以下:

frontend http-in
    bind             *:80
    mode             http
    log              global

    acl url_static   path_beg  -i /static /images /stylesheets
    acl url_static   path_end  -i .jpg .jpeg .gif .png .ico .bmp .html

    use_backend      static_group   if url_static
    default_backend  dynamic_group

backend dynamic_group
    stick-table type ip size 5k expire 1m
    stick on src
    balance roundrobin
    option http-server-close
    option httpchk  GET /index.php
    http-check expect status 200 server app1 192.168.100.60:80 check rise 1 maxconn 3000 server app2 192.168.100.61:80 check rise 1 maxconn 3000 backend static_group stick-table type ip size 5k expire 1m stick on src balance roundrobin option http-keep-alive http-reuse safe option httpchk GET /index.html http-check expect status 200 server staticsrv1 192.168.100.62:80 check rise 1 maxconn 5000 server staticsrv2 192.168.100.63:80 check rise 1 maxconn 5000 

上面的配置中,設置了acl,當知足靜態訪問時,使用static_group後端組,不然使用dynamic_group後端組。在兩個後端組中,都設置了stick-tablestick on,其中stick on是存儲指定內容,並在請求到達時匹配該內容,它的具體用法見後文。只有配置了stick on後,haproxy才能根據匹配的結果決定是否存儲到stick table中,以及如何篩選待分派的後端。

總之,上面的兩個後端組都已經指定了要向stick table中存儲源ip地址做爲key。當客戶端請求到達時,haproxy根據調度算法分配一個後端,但請求交給後端成功後,Haproxy當即向stick table表中插入一條stickiness記錄。當客戶端請求再次到達時,haproxy發現能匹配源ip,因而按照該stickiness記錄,將請求分配給對應的後端。

如下是分別使用兩臺機器測試192.168.100.59/index.html192.168.100.59/index.php後,stick table記錄的數據。

[root@xuexi ~]# echo "show table static_group" | socat unix:/var/run/haproxy.sock - # table: static_group, type: ip, size:5120, used:2 0x1bc0024: key=192.168.100.1 use=0 exp=48013 server_id=2 0x1bbec14: key=192.168.100.59 use=0 exp=27994 server_id=1 [root@xuexi ~]# echo "show table dynamic_group" | socat unix:/var/run/haproxy.sock - # table: dynamic_group, type: ip, size:5120, used:2 0x1bc00c4: key=192.168.100.1 use=0 exp=53686 server_id=2 0x1bbeb04: key=192.168.100.59 use=0 exp=34309 server_id=1 

其中server_id默認是從1自增的,它能夠在server指令中用"id"選項進行顯式指定。例如:

server staticsrv1 192.168.100.62:80 id 111 check rise 1 max conn 6500 

若是,在使用stickiness的同時,haproxy還設置了cookie,誰的優先級高呢?

2.4 使用cookie做爲客戶端標識符

通常會話保持考慮的對象是應用程序服務器,所以此處咱們忽略後端的靜態服務器,只考慮php應用服務器。在dynamic_group兩個後端server app1和app2的index.php中分別設置好PHPSESSID做爲測試。例如:

<h1>response from webapp 192.168.100.60</h1> <?php session_start(); echo "Server IP: "."<font color=red>".$_SERVER['SERVER_ADDR']."</font>"."<br>"; echo "Server Name: "."<font color=red>".$_SERVER['SERVER_NAME']."</font>"."<br>"; echo "SESSIONNAME: "."<font color=red>".session_name()."</font>"."<br>"; echo "SESSIONID: "."<font color=red>".session_id()."</font>"."<br>"; ?> 

cookie是string的一種特殊狀況,所以建立stick table時,指定type爲string。如下是在haproxy上的配置:

backend dynamic_group
    stick-table type string len 32 size 5k expire 2m
    stick on req.cook(PHPSESSID)
    stick store-response res.cook(PHPSESSID)
    balance roundrobin
    option http-server-close
    option httpchk  GET /index.php
    http-check expect status 200 server app1 192.168.100.60:80 check rise 1 maxconn 3000 server app2 192.168.100.61:80 check rise 1 maxconn 3000 

stick store-response指令表示從響應報文中匹配某些數據出來,而後存儲到stick table中,此處表示截取響應報文中"Set-Cookie"字段中名爲"PHPSESSID"的cookie名進行存儲。stick on req.cook(PHPSESSID)表示從請求報文的"Cookie"字段中匹配名爲PHPSESSID的cookie。若是能和存儲在stick table中的PHPSESSID匹配成功,則表示該客戶端被處理過,因而將其引導到對應的後端服務器上。嚴格地說,這裏不是識別客戶端,而是經過PHPSESSID來識別後端。

某次瀏覽器的請求獲得以下結果:以後每次請求也都是分配到192.168.100.61上。注意,不要使用curl命令來測試,由於這裏是根據PHPSESSID匹配的,curl每次接收到響應後進程就直接退出了,沒法緩存cookie,所以curl每次請求都至關於一次新請求。

在haproxy上查看stick table。

[root@xuexi ~]# echo "show table dynamic_group" | socat unix:/var/run/haproxy.sock - # table: dynamic_group, type: string, size:5120, used:1 0x12163d4: key=g5ossskspc96aecp4hvmsehoh4 use=0 exp=50770 server_id=2 

2.5 使用string做爲客戶端標識符

上面的cookie是string的一種特殊用法。使用string篩選內容進行存儲,靈活性很是大,能夠經過它實現某些複雜、特殊的需求。

例如,從請求報文中截取Host字段的值做爲key存儲起來。

backend dynamic_group
    stick-table type string size 5k expire 2m
    stick on req.hdr(Host)
    balance roundrobin
    option http-server-close
    option httpchk  GET /index.php
    http-check expect status 200 server app1 192.168.100.60:80 check rise 1 maxconn 3000 server app2 192.168.100.61:80 check rise 1 maxconn 3000 

找一臺linux客戶端使用curl進行測試,發現全部請求都將引導到同義後端服務器上。

[root@xuexi ~]# for i in `seq 1 5`;do grep "response" <(curl 192.168.100.59/index.php 2>/dev/null);done <h1>response from webapp 192.168.100.60</h1> <h1>response from webapp 192.168.100.60</h1> <h1>response from webapp 192.168.100.60</h1> <h1>response from webapp 192.168.100.60</h1> <h1>response from webapp 192.168.100.60</h1> 

查看stick table也只能看到一條記錄,並且其key部分正是捕獲到的Host字段的值。

[root@xuexi ~]# echo "show table dynamic_group" | socat unix:/var/run/haproxy.sock - # table: dynamic_group, type: string, size:5120, used:1 0xf0d904: key=192.168.100.19 use=0 exp=46308 server_id=1 

2.6 stick on、stick match、stick store

在前面haproxy的配置中出現過stick onstick store-response,除此以外,還有兩個指令stick matchstick store-request。語法以下:

stick store-request <pattern> [table <table>] [{if | unless} <condition>] stick store-response <pattern> [table <table>] [{if | unless} <condition>] stick match <pattern> [table <table>] [{if | unless} <cond>] stick on <pattern> [table <table>] [{if | unless} <condition>] 

其中stick store指令是從請求或響應報文中截取一部分字符串出來,並將其做爲stickiness的key存儲到stick table中。例如:

# 截取響應報文中名爲PHPSESSID的cookie做爲key stick store-response res.cook(PHPSESSID) # 截取請求報文中Host字段的值做爲key stick store-request req.hdr(Host) # 對請求的源ip地址進行匹配,若不是兄弟網絡中的主機時,就寫入stick table中,且該table名爲dynamic_group stick store-request src table dynamic_group if !my_brother 

stick match是將請求報文中的指定部分和stick table中的記錄進行匹配。例如:

# 截取請求報文中名爲PHPSESSID的cookie,去stick table中搜索是否存在對應的記錄 stick match req.cook(PHPSESSID) # 當源IP不是本機時,去dynamic_group表中搜索是否有能匹配到源IP地址的記錄 stick match src table dynamic_group if !localhost 

stick on等價於stick store+stick match,是它們的簡化寫法。例如:

# 存儲並匹配源IP地址 stick on src #1 = #2 + #3 stick match src #2 stick store-request src #3 # 存儲並匹配源IP地址 stick on src table dynamic_group if !localhost #1 = #2 + #3 stick match src table dynamic_group if !localhost #2 stick store-request src table dynamic_group if !localhost #3 # 存儲並匹配後端服務器設置的PHPSESSID stick on req.cook(PHPSESSID) #1 +#2 = #3 + #4 stick store-response res.cook(PHPSESSID) #2 stick match req.cook(PHPSESSID) #3 stick store-response res.cook(PHPSESSID) #4 

2.7 使用stick table統計狀態信息

stick table除了存儲基本的粘性信息,還能存儲額外的統計數據,這實際上是haproxy提供的一種"採樣調查"功能。它能採集的數據種類有如下幾種:

每一個stickiness記錄中能夠同時存儲多個記錄類型,使用逗號分隔或屢次使用store關鍵字便可。但注意,後端服務器的server id會自動記錄,其它全部額外信息都須要顯式指定。

須要注意,每一個haproxy後端組只能有一張stick table,但卻不建議統計太多額外的狀態信息,由於每多存一個類型,意味着使用更多的內存。

若是存儲全部上述列出的數據類型,須要116字節,100W條記錄要用116M,這不是能夠忽略的大小。此外還有50M的key,共166M。

例以下面的示例中,使用了通用計數器累計,並記錄了每30秒內的平均鏈接速率。

stick-table type ip size 1m expire 5m store gpc0,conn_rate(30s) 

 

轉載請註明出處:http://www.cnblogs.com/f-ck-need-u/p/8558514.html

相關文章
相關標籤/搜索