haproxy会话保持方式
haproxy实现会话保持有3种方式:
- 源ip hash,因后端节点扩展时,重新调度结果波动较大,一般不采用;(可采用一致性hash使得波动较小;)
- cookie,haproxy在发给客户端的响应报文头部加上一个键值对到cookie中,记录了第一次被分配的后端节点标识id,后续请求根据id分配到相同节点
- stick table,以请求的某些特征,如源ip,cookie,请求host做key,后端节点的标识id做value,组成一条k-v数据,多条数据组成stick table,再次请求时,相关请求的特性相同能关联到相同的后端节点id,进行保证相关请求分配到相同节点;(stick table是存于haproxy实例的内存中,haproxy作为调度器也应该考虑高可用,同时,stick-table要做各个haproxy之间的复制,做高可用)
- 注意:此3种方式,都是实现将同一个客户端相关请求调度到相同的后端节点上,从而实现了请求的会话保持,前提是后端节点不挂掉,如果为了保证会话信息的高可用,解决方案:会话复制replication或做了高可用的会话服务器如redis
会话保持之cookie
https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#4-cookie
语法
cookie <name> [ rewrite | insert | prefix ] [ indirect ] [ nocache ]
[ postonly ] [ preserve ] [ httponly ] [ secure ]
[ domain <domain> ]* [ maxidle <idle> ] [ maxlife <life> ]
3种模式,
rewrite 在原有cookie的基础上覆盖写入
prefix 加在原有cookie头部
insert 添加一个cookie头部,放haproxy设置的cookie信息;
示例
backend group1
cookie myapp insert nocache
server app1 192.168.80.102:80 cookie server1
server app2 192.168.80.103:80 cookie server2
在上面的配置中,设置了一个cookie,key是myapp
值分别是server1 server2,第一次请求时若被分到了app1这个节点,那么haproxy就给其响应报文,添加一个cookie为 myapp=server1
如下,在请求头部发现,再次请求已经携带了myapp=server1这个cookie,证明第一次请求被分配到了server1处理,并从响应报文中获得了这个cookie,存储于本地;PHPSESSID是php设置了应用的id,为每个客户端关联状态会话信息的;
insert
insert表示,在转交后端节点发来的响应报文头部,插入一个cookie,k-v都由haproxy设定,没配置preserve关键词时,后端若设置了和haproxy同名的cookie,那后端的cookie会被删除顶掉!
haproxy设置的cookie没有maxAge属性,所以无法持久保持到客户端磁盘,只存在于客户端浏览器的内存缓存中;
为避免缓存影响,建议配合nocache或postonly选项!
haproxy和后端节点设置cookie名称一致时:
backend dynamic_group
cookie PHPSESSID insert nocache
...
将haproxy设置的cookie的名称改为PHPSESSID,和后端节点保持一致,此时,再次请求,发现后端节点的cookie被haproxy顶掉!
测试时,需要关闭浏览器,重新打开排除缓存影响;
一般haproxy和后端节点cookie名要不一致
前次服务的节点挂掉时
将sever1 停掉服务,再次请求,发现2次请求的haproxy的cookie会变,即haproxy在节点挂掉时,会重新分配节点,也会重新修改cookie值为新节点的标识id,但应用设置的cookie保持不变,此时虽然能被调度新节点,但原来请求的会话就会丢失,除非会话设置了共享,或存在会话服务器
indirect指令
indirect指令是指不向后端转发haproxy添加的cookie信息,
1、修改httpd日志如下,添加记录cookie的日志格式,重启httpd
<IfModule log_config_module>
#
# The following directives define some format nicknames for use with
# a CustomLog directive (see below).
#
LogFormat " \"%{COOKIE}i \"%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
2、查看日志,发现此时可以收到haproxy添加的cookie
[root@host4 ~]# tailf /var/log/httpd/access_log
...
"PHPSESSID=bo31ohofi6rrpars7kl1f83r20; ha_cook=server2 "
3、haproxy配置indirect指令:
backend dynamic_group
cookie ha_cook insert nocache indirect
balance roundrobin
4、再次查看日志,此时无法获取haproxy设置的cookie
[root@host4 ~]# tailf /var/log/httpd/access_log
...
"PHPSESSID=bo31ohofi6rrpars7kl1f83r20 "
preserve指令
backend dynamic_group
cookie PHPSESSID insert nocache indirect preserve
balance roundrobin
将haproxy设置的cookie和后端节点保持一致,但使用了preserve选项,
此时访问2个重名的cookie,保留的是后端节点设置的cookie,
此时haproxy的cookie无效,因为没了cookie,调度效果变成了轮询
prefix
要求:haproxy设置的cookie名称和后端节点的cookie名称保持一致;haproxy添加的cookie值,即各个后端节点的标识id会被添加在后端节点设置的cookie值前面,在向后转发时,haproxy会拿掉自己的cookie值,后端节点看不到haproxy设置的cookie;
1、haproxy配置和后端节点同名的cookie名,选项使用prefix
backend dynamic_group
cookie PHPSESSID prefix nocache
balance roundrobin
option http-server-close
option httpchk GET /index.php
http-check expect status 200
server appsrv1 192.168.80.102:80 check rise 1 maxconn 3000 cookie server1
server appsrv2 192.168.80.103:80 check rise 1 maxconn 3000 cookie server2
2、后端节点的session设置
<h1>response from webapp 192.168.80.102</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>";
?>
3、查看后端日志,可以看到,haproxy添加的部分,并没转发给后端节点
[root@host4 ~]# tailf /var/log/httpd/access_log
"PHPSESSID=8jkucobunbicfa05uul9pljrh6 "
4、浏览器检查报文头部,看到haproxy添加部分加到了会话设置cookie值的前面,用~隔开
rewrite
rewrite需要haproxy和后端节点cookie名称保持一致,但是haproxy会覆盖应用服务器设置的cookie值,此时客户端只能得到haproxy设置的cookie值,再次请求时,虽然能保证同一个节点的调度,但是因为客户端没有获取到应用服务器设置的cookie值,服务端也就无法将其和上一次请求关联起来;
1、haproxy选择rewrite选项,cookie名称和后端需保持一致;
backend dynamic_group
cookie PHPSESSID rewrite nocache
balance roundrobin
option http-server-close
option httpchk GET /index.php
http-check expect status 200
server appsrv1 192.168.80.102:80 check rise 1 maxconn 3000 cookie server1
server appsrv2 192.168.80.103:80 check rise 1 maxconn 3000 cookie server2
2、后端节点的session设置
<h1>response from webapp 192.168.80.102</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>";
?>
3、后端节点日志,看到的只有haproxy设置的cookie
"PHPSESSID=server1"
4、浏览器只能收到haproxy设置的cookie
忽略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
对于静态资源的访问,应该不考虑cookie的会话保持
设置ignore-persist 可以忽略cookie的设置;
会话保持之stick table
stick table简介
除了源ip的hash,设置cookie,haproxy可以利用内存中的stick table,将客户端和上一次调度过的后端节点关联起来;**方法:截取客户端的特性信息,如ip,携带的独有cookie,某长度的报文string,做为key,后端节点的标识id作为value,组成k-v类型的数据 ;**相同客户端请求过来时由于特征信息的唯一性,可以通过查表得到上次调度的后端节点,从而实现调度时的会话保持
特性:
- 多主模型之间复制效率高;
- 占用内存小
- 可以添加额外的统计信息,根据统计信息可以统计每个节点的接入请求,速率等;做一些统计、分析监控之用;
示例
stick-table type {ip | integer | string [len <length>] | binary [len <length>]}
size <size> [expire <expire>] [nopurge] [peers <peersect>]
[store <data_type>]*
type 选择客户端ip,某个整数,或从报文截取的某长度的字符串 作为key
size 表大小
expire 过期时间
peers 多主haproxy复制时,对端节点信息
store 指定要添加哪些额外信息
# 每个后端组,只能有一个stick table,表名和后端组名称相同;
如下,为一个源ip为key的stick table,表容量100w条记录,过期时间5m,无额外统计信息;
stick-table type ip size 1m expire 5m
stick-table type ip size 1m expire 5m store conn_cnt
添加了一项连接计数器,conn_cnt为haproxy内置变量;其余可查看官方doc
查看stick talble
1,给2个后端组,都创建一个stick table
[root@host2 ~]# vim /etc/haproxy/haproxy.cfg
backend static_group
stick-table type ip size 1m expire 5m
backend dynamic_group
stick-table type ip size 1m expire 5m
[root@host2 ~]# systemctl restart haproxy
2,用socat工具查看新生成的2个table,表名自动和后端组保持一致;
[root@host2 ~]# echo "show table" | socat stdio /var/lib/haproxy/stats
# table: static_group, type: ip, size:1048576, used:0
# table: dynamic_group, type: ip, size:1048576, used:0
和stick table相关的命令
[root@host2 ~]# echo "help" | socat stdio /var/lib/haproxy/stats
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
客户端ip做key
# stick on src表示启用 type 为ip的记录
backend dynamic_group
stick-table type ip size 1m expire 5m
stick on src
cookie ha_cook insert nocache
balance roundrobin
option http-server-close
option httpchk GET /index.php
http-check expect status 200
server appsrv1 192.168.80.102:80 check rise 1 maxconn 3000 cookie server1
server appsrv2 192.168.80.103:80 check rise 1 maxconn 3000 cookie server2
# 查看生成的表信息,分别记录了2个客户端的信息;
[root@host2 ~]# echo "show table dynamic_group" | socat stdio /var/lib/haproxy/stats
# table: dynamic_group, type: ip, size:1048576, used:2
0x248a784: key=192.168.80.1 use=0 exp=265067 server_id=2
0x248a674: key=192.168.80.100 use=0 exp=259197 server_id=1
应用服务器生成的cookie做key
1,定义采用cookie做key的stick-table
backend dynamic_group
stick-table type string len 32 size 1m expire 5m
stick on req.cook(PHPSESSID)
stick store-response res.cook(PHPSESSID)
cookie ha_cook insert nocache
balance roundrobin
option http-server-close
option httpchk GET /index.php
http-check expect status 200
server appsrv1 192.168.80.102:80 check rise 1 maxconn 3000 cookie server1
server appsrv2 192.168.80.103:80 check rise 1 maxconn 3000 cookie server2
stick store-response res.cook(PHPSESSID)
先: 截取响应报文中的key为PHPSESSID的cookie,做为表的key,发出该响应报文的后端节点id做value
stick on req.cook(PHPSESSID)
然后: 截取客户端请求中key为PHPSESSID的cookie,做对比,和表里的key做对比,key相同表示该请求在之前是由对应的节点所生成的session;实现了会话保持
stick-table type string len 32 size 1m expire 5m
截取cookie属于string 类型的一种;
2,查看生成的表,key是应用服务器生成的session值,value是生成该session的服务器;
[root@host2 ~]# echo "show table dynamic_group" | socat stdio /var/lib/haproxy/stats
# table: dynamic_group, type: string, size:1048576, used:1
0x2575804: key=00302nbkngemu1n2317dofgff2 use=0 exp=288910 server_id=2
截取string做key
string可以截取多种,下方截取了请求报文的host头部做key
# 截取host做key
backend dynamic_group
stick-table type string len 32 size 1m expire 5m
stick on req.hdr(Host)
cookie ha_cook insert nocache
balance roundrobin
option http-server-close
option httpchk GET /index.php
http-check expect status 200
server appsrv1 192.168.80.102:80 check rise 1 maxconn 3000 cookie server1
server appsrv2 192.168.80.103:80 check rise 1 maxconn 3000 cookie server2
# 客户端请求
[root@host1 ~]# curl 192.168.80.101
# 查看表数据,只要是请求80.101的都被匹配到发往server_id=1的后端节点
[root@host2 ~]# echo "show table dynamic_group" | socat stdio /var/lib/haproxy/stats
# table: dynamic_group, type: string, size:1048576, used:0
[root@host2 ~]# echo "show table dynamic_group" | socat stdio /var/lib/haproxy/stats
# table: dynamic_group, type: string, size:1048576, used:1
0x25ff734: key=192.168.80.101 use=0 exp=297121 server_id=1
stick store、match、on
- 先stick store从报文中截取信息做key,存入表中,
- 客户端请求时,用stick match 和表中key匹配,匹配到的key,其value就是将要被分配的节点
- stick on,等于先stick store,后stick match的结合
stick table统计信息
官方介绍:
https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#4-stick-table
stick table 复制
https://www.haproxy.com/blog/introduction-to-haproxy-stick-tables/
介绍
节点间复制:stick table,可以高效的在各个haproxy节点之间复制,同步新增的stick-table信息;
进程间复制:同节点之间,haproxy重启时,新老进程之间建立tcp连接,可以复制stick-table信息,使得信息不因重启而丢失;重启后的新进程可以,继续和其他haproxy节点进行stick table复制;
一般来说,后端应用服务器实现了会话复制,或第三方会话服务器的情况下,haproxy根本不需要使用stick table或cookie,即便使用,也一般是为了实现统计数据,实现调度时的会话粘性,因为每个后端节点都可以读取相同的会话信息;
配置
1、下载haproxy
yum install -y haproxy
2、修改配置文件,添加haproxy的组信息
# global段,利用peers指令,指定各个haproxy节点的信息,
# test_peer为组名
# ha1相当于别名
# 12138为stick table复制时的通信端口
peers test_peer
peer ha1 192.168.80.101:12138
peer ha2 192.168.80.102:12138
# 定义前端和后端信息
frontend test1 *:8080
use_backend app
backend app
stick-table type ip size 100k expire 5m peers test_peer
# 定义stick table时,用peers 引用globa中定义的peer组;
balance roundrobin
server app2 192.168.80.102:80 check
server app1 192.168.80.101:80 check
3、启动haproxy进程
# 命令行方式启动haproxy,-D后台运行,-L执行自己在peer组中的别名,此处是ha2, -f指定配置文件;
# 同样的,将相同的启动参数(-L 自己的别名),写入服务启动文件中,效果一样,就可以用systemctl来启动haproxy
[root@host3 ~]# haproxy -D -L ha2 -f /etc/haproxy/haproxy.cfg
[root@host3 ~]# ss -nlt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:5000 *:*
LISTEN 0 6 192.168.80.102:12138 *:*
LISTEN 0 128 *:8080 *:*
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*
4、查看stick table信息
# 安装socat包,查看其中的信息即可
yum install -y socat
echo "show table" | socat stdio /var/lib/haproxy/stats
# 分别向2个haproxy节点,进行请求,可以发现,向ha2,即host3请求,会加入一条stick table信息
# 向ha1的haproxy请求,ha2上仍能看到新加的stick table信息;可见,是节点间对stick table进行了复制,复制成功!
[root@host3 ~]# echo "show table app " | socat stdio /var/lib/haproxy/stats
# table: app, type: ip, size:102400, used:1
0x55815f180fb4: key=192.168.80.102 use=0 exp=215037 server_id=1
[root@host3 ~]# echo "show table app " | socat stdio /var/lib/haproxy/stats
# table: app, type: ip, size:102400, used:2
0x55815f181054: key=192.168.80.101 use=0 exp=297727 server_id=2
0x55815f180fb4: key=192.168.80.102 use=0 exp=185204 server_id=1
reference
https://www.cnblogs.com/f-ck-need-u/p/8565998.html#1-stick-table-