haproxy会话保持实现

haproxy会话保持实现

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,为每个客户端关联状态会话信息的;

image-20200917125006041

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名要不一致

image-20200917145428629

前次服务的节点挂掉时

​ 将sever1 停掉服务,再次请求,发现2次请求的haproxy的cookie会变,即haproxy在节点挂掉时,会重新分配节点,也会重新修改cookie值为新节点的标识id,但应用设置的cookie保持不变,此时虽然能被调度新节点,但原来请求的会话就会丢失,除非会话设置了共享,或存在会话服务器

image-20200917150746288

image-20200917150726893

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,调度效果变成了轮询

image-20200917153936716

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值的前面,用~隔开

image-20200917154910043

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

image-20200917155650011

忽略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

image-20200917174254770

截取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

  1. 先stick store从报文中截取信息做key,存入表中,
  2. 客户端请求时,用stick match 和表中key匹配,匹配到的key,其value就是将要被分配的节点
  3. 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-

updatedupdated2020-10-222020-10-22
加载评论