既然進來了,不妨點個贊鼓勵下我吧!謝謝! 本文目錄:1. 反向代理為什麼需要設置cookie2.haproxy設置cookie的幾種方式 2.1 cookie insert 2.2 cookie prefix 2.3 cookie rewrite3.haproxy如何使用cookie實現會話保持以及 ...
既然進來了,不妨點下推薦鼓勵下我吧!謝謝!
本文目錄:
1. 反向代理為什麼需要設置cookie
2.haproxy設置cookie的幾種方式
2.1 cookie insert
2.2 cookie prefix
2.3 cookie rewrite
3.haproxy如何使用cookie實現會話保持以及如何忽略會話保持
1.反向代理為什麼需要設置cookie
任何一個七層的http負載均衡器,都應該具備一個功能:會話保持。會話保持是保證客戶端對動態應用程式正確請求的基本要求。
還是那個被舉爛了卻最有說服力的例子:客戶端A向服務端B請求將C商品加入它的賬戶購物車,加入成功後,服務端B會在某個緩存區域中記錄下客戶端A和它的商品C,這個緩存的內容就是session上下文環境。而識別客戶端的方式一般是設置session ID(如PHPSESSID、JSESSIONID),並將其作為cookie的內容交給客戶端。客戶端A再次請求的時候(比如將購物車中的商品下訂單)只要攜帶這個cookie,服務端B就可以從中獲取到session ID並找到屬於客戶端A的緩存內容(商品C),也就可以繼續執行下訂單部分的代碼。
假如這時使用負載均衡軟體對客戶端的請求進行負載,就必須要保證能將客戶端A的請求再次引導到服務端B,而不能引導到服務端X、服務端Y,因為X、Y上並沒有緩存和客戶端A對應的session內容,也就無法為客戶端A下訂單。
因此,反向代理軟體必須具備將客戶端和服務端"綁定"的功能,也就是所謂的提供會話保持,讓客戶端A後續的請求一定轉發到服務端B上。
這裡討論的對象是http的動態應用請求,它要求會話保持。更通用地,只要負載均衡軟體負載的不是"無狀態"的協議或服務,就應該提供會話保持能力,除非它是四層負載軟體。
haproxy提供了3種實現會話保持的方式:
- (1).源地址hash;
- (2).設置cookie;
- (3).會話粘性表stick-table;
本文只討論haproxy在設置cookie上實現會話保持的方式,stick-table會話粘性的方式則在下一篇文章中單獨討論。而源地址hash是一種負載調度演算法,沒什麼可討論的,而且除非實在沒辦法,不建議使用這種調度演算法。
2.haproxy設置cookie的幾種方式
設置cookie的方式是通過在配置文件中使用cookie指令進行配置的。由於haproxy設置cookie的目的是為了將某客戶端引導到之前為其服務過的後端伺服器上,簡單地說,就是和後端某伺服器保持聯繫,因此cookie指令不能設置在frontend段落。
首先看一個設置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".
其中大致說明瞭以下幾個意思:
- 該關鍵詞表示,haproxy將在客戶端沒有cookie時(比如第一次請求),在響應報文中插入一個cookie。
- 當沒有使用關鍵詞"preserve"選項時,如果後端伺服器設置了一個和此處名稱相同的cookie,則首先刪除服務端設置的cookie。
- 該cookie只能作為會話保持使用,無法持久化到客戶端的磁碟上(因為haproxy設置的cookie沒有maxAge屬性,無法持久保存,只能保存在瀏覽器緩存中)。
- 預設情況下,除非使用了"indirect"選項,否則服務端可以看到客戶端請求時的所有cookie信息。
- 由於緩存的影響,建議加上"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都添加到可緩存的頁面上,則所有客戶都將從中間的緩存層獲取頁面,並且將共用同一個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
衝突。
回到Linux系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7048359.html
回到網站架構系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7576137.html
回到資料庫系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7586194.html
轉載請註明出處:http://www.cnblogs.com/f-ck-need-u/p/8553190.html
註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!