haproxy的stick table复制

时间:2023-03-08 19:32:52

HAProxy系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.html


在上一篇文章中,分析了haproxy的stick table特性和用法,其中特性之一也是很实用的特性是stick table支持在haproxy多个节点之间进行复制(replication)。

本文仅讨论如何配置实现stick table的复制功能,不考虑在什么环境下实现它,以及它的双主模型如何配置。

本文实验环境:

haproxy的stick table复制

1.stick table复制特性

只要设置好haproxy的节点组,haproxy就会自动将新插入的、刚更新的stickiness记录通过TCP连接推送到节点组中的非本地节点上。这样一来,stickiness记录不会丢失,即使某haproxy节点出现了故障,其他节点也能将客户端按照粘性映射关系引导到正确的后端服务器上。

由于每条stickiness记录占用空间都很小(平均最小50字节,最大166字节,由是否记录额外统计数据以及记录多少来决定占用空间大小),使得即使在非常繁忙的环境下多个节点之间推送都不会出现压力瓶颈和网络阻塞(可以按节点数量、stickiness记录的大小和平均并发量来计算每秒在网络间推送的数据流量)。

它不像被人诟病的session复制(copy),因为session复制的数据量比较大,而且是在各应用程序服务器之间进行的。而一个稍大一点的核心应用,提供服务的应用程序服务器数量都不会小,这样复制起来很容出现网络阻塞。关于stick table的复制效率,可看文章结尾的测试

此外,stick table还可以在haproxy重启时,在新旧两个进程间进行复制,这是本地复制。当haproxy重启时,旧haproxy进程会和新haproxy进程建立TCP连接,将其维护的stick table推送给新进程。这样新进程不会丢失粘性信息,和其他节点也能最大程度地保持同步,使得其他节点只需要推送该节点重启过程中新增加的stickiness记录就能完全保持同步。

如果后端使用了session共享,在大多数情况下没必要在代理层实现会话保持。如果此时使用stick table,一般只是为了收集统计数据进行采样调查,但这样的状态统计数据无需在各节点之间进行复制。

2.如何定义haproxy节点成员

haproxy提供了peers指令,用于定义节点组,peer指令用于定义节点组中的成员。

例如,定义本文测试环境的节点组:

peers my_peers    # 节点组名
peer haproxy1 192.168.100.59:12138 # 定义对端名称,以及和对端建立tcp连接的端口,用于推送stickiness记录
peer haproxy2 192.168.100.54:12138
peer haproxy3 192.168.100.42:12138

然后在stick-table指令中引用节点组。例如:

stick-table type ip size 100k expire 5m peers my_peers

注意,应当让每个haproxy节点的节点组内容一致,然后使用haproxy命令的"-L"选项指定本地节点成员,这样方便管理每个节点和本地节点。

haproxy -D -L haproxy1 -f /etc/haproxy/haproxy.cfg

如果不指定"-L",则在解析配置文件时将默认以主机名作为本地节点名进行解析,但很可能它不会定义在peers中,因此会报错。

因此,如果要实现stick table的复制,还想使用sysV或systemctl管理haproxy服务,需要修改这些服务管理脚本,在启动、重启和语法检查项上加上"-L"选项。例如,systemd的haproxy服务管理脚本改为如下内容:

[root@xuexi ~]# cat /usr/lib/systemd/system/haproxy.service
[Unit]
Description=HAProxy Load Balancer
After=syslog.target network.target [Service]
EnvironmentFile=/etc/sysconfig/haproxy
ExecStartPre=/usr/sbin/haproxy -L haproxy2 -f /etc/haproxy/haproxy.cfg -c -q
ExecStart=/usr/sbin/haproxy-systemd-wrapper -L haproxy2 -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid $OPTIONS
ExecReload=/usr/sbin/haproxy -L haproxy2 -f /etc/haproxy/haproxy.cfg -c -q
ExecReload=/bin/kill -USR2 $MAINPID
KillMode=mixed [Install]
WantedBy=multi-user.target

另外个人建议,每个节点要么完全使用sysV、systemctl管理haproxy的启动、停止,要么完全使用haproxy命令手动管理服务的启动和停止,否则可能会出现记录不同步的现象。至今没找到会出现这个问题的原因。

3.完成配置

如下,是每个haproxy节点的配置文件内容。

global
log 127.0.0.1 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 20000
user haproxy
group haproxy
daemon
stats socket /var/run/haproxy.sock mode 600 level admin
spread-checks 2 peers mypeers
peer haproxy1 192.168.100.59:12138
peer haproxy2 192.168.100.54:12138
peer haproxy3 192.168.100.42:12138
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
timeout http-request 2s
timeout queue 3s
timeout connect 1s
timeout client 10s
timeout server 2s
timeout http-keep-alive 10s
timeout check 2s
maxconn 18000 frontend http-in
bind *:80
mode http
log global
default_backend dynamic_group backend dynamic_group
stick-table type string len 32 size 5k expire 5m peers mypeers
stick on req.cook(PHPSESSID)
stick store-response res.cook(PHPSESSID)
balance roundrobin
option http-server-close
option httpchk GET /index.php
option redispatch
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

然后分别在3个节点上启动haproxy,注意加上"-L"选项。

haproxy -D -L haproxy1 -f /etc/haproxy/haproxy.cfg    # exec on haproxy1
haproxy -D -L haproxy2 -f /etc/haproxy/haproxy.cfg # exec on haporxy2
haproxy -D -L haproxy3 -f /etc/haproxy/haproxy.cfg # exec on haproxy3

分别在haproxy1、haproxy2、haproxy3上监控stick table。如果下面命令执行失败,见查看stick table信息

watch -n 1 'echo "show table dynamic_group" | socat unix:/var/run/haprox.sock -'

由于上面的配置文件中,stick table中存储和匹配的记录是响应报文中名为PHPSESSID的cookie,因此在浏览器上测试时,能实现会话粘性。但如果使用curl命令进行测试,由于curl每次执行结束进程就退出,无法缓存cookie,因此curl的每次请求都是新连接。此处正好利用curl这一点特性,来生成多个stickiness记录,方便观察stick table记录推送的情况。

找一台Linux主机,分别对三个haproxy节点进行5次curl请求。

ip1=192.168.100.59
ip2=192.168.100.54
ip3=192.168.100.42
for i in $ip1 $ip2 $ip3;do
for j in `seq 1 5`;do
curl http://$i/index.php &>/dev/null
usleep 500000
done
done

以下是截取自三个节点的stick table内容。

###################### haproxy1 ########################
# table: dynamic_group, type: string, size:5120, used:15
0x1551cc4: key=1t1lr3s5s7pm4tuu3476c40jk3 use=0 exp=286311 server_id=2
0x1551f04: key=3m3o6qkfvk8l4lenk0k8to2bd6 use=0 exp=288500 server_id=2
0x1551904: key=8bg6ej7md7453sp0rrgi5vsiu7 use=0 exp=281350 server_id=1
0x1552144: key=9c4lf1tncd290mjl7vnjafnqe2 use=0 exp=287418 server_id=2
0x1552744: key=b3gqn2i3emi43rfh3i8t0jhln4 use=0 exp=283523 server_id=1
0x15519c4: key=cskoqa8vbb7fbac4eko27hnk70 use=0 exp=281897 server_id=2
0x1550c54: key=ejjgpe1j99nv7hqmnlpib5osq6 use=0 exp=286867 server_id=1
0x1550fc4: key=immqb6ocsq8m5982o28hatk6c2 use=0 exp=285187 server_id=2
0x1550e44: key=lg78n74u0dj51m3cvkn9qhce66 use=0 exp=287953 server_id=1
0x15522c4: key=livn0t62bthpfvp5motknhb934 use=0 exp=284620 server_id=1
0x1551b44: key=mhkh0c4lpisr9kgdb3ggmplpk1 use=0 exp=285745 server_id=1
0x1552384: key=ovfoprd6p91sjbf22qo5jjd4u0 use=0 exp=289035 server_id=1
0x1552204: key=rvt3fnhr51sfrqu1aefjceqlu4 use=0 exp=282978 server_id=2
0x1550d84: key=snkm72pvasr36ut39qopd3jmb1 use=0 exp=282442 server_id=1
0x1552084: key=ucj2pkve527nt39mejp1n7jdu5 use=0 exp=284084 server_id=2 ###################### haproxy2 ########################
# table: dynamic_group, type: string, size:5120, used:15
0x7fa77ec4c8a4: key=1t1lr3s5s7pm4tuu3476c40jk3 use=0 exp=278032 server_id=2
0x7fa77ec4d4e4: key=3m3o6qkfvk8l4lenk0k8to2bd6 use=0 exp=280404 server_id=2
0x7fa77ec4c2a4: key=8bg6ej7md7453sp0rrgi5vsiu7 use=0 exp=272658 server_id=1
0x7fa77ec4c424: key=9c4lf1tncd290mjl7vnjafnqe2 use=0 exp=279232 server_id=2
0x7fa77ec4be24: key=b3gqn2i3emi43rfh3i8t0jhln4 use=0 exp=275012 server_id=1
0x7fa77ec4c4e4: key=cskoqa8vbb7fbac4eko27hnk70 use=0 exp=273251 server_id=2
0x7fa77ec4c724: key=ejjgpe1j99nv7hqmnlpib5osq6 use=0 exp=278635 server_id=1
0x7fa77ec4d2a4: key=immqb6ocsq8m5982o28hatk6c2 use=0 exp=276814 server_id=2
0x7fa77ec4c964: key=lg78n74u0dj51m3cvkn9qhce66 use=0 exp=279812 server_id=1
0x7fa77ec4d664: key=livn0t62bthpfvp5motknhb934 use=0 exp=276201 server_id=1
0x7fa77ec4d7e4: key=mhkh0c4lpisr9kgdb3ggmplpk1 use=0 exp=277418 server_id=1
0x7fa77ec4c1e4: key=ovfoprd6p91sjbf22qo5jjd4u0 use=0 exp=280984 server_id=1
0x7fa77ec4d724: key=rvt3fnhr51sfrqu1aefjceqlu4 use=0 exp=274422 server_id=2
0x7fa77ec4c664: key=snkm72pvasr36ut39qopd3jmb1 use=0 exp=273842 server_id=1
0x7fa77ec4d424: key=ucj2pkve527nt39mejp1n7jdu5 use=0 exp=275620 server_id=2 ###################### haproxy3 ########################
# table: dynamic_group, type: string, size:5120, used:15
0x1372c24: key=1t1lr3s5s7pm4tuu3476c40jk3 use=0 exp=276198 server_id=2
0x1372e64: key=3m3o6qkfvk8l4lenk0k8to2bd6 use=0 exp=278387 server_id=2
0x1372864: key=8bg6ej7md7453sp0rrgi5vsiu7 use=0 exp=271237 server_id=1
0x1373964: key=9c4lf1tncd290mjl7vnjafnqe2 use=0 exp=277304 server_id=2
0x1373f64: key=b3gqn2i3emi43rfh3i8t0jhln4 use=0 exp=273410 server_id=1
0x1372924: key=cskoqa8vbb7fbac4eko27hnk70 use=0 exp=271784 server_id=2
0x13723e4: key=ejjgpe1j99nv7hqmnlpib5osq6 use=0 exp=276753 server_id=1
0x13726e4: key=immqb6ocsq8m5982o28hatk6c2 use=0 exp=275073 server_id=2
0x1372564: key=lg78n74u0dj51m3cvkn9qhce66 use=0 exp=277840 server_id=1
0x1373ae4: key=livn0t62bthpfvp5motknhb934 use=0 exp=274507 server_id=1
0x1372aa4: key=mhkh0c4lpisr9kgdb3ggmplpk1 use=0 exp=275631 server_id=1
0x1373ba4: key=ovfoprd6p91sjbf22qo5jjd4u0 use=0 exp=278922 server_id=1
0x1373a24: key=rvt3fnhr51sfrqu1aefjceqlu4 use=0 exp=272865 server_id=2
0x13724a4: key=snkm72pvasr36ut39qopd3jmb1 use=0 exp=272330 server_id=1
0x13738a4: key=ucj2pkve527nt39mejp1n7jdu5 use=0 exp=273971 server_id=2

结果符合预期目标,每个表都有15条stickiness,且它们的key和对应的server_id完全相同。

4.测试并发请求下的stick table复制效率

再来个大一点的并发测试。环境如下:这个环境并不合理,但不影响根据测试得出结果。

haproxy的stick table复制

3个haproxy节点进行replication。它们都代理3个php app server。

然后使用ab进行测试其中一个haproxy节点,并发请求数为1000,总共请求50000个请求。

ab -c 1000 -n 50000 -r http://192.168.100.42/index.php

结果如图,没有错误请求的状况下,5W个请求总共花费10.718秒。

haproxy的stick table复制

监控3个haproxy节点的stick table复制情况,并秒表计时。结果如图:

haproxy的stick table复制

从结果中看到,3个节点之间完全同步50000个stickiness记录,比请求结束只多花了1.8秒,其中这1.8秒还有我迟钝的反应时间和按下停止计时按钮的时间。也就是说,50000个stickiness记录完全同步完成的时间比请求完成最多也就多花不到1秒的时间(第二次测试时,注意力更集中了些,发现50000个请求才多花了0.16秒的时间就同步完成了,这还要考虑我点下秒表停止按钮的时间,以及 watch - n 'echo "show table" | socat unix:/var/run/haproxy.sock -' 的时候每秒刷新一次的时间差)。

不难看出,stick table的复制效率非常高。事实上,即使在非常繁忙的环境下,多个节点间进行复制,同步完成也不会有多少延迟。可以想象下,haproxy检索、插入一个stickiness并推送(n个节点*50字节)的数据出去,比haproxy处理并转发一个请求给后端要轻松的多的多的多。特别是单独使用一个网卡负责推送stick table时,效率更高。

有了stick table的复制功能,在没有session共享的情况下,为架构设计多提供了几种方案。至于如何选择,需要考虑与其配合的软件特性。特别适用的一种情况是,在haproxy前端使用LVS代理时,由于LVS只能4层负载,很可能将同一客户端调度到不同的haproxy节点上,如果使用stick table复制,就不用担心haproxy不能将客户端引导到同一后端服务器上的问题。另一种使用stick table复制的场景是结合keepalived实现双主模型代理不同服务时,为了会话保持也高可用,可以使用stick table的复制保证粘性记录不丢失。