本文目錄:1.stickiness和stick table簡介2.使用stick table 2.1 創建stick table 2.2 查看stick table 2.3 使用客戶端源IP作為客戶端標識符 2.4 使用cookie作為客戶端標識符 2.5 使用String作為客戶端標識符 2.6 ...
本文目錄:
1.stickiness和stick table簡介
2.使用stick table
2.1 創建stick table
2.2 查看stick table
2.3 使用客戶端源IP作為客戶端標識符
2.4 使用cookie作為客戶端標識符
2.5 使用String作為客戶端標識符
2.6 stick on、stick match、stick store
2.7 使用stick table統計狀態信息
在上一篇文章中,分析了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表示,分別表示1024、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-table
和stick 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.html
和192.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 on
和stick store-response
,除此之外,還有兩個指令stick match
、stick 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)
回到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/8558514.html
註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!