HTTP请求在设计上是无状态的。然而 , 这引起了一些问题 , 即如何跟踪用户的活动 , 包括恶意的活动 , 跨越请求 , 以便你可以收集指标 , 阻止用户 , 并根据状态做出其他决定。追踪用户在一个请求和下一个请求之间的活动的唯一方法是添加一个机制来存储事件 , 并通过客户端IP或其他关键对其进行分类。

Stick table在很大程度上依赖于HAProxy的ACL(访问控制列表) , stick table有很多的用途 , 但在这里我们将强调三个方面:服务器持久性、机器检测和收集指标。

stick table是haproxy的一个非常优秀的特性 , 这个表里面存储的是stickiness记录 , stickiness记录了客户端和服务端1:1对应的引用关系。通过这个关系 , haproxy可以将客户端的请求引导到之前为它服务过的后端服务器上 , 也就是实现了会话保持的功能。这种记录方式 , 俗称会话粘性(stickiness) , 即将客户端和服务端粘连起来。

对于一些应用程序来说 , 基于cookie或一致的基于散列的持久化方法由于这样或那样的原因并不适用于服务器持久化。通过stick table, 你可以让HAProxy存储一段信息 , 如IP地址、cookie或请求体中的字节范围(例如 , 非HTTP协议中的用户名或会话ID) ,并将其与服务器联系起来。然后 ,当HAProxy看到使用同一信息的新连接时 ,它将把请求转发给同一服务器。如果你在服务器上的内存中存储应用会话 , 这就非常有用。

除了传统的服务器持久性的使用案例 , 你还可以使用stick table来防御某些类型的攻击威胁。

第三个方面是使用stick table来收集指标。有时 , 你想了解HAProxy中正在发生的事情 ,但不需要启用日志 , 也不需要解析日志来获得相关的信息。这时 , Runtime API的力量就发挥出来了。使用API , 你可以从命令行、自定义脚本或可执行程序中读取和分析stick table数据。这就为在你选择的仪表盘中实现数据的可视化打开了大门。

HAProxy为您提供了一种快速的内存存储 , 称为stick table , 可以让您跟踪用户的活动 , 包括恶意的活动 , 跨请求。

stick table收集并存储流经HAProxy负载平衡器的请求的数据。把它想象成一台机器 , 在汽车进入赛车场时对它们进行颜色编码。第一步是设置stick table应该被允许使用的存储量 , 数据应该被保存多长时间 ,以及你想观察什么数据。这是通过frontend或backend中的stick-table指令完成的。

每个frontend或backend部分只能有一个stick table定义在其中。这样做的坏处是 , 无法与其他frontend和backend共享该存储空间。你可以定义一个frontend或backend , 其唯一的目的是保存一个stick table。然后你可以在其他地方使用这个table参数。 然后 , 您可以在其他地方使用stick table参数。

backend st_src_global
    stick-table type ip size 1m expire 10s store http_req_rate(10s)
​
frontend fe_main
    bind *:80
    http-request track-sc0 src table st_src_global

stick table中使用key/value的方式映射客户端和后端服务器 , key是客户端的标识符 , 可以使用客户端的源ip(50字节)、cookie以及从报文中过滤出来的部分String。value部分是服务端的标识符。

stick table实现会话粘性的过程如下图:

img

除了存储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记录就能完全保持同步。

下面测试中环境说明:

img

1. stick table 使用

使用stick-table的一般思路如下:

  • 首先声明一个stick-table ,用于存储特定类型的请求(或连接)的相关数据 ;

  • 使用stick store-requset 、stick store-response 、tcp-request content track-sc0 、tcp-request connection track-sc0等语句将数据送入之前定义的stick-table中;

  • 在需要取出数据的地方 , 用之前存储的key(例如源ip , cookie串等) , 去stick-table中去取出key对应的数据 , 利用这些数据做acl判断来决定如何处理后续的用户请求。

注意

默认情况下 , stick-table的声明本身只是创建一个sitck-table , 并不做任何其他事情。向stick-table中存储数据 , 需要其他语句。与之类似的是acl , acl声明语句并不会做任何真正的"action/check" , 声明语句的作用仅仅是将"acl名字"与"action/check" 动作关联起来而已。“action/check"真正被执行是在该acl在其他部分被真正调用的时候。 haproxy中的acl通常是作为某种判断条件 ,但却不只限于此 , 有时声明一个acl只是为了完成某个动作 ,例如GPC计数器的自增操作等 , 就完全脱离了acl原始概念 , 这种我们可以认为它是"伪acl”。

backend per-ip
stick-table type ip size 50k expire 120m store gpc0,http_req_rate(120s)
​
frontend
tcp-request connection track-sc1 src table per-ip
tcp-request connection reject if { sc1_get_gpc0 gt 0 }
…
use_backend foo…
​
backend foo
tcp-request content track-sc2 src table per-ip if METH_POST
acl bruteforce_detection sc2_http_req_rate gt 5
acl block sc1_inc_gpc0 gt 0
http-request deny if bruteforce_detection block

这个案例解释了"dummy proxy"如何定义 , 同时也侧面说明了sc0,sc1,sc2的作用。 a. 首先定义了一个dummy proxy 名字叫per-ip b. frontend追踪用户源ip(sc1) c. backend同时追踪这个源ip的所有方法是POST的请求(sc2) , 并且根据判断情况标记用户是否是攻击用户(acl block sc1_inc_gpc0 gt 0) d. fontend部分做检查 , 如果sc1的gpc0技术器大于0 , 则拒绝这个ip之后的请求。

示例1

#定义一个ip类型作为key的stick-table , 用于存储这个ip相关的计数器
stick-table type ip size 500k expire 30s store conn_cur,conn_rate(10s),http_req_rate(10s),http_err_rate(10s)
​
#打开源ip追踪 , 用以向stick-table写入数据
tcp-request content track-sc0 src
​
#  echo “show table fxa-https” | socat unix:./haproxy/stats -
table: fxa-https, type: ip, size:512000, used:1
0x1aa3358: key=1.10.2.10 use=1 exp=29957 conn_rate(10000)=43 conn_cur=1 http_req_rate(10000)=42 http_err_rate(10000)=42

示例2

#从request或response中抽取SSL session ID , 用这个ID将requset与backend"粘住" 
#SSL session ID可能由client提供 , 也可能由server提供
backend https
backend https
mode tcp
balance roundrobin
​
#最大SSL session ID长度是32 bytes.
stick-table type binary len 32 size 30k expire 30m
​
#定义两个acl , 分别匹配ssl client hello和ssl server hello  , 在这个位置 , ACL并没有被执行 , 仅仅是将ACL名字与判断条件关联起来而已。
acl clienthello req.ssl_hello_type 1
acl serverhello res.ssl_hello_type 2
​
#使用tcp content accept来检测ssl client hello和ssl server hello.
#因为client ssl hello包含在request中 , 因此必须等待5秒 , 确保tcp连接的数据都进入了buffer
tcp-request inspect-delay 5s
tcp-request content accept if clienthello # 这里 , acl的check操作才真正执行
​
#ssl server hello 由服务器端发出 , 所以不需要延迟 , 直接判断
tcp-response content accept if serverhello
​
#SSL session ID 的长度为1 byte , 位置在tcp payload的 offset 43之后。
#client hello在请求中 , server hello在response中
#取出payload_lv(43,1) , 作为key存入stick-table
stick store-request payload_lv(43,1) if clienthello
stick store-response payload_lv(43,1) if serverhello
​
#用payload_lv(43,1)做key去stick-table中查找
stick match payload_lv(43,1)
server s1 192.168.1.1:443
server s2 192.168.1.2:443

1.1 创建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值 , 也可以是一段从请求报文或响应报文中匹配出来的字符串。

      Type

      Size (b)

      Description

      ip

      50

      这将存储一个IPv4地址。它主要用于跟踪发出请求的IP的活动 , 可以由HAProxy用src方式提供。然而 , 它也可以被送入一个样本 , 如req.hdr(x-forwarded-for) ,以从另一个代理那里获得IP。

      ipv6

      60

      这将存储一个IPv6地址或一个IPv6映射的IPv4地址。否则它与ip类型相同。

      integer

      32

      这通常用于存储来自cookie、头文件等的客户ID号码。它也可以用来存储一些东西 , 如通过fe_id或int(1)来跟踪一个条目下的所有东西(原因我们将在后面的章节中介绍)

      string

      len

      这将存储一个字符串 , 通常用于会话ID、API密钥等类似内容。当创建一个假头来存储自定义的样本组合时 , 它也很有用。它需要一个len参数 , 后面是可以存储的字节数。较大的样本将被截断。

      binary

      len

      这是用来存储二进制样本的。它最常用于持久化 , 通过字节转换器从TCP流中提取一个客户端ID。它也可以用来存储其他样本 , 如base32(IP+URL)的获取。它需要一个len参数 , 后面是可以存储的字节数。较长的样本将被截断

      你选择的类型定义了表内的键。例如 , 如果你使用ip类型 ,那么我们将捕获IP地址作为键。

    • size:表中允许的最大stickiness记录数量。单位使用k、m和g表示 , 分别表示1024、220和230条记录。

    • 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内存空间。key的大小和它所存储的计数器加起来就是总空间了。

你用stick table来建立一个DDoS防御系统。一个很好的用例 ,但是当攻击者带来足够的IP时 , 会发生什么?它是否会导致足够多的条目被添加 , 从而使你服务器上的所有内存都被消耗掉?stick table的内存在需要时才会被使用 , 但即使如此 , 你也应该记住它可能增长的大小 ,并通过size参数对条目的数量设置一个上限。

1.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

如果要监控某些状态信息的实时变化 , 可以使用watch命令。

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

haproxy 支持以下列出的所有操作命令

[root@xuexi ~]# echo "help" | socat unix:/var/run/haproxy.sock -  
​

其中和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

1.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

1.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每次请求都相当于一次新请求。

img

在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

1.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

1.6 stick on 、stick match 、stick store

语法如下:

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>]
​
#命令中使用得<pattern>,可以在使用5.9.1.1章中的获取方式。

其中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

1.7 使用stick table 统计状态信息

在store 关键字之后 , 是一个以逗号分隔的列表 , 列出了应该与给定的键相关的值。虽然有些类型可以使用ACL或通过Runtime API来设置 , 但大多数是由HAProxy的内置检索自动计算的 , 如http_req_rate。在一个给定的键上 , 可以有你想要的任意数量的值存储。

stick table除了存储基本的粘性信息 , 还能存储额外的统计数据 , 这其实是haproxy提供的一种"采样调查"功能。它能采集的数据种类有以下几种:

img

每个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)

1.7.1 http_req_rate

这可能是stick table中最经常存储/使用的值。正如它的名字所暗示的那样 , 它存储了被跟踪的键(如源IP地址)在指定时间段内发出的HTTP请求的数量 ,不管它们是否被接受。使用这个可以像下面一样简单。

stick-table type ip size 1m expire 10s store http_req_rate(10s)
tcp-request inspect-delay 10s
tcp-request content track-sc0 src
http-request deny if { sc_http_req_rate(0) gt 10 }

第一行定义了一个stick table , 用于跟踪IP地址和它们在过去10秒内的HTTP请求率。这是通过存储http_req_rate值来实现的 , 它接受周期作为参数。请注意 , 我们已经设置了expire参数 , 以匹配10秒的周期。 第二行是在表中插入或更新一个key并更新其计数器。使用粘性计数器sc0 , 它使用src 方法将密钥设置为源IP。你可能想知道什么时候使用tcp-request content track-sc0而不是http-request track-sc0。这主要是一个偏好问题 , 但由于TCP阶段发生在HTTP阶段之前 , 你应该尽量把tcp-*指令排在http-*指令之前 ,否则当HAProxy启动时你会收到警告。另外 ,如果你想在较早的TCP阶段拒绝连接 , 就应该倾向于使用tcp-request的变体。然而 , 如果你要捕获HTTP头、cookies或其他封装在HTTP消息中的数据 , 那么要使用tcp-request内容track-s0 , 你必须使用inspect-delay指令。我们稍后会讨论这个问题。

最后 , 如果客户端在10秒的有效期内发出了10个以上的请求 , 第三行将以403 Forbidden拒绝请求。注意 , 在决定是否拒绝请求时 , 我们用sc_http_req_rate函数检查http_req_rate的值 , 把0传给它 , 这个数字对应于我们的粘性计数器sc0。

像http_req_rate这样返回速率的值 , 都有一个参数 ,即它们所覆盖的时间范围。可以跟踪的最大时间是30天左右(例如30d)。对于更长的时间段 , 可以考虑使用计数器http_req_cnt ,然后从中推断。

1.7.2 conn_cur and conn_rate

两个密切相关的计数器 ,conn_cur和conn_rate ,跟踪一个给定的密钥已经或正在进行的连接数。当tcp-request content track-sc0 src行被处理时 , conn_cur计数器会自动增加或减少 ,以反映该键或源IP的当前开放连接数。conn_rate计数器与此类似 , 但它给定了一个时间段 ,并计算出该时间段内新连接的平均速率。

stick-table type ip size 1m expire 10s store conn_cur
tcp-request content track-sc0 src
tcp-request content reject if { sc_conn_cur(0) gt 10 }

使用这个的一个方法是检测一个客户已经打开了太多的连接 , 所以你可以拒绝他们的任何更多连接。在这种情况下 , 如果源IP打开的连接数超过10个 , 那么该连接将被拒绝并关闭。

这些计数器主要用于保护那些涉及大量源自同一IP地址的新连接的攻击。在下一节 , 你将看到HTTP计数器 , 它在保护HTTP请求泛滥方面更为有效。HTTP计数器跟踪请求 , 与是否使用HTTP keep-alive或复用无关。然而在新连接泛滥的情况下 , 这些计数器可以最好地阻止它们。

1.7.3 http_err_rate

这是跟踪以错误代码(4xx)响应结束的HTTP请求的比率。 这有一些有用的应用。

  • 你可以检测漏洞扫描器 , 这些扫描器往往会得到很多像404 Not Found这样的错误页面

  • 你可以通过使用URL路径作为棍子表键来检测丢失的页面。比如说:

    stick-table type string len 128 size 2k expire 1d store http_err_rate(1d)
    tcp-request content track-sc0 path

    这将制作一个可由运行时API检索的表格 , 显示各种路径的错误率。

    # table: fe_main, type: string, size:2048, used:2
    0xbc929c: key=/ use=0 exp=86387441 http_err_rate(86400000)=0
    0xbc99ac: key=/foobar use=0 exp=86390564 http_err_rate(86400000)=1
  • 你可以检测登录暴力攻击或扫描器。如果你的登录页面在登录失败时产生一个HTTP错误代码 ,那么这可以被用来检测暴力攻击。对于这一点 , 你将在src上进行追踪 , 而不是像前面的例子那样在path上进行追踪。

1.7.4 bytes_out_rate

bytes_out_rate计数器测量从你的服务器为一个给定的键(如path)发送的流量的速率。它的主要用途是识别那些产生最大流量的内容或用户。然而 , 它也有其他有趣的用途。它可以帮助按站点或路径测量流量 , 你可以在容量规划中使用 , 或查看哪些资源可能需要转移到他们自己的集群中(例如 , 如果你经营一个CDN , 这可以用来选择流量大的内容转移到其他缓存节点) 。

我们还可以将bytes_out_rate作为另一个数据集 , 输入到异常检测系统中(例如 , 一个从未发送过大量流量的网络脚本突然开始发送3GB的流量 , 这可能表明数据被成功渗出)。

与bytes_out_rate类似 , bytes_in_rate观察一个客户发送多少流量 , 这可以用来检测异常行为, 并作为VPN系统计费的因素, 其中客户流量将被双向计算 , 以及此类事情。

1.7.5 gpc0/gpc1

通用计数器(gpc0和gpc1) 与gpt0(通用标签)一样 , 都是特殊的, 因为在创建时默认为0 ,并且不会自动更新。ACL可以通过sc_inc_gpc0的获取方法来增加这个计数器 , 这样你就可以用它来跟踪自定义的统计数据。

如果你跟踪gpc0_rate , 它将自动给你一个gpc0被增加的速度的视图。这可以告诉你这个事件发生的频率。

2. stick table 复制

2.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.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命令手动管理服务的启动和停止 , 否则可能会出现记录不同步的现象。至今没找到会出现这个问题的原因。

2.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完全相同。

3. 跟踪数据

现在你已经定义了一个stick table , 下一步是跟踪其中的东西。这可以通过使用http-request track-sc0、tcp-request connection track-sc0或tcp-request content track-sc0来完成。首先要考虑的是使用一个粘性计数器sc0。这是用来指定一个槽 , 用来跟踪连接或请求。你可以用0替换的最大数字是由构建时的变量MAX_SESS_STKCTR设置的。在HAProxy Enterprise中 , 它被设置为12 , 允许sc0到sc11。

backend st_src_global
    stick-table type ip size 1m expire 10m store http_req_rate(10m)
​
backend st_src_login
    stick-table type ip size 1m expire 10m store http_req_rate(10m)
​
backend st_src_api
    stick-table type ip size 1m expire 10m store http_req_rate(10m)
​
frontend fe_main
    bind *:80
    http-request track-sc0 src table st_src_global
    http-request track-sc1 src table st_src_login if { path_beg /login }
    http-request track-sc1 src table st_src_api if { path_beg /api }

在这个例子中 , http-request track-sc0这一行没有if语句来过滤掉任何路径 , 所以sc0在追踪所有的流量。用Runtime API查询st_src_global stick表将显示每个客户端IP的HTTP请求率。这很容易。

Sticky counter 1(sc1)被使用了两次 : 一次是追踪以/login开始的请求, 另一次是追踪以/api开始的请求。这是好的 , 因为没有请求通过这个区块会同时以/login和/api开始 , 所以一个粘性计数器可以用于两个表。

尽管这两个表都被sc1跟踪 , 但它们是自己的stick table定义 , 因此将它们的数据分开。如果你提出一些请求 , 然后通过Runtime API查询这些表 , 你会看到如下结果。

$ echo "show table st_src_global" | socat stdio UNIX-CONNECT:/var/run/hapee-1.8/hapee-lb.sock
# table: st_src_global, type: ip, size:1048576, used:1
0x18f907c: key=127.0.0.1 use=0 exp=3583771 http_req_rate(86400000)=3
​
$ echo "show table st_src_api" | socat stdio UNIX-CONNECT:/var/run/hapee-1.8/hapee-lb.sock
# table: st_src_api, type: ip, size:1048576, used:1
0x18f919c: key=127.0.0.1 use=0 exp=3572396 http_req_rate(86400000)=2
​
$ echo "show table st_src_login" | socat stdio UNIX-CONNECT:/var/run/hapee-1.8/hapee-lb.sock
# table: st_src_login, type: ip, size:1048576, used:1
0x18f989c: key=127.0.0.1 use=0 exp=3563780 http_req_rate(86400000)=1

你可以看到st_src_global表中共有三个请求 , st_src_api表中有两个请求 , st_src_login表中有一个。尽管最后两个使用了相同的粘性计数器 ,但数据是分开的。如果我犯了一个错误 , 用sc0跟踪st_src_global和st_src_login , 那么我会发现st_src_login表是空的 , 因为当HAProxy去跟踪它时 , sc0已经用于这个连接了。

4. base use

现在你已经看到了如何创建stick table存储并跟踪数据 , 你会希望能够配置HAProxy , 使其根据捕获的信息采取行动。回到stick table的一个常见的用例 , 让我们看看如何使用数据将客户端持久化到一个特定的服务器。这是用stick on指令完成的 , 通常在后端部分找到 , 看起来像下面这样。

stick-table type string len 32 size 100k expire 30m
stick on req.cook(sessionid)

在这个例子中 , 注意到我们没有使用stick-table指令的存储参数。服务器ID是一个整数 , HAProxy用它来识别每个服务器 , 它是独一无二的 , 你不需要通过存储关键字来定义它 , 它就会被存储。如果需要进行持久化设置 , 服务器ID将自动存储在相关的stick表中。在stick on指令从cookie中提取客户的会话ID并将其作为键值存储在表中后 , 客户将继续被引导到同一个服务器。

在持久性的话题上 , 假设我们有一个参加主-主复制的MySQL服务器集群。我们担心如果在那一刻, 主服务器宕机 , 然后又恢复了 , 那么向其中一个服务器的写入可能会导致主键的重复。通常情况下 , 这可能会造成相当复杂的情况 , 即两个MySQL服务器都有一些查询 , 而另一个没有 , 这需要大量的工作来使它们恢复同步。

backend中mysql配置

backend mysql
    mode tcp
    stick-table type integer size 1 expire 1d
    stick on int(1)
    server primary 192.168.122.60:3306 check on-marked-down shutdown-sessions
    server backup 192.168.122.61:3306 check backup on-marked-down shutdown-sessions

有了这个配置 , 我们在stick table中只存储了一个条目 , 其中键是1 ,值是活动服务器的server_id。现在 ,如果主服务器发生故障 ,备份服务器的server_id将覆盖stick table中的值 ,即使主服务器重新上线 , 所有的请求也将继续流向备份服务器。当你准备让集群恢复正常运行时 , 可以通过将备份节点循环到维护模式 ,或通过运行时API来撤销这一做法。

注 : on-marked-down shutdown-sessions使HAproxy在后端宕机时关闭所有现有的连接。通常情况下 , HAProxy允许现有的连接完成 ,这可能会导致重复的主键(如果连接继续工作)或查询超时(如果它不工作)。

使用stick table的另一种方式是收集有关你的网站流量的信息 , 以便你可以根据它做出决定。例如 , 你想知道禁用TLS 1.1是否安全。你可以建立一个stick table , 跟踪人们正在使用的TLS版本。考虑一下下面的例子。

backend st_ssl_stats
    stick-table type string len 32 size 200 expire 24d store http_req_rate(24d)
​
frontend fe_main
    tcp-request inspect-delay 10s
    tcp-request content track-sc0 ssl_fc_protocol table st_ssl_stats

现在你可以查询服务器 , 看看使用了哪些TLS协议。

$ echo "show table st_ssl_stats" | socat stdio UNIX-CONNECT:/var/run/hapee-1.8/hapee-lb.sock
# table: st_ssl_stats, type: string, size:200, used:2
0xe4c62c: key=TLSv1 use=0 exp=2073596788 http_req_rate(2073600000)=1
0xe5a18c: key=TLSv1.2 use=0 exp=2073586582 http_req_rate(2073600000)=2

或者你可以把它转过来 ,按IP地址追踪使用过TLSv1.1的客户。

backend st_ssl_stats
    stick-table type ip size 200 expire 1h store http_req_rate(1d)
​
frontend fe_main
    tcp-request inspect-delay 10s
    tcp-request content track-sc0 src table st_ssl_stats if { ssl_fc_protocol TLSv1.1 }

例如 , 为了阻止那些在stick table定义的时间段内发出超过100个的请求 , 并通过在轨迹行中定义的键 , 你将以如下方式使用sc_http_req_rate。

http-request deny if { src,table_http_req_rate(st_src_global) gt 100 }

如果你已经在其他地方通过http-request track-sc0或tcp-request content track-sc0行来追踪密钥 , 那么这些取数对CPU来说是少量的额外工作。然而 ,这有几个很好的理由。

  • 你想检查一个请求是否应该被阻止 , 而不通过跟踪它来增加请求计数器(这样 , 一个客户可以在一秒钟内提出10个请求 , 而所有超过这个数量的请求都会被阻止 , 而不是在一秒钟内提出10个请求 , 让未来被阻止的请求继续被阻止 , 直到它们冷却下来)。

  • 你想传递另一个键;例如 , 传递req.arg(ip)而不是src将允许一个类似的API , 你可以请求http://192.168.122.64/is_blocked?ip=192.168.122.1 , 并查看该IP是否被阻止(或其请求率是多少)。

  • 你在使用Stick Table Aggregator , 并想从包含它所创建的表中查询数据(一个新的表被创建 , 包含了聚合的数据)。

5. 其他考虑因素

tcp-request inspect-delay 10s

你只需要在frontend或backend使用这个 , 当你在语句上有一个ACL , 会在比HAProxy通常拥有的信息更早的阶段处理。例如 , tcp-request content reject if { path_beg /foo }需要一个tcp-request inspect-delay,因为HAProxy不会在TCP阶段等待HTTP URL路径数据。相反, http-request deny if { path_beg /foo }不需要tcp-request inspect-delay行 , 因为HAProxy在有HTTP请求之前不会处理http-request规则。 当tcp-request inspect-delay出现时 , 它将保留请求 , 直到该块中的规则得到它们需要的数据来做出决定 , 或达到指定的延迟 , 以先到者为准。


参考链接

haproxy实现会话保持(2):stick table - 骏马金龙 - 博客园 (cnblogs.com)


熊熊