haproxy實現會話保持(1):cookie

来源:https://www.cnblogs.com/f-ck-need-u/archive/2018/03/13/8553190.html
-Advertisement-
Play Games

既然進來了,不妨點個贊鼓勵下我吧!謝謝! 本文目錄: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".

其中大致說明瞭以下幾個意思:

  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都添加到可緩存的頁面上,則所有客戶都將從中間的緩存層獲取頁面,並且將共用同一個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

註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 內容:Java變數,基本數據類型 邏輯類型:boolean =true/false整數類型:byte占一個位元組,short占兩個位元組,int占四個位元組,long占8個位元組 int 可以表達十進位範圍 (2147483648,4294967296)字元類型:char占兩個位元組 (加單引號)ch=97, ...
  • 前言 在一個小項目的需求中,我需要一個短鏈生成伺服器來縮短一些某個網站的鏈接。 剛開始我使用的是新浪的短鏈生成服務,後來心血來潮Google了一下短鏈生成的演算法,在知乎上看到了 "一個非常棒的構思" ,也就是直接使用資料庫的id的62進位形式作為短鏈索引。 當天我們就把新浪的短鏈換成了自己的服務,不 ...
  • 作為一個java的學習者,我相信JDBC是大家最早接觸也是入門級別的資料庫連接方式,所以我們先來回憶一下JDBC作為一種用於執行SQL語句的Java API是如何工作的。下麵的一段代碼就是最基本的JDBC開發流程。 在上代碼之前要先導入JDBC的jar包,由於我用的資料庫是mysql,所以要先導 入 ...
  • 兩個星期前,微軟發佈了 "EF Core 2.1 Preview 1" ,同時還發佈了 ".NET Core 2.1 Preview 1" 和 "ASP.NET Core 2.1 Preview 1" ;EF Core 2.1 Preview 1 除了 "許多小改進和超過100種產品錯誤修複之外" ...
  • 本次 Windows Developer Day,最值得期待的莫過於 Windows AI Platform 了,可以說是千呼萬喚始出來。觀看直播的開發者們,留言最多的也是 Windows AI Platform。 下麵結合微軟提供的展示過程,文檔和 Git Sample 來詳細分析一下。 基礎概念 ...
  • 以前實現數據的緩存有多種方法,如客戶端的Cookie,伺服器端的Session、Application。 一、Cookie Cookie是保存客戶端的一組數據,主要用來保存用戶的個人信息,主要存放瀏覽器請求伺服器時的請求信息,這些信息是非敏感信息。主要用於當用戶訪問您的系統時,應用程式可以檢索以前存 ...
  • RabbitMQ原理及教程:http://www.cnblogs.com/AlvinLee/p/6141834.html EasyNetQ是什麼以及常見用法這裡都不做講解。可以參考這篇博文:http://blog.csdn.net/hesi9555/article/details/70139346 ...
  • 前言 在說C# Hook之前,我們先來說說什麼是Hook技術。相信大家都接觸過外掛,不管是修改游戲客戶端的也好,盜取密碼的也罷,它們都是如何實現的呢? 實際上,Windows平臺是基於事件驅動機制的,整個系統都是通過消息的傳遞來實現的。當進程有響應時(包括響應滑鼠和鍵盤事件),則Windows會嚮應 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...