haproxy

基于haproxy实现的滑动计数示例

haproxy slide window

代码结构

(.venv) ryefccd@republic:~/workspace/brde$ tree -L 2 conf/haproxy/
conf/haproxy/
├── haproxy.cfg            # haproxy 配置文件
├── haproxy_demo.cfg       # haproxy slide window 配置文件
├── readme.md              # 自述文件
└── sw_openapi             # haproxy 导出的 restful 接口的 openapi 文档
    ├── swagger-ui         # openapi 接口结构页面渲染js和css依赖文件
    ├── sw_func.html       # openapi(swagger) 页面入口
    └── sw_func.json       # restful 接口的spec定义文件

2 directory, 5 files

部署说明

将 haproxy.cfg 文件 和 sw_openapi 文件夹复制到 /etc/haproxy 文件夹中.
根据实际情况修改 haproxy 的监听端口(bind :port).

frontend fe_api
    bind :88

    use_backend rate_10s   if { path /rate_10s }  #### \{ 与 path 之间要空格, uri 与 \} 之间也要保空格
    use_backend rate_1m    if { path /rate_1m  }
    use_backend rate_5m    if { path /rate_5m  }
    use_backend rate_1h    if { path /rate_1h  }
    use_backend rate_1d    if { path /rate_1d  }
    use_backend rate_7d    if { path /rate_7d  }

    use_backend group_distinct_1m  if { path /group_distinct_1m }
    use_backend group_distinct_5m  if { path /group_distinct_5m }
    use_backend group_distinct_1h  if { path /group_distinct_1h }
    use_backend group_distinct_1d  if { path /group_distinct_1d }
    use_backend group_distinct_7d  if { path /group_distinct_7d }
    ...

最后结构如下所示:

root@ub20:~# tree -L 2 /etc/haproxy/
/etc/haproxy/
├── errors
│   ├── 400.http
│   ├── 403.http
│   ├── 408.http
│   ├── 500.http
│   ├── 502.http
│   ├── 503.http
│   └── 504.http
├── haproxy.cfg
└── sw_openapi
    ├── sw_func.html
    ├── sw_func.json
    └── swagger-ui

最后执行 systemctl reload haproxy 即可重载服务.

测试步骤

测试脚本:

# 在最近10s的窗口移动步长, 30s窗口长度之内做 fccdabc 值进行频率计数 
curl "http://127.0.0.1/rate_10s?v=fccdabc"
# 在最近1m的窗口移动步长, 3m窗口长度之内做 fccdabc 值进行频率计数 
curl "http://127.0.0.1/rate_1m?v=fccdabc"
# 在最近1h的窗口移动步长, 3h窗口长度之内做 fccdabc 值进行频率计数 
curl "http://127.0.0.1/rate_1h?v=fccdabc"

# 对mykey进行查看, 查看当前的剩余过期时间ttl和上次计数修改时间
curl -i -v -XGET "http://127.0.0.1/?v=fccdabc"

以后可以在修改数据前使用 haproxy 的变量把ttl和上次计数修改时间记录下来. 最后和当前的最新计数返回. 这些信息有助于去记录数据的分布.

压测命令:

wrk -t12 -c400 -d30s --latency "http://10.84.71.214/rate_10s?v=fccdabc"

# 对照组
wrk -t12 -c400 -d30s --latency "http://10.84.71.214/?v=fccdabc"

调试 stick table:

echo "show table rate_10s" | socat unix:/run/haproxy/admin.sock -

每隔一秒刷新 stick table 中的数据.
watch -n 1 'echo "show table rate_10s" | socat unix:/run/haproxy/admin.sock -'

haproxy distinct count ratelimit

测试脚本:

curl "http://150.158.144.155:88/group_distinct_1m?group=deviced1&v=fccd1"
...

curl "http://150.158.144.155:88/group_distinct_1m?group=deviced1&v=fccd2"
...

curl "http://150.158.144.155:88/group_distinct_1m?group=deviced1&v=fccd3"
...

curl "http://150.158.144.155:88/group_distinct_1m?group=deviced1&v=fccd4"
...

curl "http://150.158.144.155:88/group_distinct_1m?group=deviced1&v=fccd5"
...
watch -n 1 'echo "show table group_distinct_1m" | sudo socat unix:/run/haproxy/admin.sock -'

Every 1.0s: echo "show table group_distinct_1m" | sudo socat unix:/run/haproxy/admin.sock -                                                                                                              VM-16-16-ubuntu: Mon May 20 23:21:03 2024

# table: group_distinct_1m, type: string, size:1048576, used:6
0x56518c58ae10: key=deviced1 use=0 exp=130356 shard=0 gpc0=31 gpc0_rate(60000)=17 gpc1=5 gpc1_rate(60000)=2
0x56518c53a5f0: key=deviced1:fccd1 use=0 exp=93409 shard=0 gpc0=1 gpc0_rate(60000)=1 gpc1=0 gpc1_rate(60000)=0
0x56518c537d50: key=deviced1:fccd2 use=0 exp=97194 shard=0 gpc0=1 gpc0_rate(60000)=1 gpc1=0 gpc1_rate(60000)=0
0x56518c537eb0: key=deviced1:fccd3 use=0 exp=107137 shard=0 gpc0=9 gpc0_rate(60000)=6 gpc1=0 gpc1_rate(60000)=0
0x56518c5f4770: key=deviced1:fccd4 use=0 exp=117528 shard=0 gpc0=4 gpc0_rate(60000)=2 gpc1=0 gpc1_rate(60000)=0
0x56518c5f4ab0: key=deviced1:fccd5 use=0 exp=130356 shard=0 gpc0=16 gpc0_rate(60000)=10 gpc1=0 gpc1_rate(60000)=0

压测命令:

wrk -t12 -c400 -d30s --latency "http://150.158.144.155:88/group_distinct_1m?group=deviced1&v=fccd1"

haproxy config

global
	log /dev/log	local0
	log /dev/log	local1 notice
	chroot /var/lib/haproxy
	stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
	stats timeout 30s
	user haproxy
	group haproxy
	daemon
	tune.bufsize 2097152  # 2MB

	# Default SSL material locations
	ca-base /etc/ssl/certs
	crt-base /etc/ssl/private

	# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
    ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

defaults
	log	global
	mode	http
	option	httplog
	option	dontlognull
	timeout connect 5000
	timeout client  50000
	timeout server  50000
	errorfile 400 /etc/haproxy/errors/400.http
	errorfile 403 /etc/haproxy/errors/403.http
	errorfile 408 /etc/haproxy/errors/408.http
	errorfile 500 /etc/haproxy/errors/500.http
	errorfile 502 /etc/haproxy/errors/502.http
	errorfile 503 /etc/haproxy/errors/503.http
	errorfile 504 /etc/haproxy/errors/504.http

frontend fe_api
    bind :88
    
    use_backend rate_10s   if { path /rate_10s }  #### \{ 与 path 之间要空格, uri 与 \} 之间也要保空格
    use_backend rate_1m    if { path /rate_1m  }
    use_backend rate_5m    if { path /rate_5m  }
    use_backend rate_1h    if { path /rate_1h  }
    use_backend rate_1d    if { path /rate_1d  }
    use_backend rate_7d    if { path /rate_7d  }

    use_backend rolling_sum10s if { path /rolling_sum10s }

    use_backend group_distinct_1m  if { path /group_distinct_1m }
    use_backend group_distinct_5m  if { path /group_distinct_5m }
    use_backend group_distinct_1h  if { path /group_distinct_1h }
    use_backend group_distinct_1d  if { path /group_distinct_1d }
    use_backend group_distinct_7d  if { path /group_distinct_7d }
    
    default_backend default_be

backend default_be
    http-request return status 200 content-type text/html              file /etc/haproxy/sw_openapi/sw_func.html if { path / }  # openapi entrypoint html
    http-request return status 200 content-type text/css               file /etc/haproxy/sw_openapi/swagger-ui/5.0.0/swagger-ui.min.css if { path_end swagger-ui.min.css }
    http-request return status 200 content-type application/javascript file /etc/haproxy/sw_openapi/swagger-ui/5.0.0/swagger-ui-bundle.min.js  if { path_end swagger-ui-bundle.min.js }
    http-request return status 200 content-type application/json       file /etc/haproxy/sw_openapi/sw_func.json if { path_end sw_func.json }
    http-request return status 200 content-type application/json lf-string '{"ip": "%[src]","port": %cp,"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]", "timestamp":"%[date]"}' hdr Access-Control-Allow-Origin "*" if { path /inspect  }


backend rate_10s
    stick-table type binary len 16 size 1g expire 10s store gpc0,gpc0_rate(10s),gpc1,gpc1_rate(10s)
    acl is_post method POST
    
    # 在 sc-inc-gpc0 之前获取 ttl(expire) 和 idle 信息.
    # http-request set-var(txn.mykey) url_param(mykey)

    http-request set-var(txn.v) url_param(mykey) unless { url_param(v) -m found } # distinct --> aggregate_key
    http-request set-var(txn.v) url_param(v) if { url_param(v) -m found }
    http-request set-var(txn._v) var(txn.v),digest(md5)
    http-request set-var(txn.vttl) var(txn._v),table_expire
    http-request set-var(txn.vttl) int(0) unless { var(txn.vttl) -m found }   ## 如果前面没有设置 txn.vttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.vidle) var(txn._v),table_idle
    http-request set-var(txn.vidle) int(0) unless { var(txn.vidle) -m found }  ## 如果前面没有设置 txn.vttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    # stick-table  type string  size 1g  expire 30s  store gpc0,gpc0_rate(10s),gpc1,gpc1_rate(10s)
    # 在 sc-inc-gpc0 之后, table key 的 ttl 和 idle 时间会重置.
    #  track-scX 和 sc-inc-gpc0(X) 里面的 X 是 sc0, sc1, sc2 中的一个. 
    http-request track-sc0 var(txn._v) if is_post  # track-sc0 会刷新 idle 和 ttl 的值.
    http-request sc-inc-gpc0(0) if is_post  # sc-inc-xxx 对键对应的值进行累加
    http-request set-var(txn.counter) var(txn._v),table_gpc0
    http-request set-var(txn.counter) int(0) unless { var(txn.counter) -m found }
    http-request set-var(txn.rate) var(txn._v),table_gpc0_rate
    http-request set-var(txn.rate) int(0) unless { var(txn.rate) -m found }
    http-request return status 200 content-type application/json lf-string '{"counter":%[var(txn.rate)],"v":"%[var(txn.v)]","idle":%[var(txn.vidle)],"ttl":%[var(txn.vttl)],"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]","timestamp":"%[date]","ip": "%[src]","port": %cp}' hdr Access-Control-Allow-Origin "*"


backend rate_1m
    stick-table type binary len 16 size 1g expire 1m store gpc0,gpc0_rate(1m),gpc1,gpc1_rate(1m)
    acl is_post method POST
    
    # 在 sc-inc-gpc0 之前获取 ttl(expire) 和 idle 信息.

    http-request set-var(txn.v) url_param(mykey) unless { url_param(v) -m found } # distinct --> aggregate_key
    http-request set-var(txn.v) url_param(v) if { url_param(v) -m found }
    http-request set-var(txn._v) var(txn.v),digest(md5)
    http-request set-var(txn.vttl) var(txn._v),table_expire
    http-request set-var(txn.vttl) int(0) unless { var(txn.vttl) -m found }   ## 如果前面没有设置 txn.vttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.vidle) var(txn._v),table_idle
    http-request set-var(txn.vidle) int(0) unless { var(txn.vidle) -m found }  ## 如果前面没有设置 txn.vttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    # stick-table  type string  size 1g  expire 30s  store gpc0,gpc0_rate(10s),gpc1,gpc1_rate(10s)
    # 在 sc-inc-gpc0 之后, table key 的 ttl 和 idle 时间会重置.
    #  track-scX 和 sc-inc-gpc0(X) 里面的 X 是 sc0, sc1, sc2 中的一个. 
    http-request track-sc0 var(txn._v) if is_post  # track-sc0 会刷新 idle 和 ttl 的值.
    http-request sc-inc-gpc0(0) if is_post  # sc-inc-xxx 对键对应的值进行累加
    http-request set-var(txn.counter) var(txn._v),table_gpc0
    http-request set-var(txn.counter) int(0) unless { var(txn.counter) -m found }
    http-request set-var(txn.rate) var(txn._v),table_gpc0_rate
    http-request set-var(txn.rate) int(0) unless { var(txn.rate) -m found }
    http-request return status 200 content-type application/json lf-string '{"counter":%[var(txn.rate)],"v":"%[var(txn.v)]","idle":%[var(txn.vidle)],"ttl":%[var(txn.vttl)],"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]","timestamp":"%[date]","ip": "%[src]","port": %cp}' hdr Access-Control-Allow-Origin "*"


backend rate_5m
    stick-table type binary len 16 size 1g expire 5m store gpc0,gpc0_rate(5m),gpc1,gpc1_rate(5m)
    acl is_post method POST
    
    # 在 sc-inc-gpc0 之前获取 ttl(expire) 和 idle 信息.

    http-request set-var(txn.v) url_param(mykey) unless { url_param(v) -m found } # distinct --> aggregate_key
    http-request set-var(txn.v) url_param(v) if { url_param(v) -m found }
    http-request set-var(txn._v) var(txn.v),digest(md5)
    http-request set-var(txn.vttl) var(txn._v),table_expire
    http-request set-var(txn.vttl) int(0) unless { var(txn.vttl) -m found }   ## 如果前面没有设置 txn.vttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.vidle) var(txn._v),table_idle
    http-request set-var(txn.vidle) int(0) unless { var(txn.vidle) -m found }  ## 如果前面没有设置 txn.vttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    # stick-table  type string  size 1g  expire 30s  store gpc0,gpc0_rate(10s),gpc1,gpc1_rate(10s)
    # 在 sc-inc-gpc0 之后, table key 的 ttl 和 idle 时间会重置.
    #  track-scX 和 sc-inc-gpc0(X) 里面的 X 是 sc0, sc1, sc2 中的一个. 
    http-request track-sc0 var(txn._v) if is_post  # track-sc0 会刷新 idle 和 ttl 的值.
    http-request sc-inc-gpc0(0) if is_post  # sc-inc-xxx 对键对应的值进行累加
    http-request set-var(txn.counter) var(txn._v),table_gpc0
    http-request set-var(txn.counter) int(0) unless { var(txn.counter) -m found }
    http-request set-var(txn.rate) var(txn._v),table_gpc0_rate
    http-request set-var(txn.rate) int(0) unless { var(txn.rate) -m found }
    http-request return status 200 content-type application/json lf-string '{"counter":%[var(txn.rate)],"v":"%[var(txn.v)]","idle":%[var(txn.vidle)],"ttl":%[var(txn.vttl)],"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]","timestamp":"%[date]","ip": "%[src]","port": %cp}' hdr Access-Control-Allow-Origin "*"



backend rate_1h
    stick-table type binary len 16 size 1g expire 1h store gpc0,gpc0_rate(1h),gpc1,gpc1_rate(1h)
    acl is_post method POST
    
    # 在 sc-inc-gpc0 之前获取 ttl(expire) 和 idle 信息.

    http-request set-var(txn.v) url_param(mykey) unless { url_param(v) -m found } # distinct --> aggregate_key
    http-request set-var(txn.v) url_param(v) if { url_param(v) -m found }
    http-request set-var(txn._v) var(txn.v),digest(md5)
    http-request set-var(txn.vttl) var(txn._v),table_expire
    http-request set-var(txn.vttl) int(0) unless { var(txn.vttl) -m found }   ## 如果前面没有设置 txn.vttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.vidle) var(txn._v),table_idle
    http-request set-var(txn.vidle) int(0) unless { var(txn.vidle) -m found }  ## 如果前面没有设置 txn.vttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    # stick-table  type string  size 1g  expire 30s  store gpc0,gpc0_rate(10s),gpc1,gpc1_rate(10s)
    # 在 sc-inc-gpc0 之后, table key 的 ttl 和 idle 时间会重置.
    #  track-scX 和 sc-inc-gpc0(X) 里面的 X 是 sc0, sc1, sc2 中的一个. 
    http-request track-sc0 var(txn._v) if is_post  # track-sc0 会刷新 idle 和 ttl 的值.
    http-request sc-inc-gpc0(0) if is_post  # sc-inc-xxx 对键对应的值进行累加
    http-request set-var(txn.counter) var(txn._v),table_gpc0
    http-request set-var(txn.counter) int(0) unless { var(txn.counter) -m found }
    http-request set-var(txn.rate) var(txn._v),table_gpc0_rate
    http-request set-var(txn.rate) int(0) unless { var(txn.rate) -m found }
    http-request return status 200 content-type application/json lf-string '{"counter":%[var(txn.rate)],"v":"%[var(txn.v)]","idle":%[var(txn.vidle)],"ttl":%[var(txn.vttl)],"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]","timestamp":"%[date]","ip": "%[src]","port": %cp}' hdr Access-Control-Allow-Origin "*"


backend rate_1d
    stick-table type binary len 16 size 1g expire 1d store gpc0,gpc0_rate(1d),gpc1,gpc1_rate(1d)
    acl is_post method POST
    
    # 在 sc-inc-gpc0 之前获取 ttl(expire) 和 idle 信息.

    http-request set-var(txn.v) url_param(mykey) unless { url_param(v) -m found } # distinct --> aggregate_key
    http-request set-var(txn.v) url_param(v) if { url_param(v) -m found }
    http-request set-var(txn._v) var(txn.v),digest(md5)
    http-request set-var(txn.vttl) var(txn._v),table_expire
    http-request set-var(txn.vttl) int(0) unless { var(txn.vttl) -m found }   ## 如果前面没有设置 txn.vttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.vidle) var(txn._v),table_idle
    http-request set-var(txn.vidle) int(0) unless { var(txn.vidle) -m found }  ## 如果前面没有设置 txn.vttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    # stick-table  type string  size 1g  expire 30s  store gpc0,gpc0_rate(10s),gpc1,gpc1_rate(10s)
    # 在 sc-inc-gpc0 之后, table key 的 ttl 和 idle 时间会重置.
    #  track-scX 和 sc-inc-gpc0(X) 里面的 X 是 sc0, sc1, sc2 中的一个. 
    http-request track-sc0 var(txn._v) if is_post  # track-sc0 会刷新 idle 和 ttl 的值.
    http-request sc-inc-gpc0(0) if is_post  # sc-inc-xxx 对键对应的值进行累加
    http-request set-var(txn.counter) var(txn._v),table_gpc0
    http-request set-var(txn.counter) int(0) unless { var(txn.counter) -m found }
    http-request set-var(txn.rate) var(txn._v),table_gpc0_rate
    http-request set-var(txn.rate) int(0) unless { var(txn.rate) -m found }
    http-request return status 200 content-type application/json lf-string '{"counter":%[var(txn.rate)],"v":"%[var(txn.v)]","idle":%[var(txn.vidle)],"ttl":%[var(txn.vttl)],"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]","timestamp":"%[date]","ip": "%[src]","port": %cp}' hdr Access-Control-Allow-Origin "*"


backend rate_7d
    stick-table type binary len 16 size 1g expire 7d store gpc0,gpc0_rate(7d),gpc1,gpc1_rate(7d)
    acl is_post method POST
    
    # 在 sc-inc-gpc0 之前获取 ttl(expire) 和 idle 信息.

    http-request set-var(txn.v) url_param(mykey) unless { url_param(v) -m found } # distinct --> aggregate_key
    http-request set-var(txn.v) url_param(v) if { url_param(v) -m found }
    http-request set-var(txn._v) var(txn.v),digest(md5)
    http-request set-var(txn.vttl) var(txn._v),table_expire
    http-request set-var(txn.vttl) int(0) unless { var(txn.vttl) -m found }   ## 如果前面没有设置 txn.vttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.vidle) var(txn._v),table_idle
    http-request set-var(txn.vidle) int(0) unless { var(txn.vidle) -m found }  ## 如果前面没有设置 txn.vttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    # stick-table  type string  size 1g  expire 30s  store gpc0,gpc0_rate(10s),gpc1,gpc1_rate(10s)
    # 在 sc-inc-gpc0 之后, table key 的 ttl 和 idle 时间会重置.
    #  track-scX 和 sc-inc-gpc0(X) 里面的 X 是 sc0, sc1, sc2 中的一个. 
    http-request track-sc0 var(txn._v) if is_post  # track-sc0 会刷新 idle 和 ttl 的值.
    http-request sc-inc-gpc0(0) if is_post  # sc-inc-xxx 对键对应的值进行累加
    http-request set-var(txn.counter) var(txn._v),table_gpc0
    http-request set-var(txn.counter) int(0) unless { var(txn.counter) -m found }
    http-request set-var(txn.rate) var(txn._v),table_gpc0_rate
    http-request set-var(txn.rate) int(0) unless { var(txn.rate) -m found }
    http-request return status 200 content-type application/json lf-string '{"counter":%[var(txn.rate)],"v":"%[var(txn.v)]","idle":%[var(txn.vidle)],"ttl":%[var(txn.vttl)],"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]","timestamp":"%[date]","ip": "%[src]","port": %cp}' hdr Access-Control-Allow-Origin "*"


backend rolling_sum10s
    stick-table type binary len 16 size 1g expire 20s store gpc(1),gpc_rate(1,20s)   # ,gpc1,gpc1_rate(10s)
    acl is_post method POST

    # 在 sc-inc-gpc0 之前获取 ttl(expire) 和 idle 信息.

    http-request set-var(txn.key) url_param(key)
    http-request set-var(txn.num) url_param(num)
    http-request set-var(txn._key) var(txn.key),digest(md5)
    http-request set-var(txn.keyttl) var(txn._key),table_expire
    http-request set-var(txn.keyttl) int(0) unless { var(txn.keyttl) -m found }   ## 如果前面没有设置 txn.keyttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.keyidle) var(txn._key),table_idle
    http-request set-var(txn.keyidle) int(0) unless { var(txn.keyidle) -m found }  ## 如果前面没有设置 txn.keyttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    # stick-table  type string  size 1g  expire 30s  store gpc0,gpc0_rate(10s),gpc1,gpc1_rate(10s)
    # 在 sc-inc-gpc0 之后, table key 的 ttl 和 idle 时间会重置.
    #  track-scX 和 sc-inc-gpc0(X) 里面的 X 是 sc0, sc1, sc2 中的一个. 
    http-request track-sc0 var(txn._key) if is_post  # track-sc0 会刷新 idle 和 ttl 的值.
    #http-request sc-inc-gpc0(0) if is_post  # sc-inc-xxx 对键对应的值进行累加
    http-request sc-add-gpc(0,0) var(txn.num)  if is_post  # var(txn.num)  # int(10)
    http-request set-var(txn.counter) var(txn._key),table_gpc(0,)    # table_gpc0
    http-request set-var(txn.counter) int(0) unless { var(txn.counter) -m found }
    http-request set-var(txn.rate) var(txn._key),table_gpc_rate(0,)
    http-request set-var(txn.rate) int(0) unless { var(txn.rate) -m found }
    http-request return status 200 content-type application/json lf-string '{"counter":%[var(txn.rate)],"key":"%[var(txn.key)]","ttl":%[var(txn.keyttl)],"idle":%[var(txn.keyidle)],"ip": "%[src]","port": %cp,"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]", "timestamp":"%[date]"}' hdr Access-Control-Allow-Origin "*"


backend group_distinct_1m
    stick-table type binary len 16 size 1g expire 1m store gpc0,gpc0_rate(1m),gpc1,gpc1_rate(1m)
    acl is_post method POST
    
    # 在 sc-inc-gpc0 之前获取 ttl(expire) 和 idle 信息.
    # 定义变量
    http-request set-var(txn.group) url_param(group)  # group   --> group 
    http-request set-var(txn.v) url_param(distinct) unless { url_param(v) -m found } # distinct --> aggregate_key
    http-request set-var(txn.v) url_param(v) if { url_param(v) -m found }  # distinct --> aggregate_key
    # http-request set-var-fmt(txn.combine_key) "%[var(txn.group)]%[var(txn.v)]"
    http-request set-var(txn.combine_key) var(txn.group),concat(':',txn.v,)  # 避免 v 为空值或者不传入分组待观测值时导致 combine_key 和 key 一样,进而重复计数.
    # str(1),digest(md5),hex  #  str(),concat(<ip=,sess.ip,>),concat(<dn=,sess.dn,>)
    http-request set-var(txn._combine_key) var(txn.combine_key),digest(md5)
    http-request set-var(txn._group) var(txn.group),digest(md5)
    http-request set-var(txn._v) var(txn.v),digest(md5)

    http-request set-var(txn.gttl)  var(txn._group),table_expire
    http-request set-var(txn.gttl)  int(0) unless { var(txn.gttl) -m found }   ## 如果前面没有设置 txn.gttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.gidle) var(txn._group),table_idle
    http-request set-var(txn.gidle) int(0) unless { var(txn.gidle) -m found }  ## 如果前面没有设置 txn.gttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.vidle) var(txn._combine_key),table_idle
    http-request set-var(txn.vidle) int(0) unless { var(txn.vidle) -m found }
    # 在 sc-inc-gpc0 之后, table key 的 ttl 和 idle 时间会重置.
    #  track-scX 和 sc-inc-gpc0(X) 里面的 X 是 sc0, sc1, sc2 中的一个. 

    http-request track-sc0 var(txn._group)  if is_post  # track-scx 会刷新 idle 和 ttl 的值.
    http-request track-sc1 var(txn._combine_key)  if is_post  # track-scx 会刷新 idle 和 ttl 的值.
    # 先取出 table 中 txn.combine_key 的速率, 如果最近窗口速率等于0说明txn.combine_key是第一次出现. 然后再去完成 txn.combine_key 值的累计.
    http-request sc-inc-gpc1(0) if is_post { var(txn._combine_key),table_gpc0_rate eq 0 }  # 对 group:aggregate_key 这个复合键如果是第一次出现, 那么在gpc1中对group的进行自增(UV的概念).
    http-request sc-inc-gpc0(0) if is_post ## 在 gpc0 中对 group 的进行累计(PV的概念)
    http-request sc-inc-gpc0(1) if is_post ## 对 track-sc1 中的 group:aggregate_key 在 gpc0 中进行计数(PV) 

    http-request set-var(txn.gpv)  var(txn._group),table_gpc0_rate
    http-request set-var(txn.uv)  var(txn._group),table_gpc1_rate
    http-request set-var(txn.pv) var(txn._combine_key),table_gpc0_rate

    http-request return status 200 content-type application/json lf-string '{"gpv":%[var(txn.gpv)],"pv":%[var(txn.pv)],"uv":%[var(txn.uv)],"gidle":%[var(txn.gidle)],"vidle":%[var(txn.vidle)],"group":"%[var(txn.group)]","v":"%[var(txn.v)]","ttl":%[var(txn.gttl)],"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]","timestamp":"%[date]","ip": "%[src]","port": %cp}' hdr Access-Control-Allow-Origin "*"


backend group_distinct_5m
    stick-table type binary len 16 size 1g expire 5m store gpc0,gpc0_rate(5m),gpc1,gpc1_rate(5m)   
    acl is_post method POST
    
    # 在 sc-inc-gpc0 之前获取 ttl(expire) 和 idle 信息.
    # 定义变量
    http-request set-var(txn.group) url_param(group)  # group   --> group 
    http-request set-var(txn.v) url_param(distinct) unless { url_param(v) -m found } # distinct --> aggregate_key
    http-request set-var(txn.v) url_param(v) if { url_param(v) -m found }  # distinct --> aggregate_key
    # http-request set-var-fmt(txn.combine_key) "%[var(txn.group)]%[var(txn.v)]"
    http-request set-var(txn.combine_key) var(txn.group),concat(':',txn.v,)  # 避免 v 为空值或者不传入分组待观测值时导致 combine_key 和 key 一样,进而重复计数.
    # str(1),digest(md5),hex  #  str(),concat(<ip=,sess.ip,>),concat(<dn=,sess.dn,>)
    http-request set-var(txn._combine_key) var(txn.combine_key),digest(md5)
    http-request set-var(txn._group) var(txn.group),digest(md5)
    http-request set-var(txn._v) var(txn.v),digest(md5)

    http-request set-var(txn.gttl)  var(txn._group),table_expire
    http-request set-var(txn.gttl)  int(0) unless { var(txn.gttl) -m found }   ## 如果前面没有设置 txn.gttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.gidle) var(txn._group),table_idle
    http-request set-var(txn.gidle) int(0) unless { var(txn.gidle) -m found }  ## 如果前面没有设置 txn.gttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.vidle) var(txn._combine_key),table_idle
    http-request set-var(txn.vidle) int(0) unless { var(txn.vidle) -m found }
    # 在 sc-inc-gpc0 之后, table key 的 ttl 和 idle 时间会重置.
    #  track-scX 和 sc-inc-gpc0(X) 里面的 X 是 sc0, sc1, sc2 中的一个. 

    http-request track-sc0 var(txn._group)  if is_post  # track-scx 会刷新 idle 和 ttl 的值.
    http-request track-sc1 var(txn._combine_key)  if is_post  # track-scx 会刷新 idle 和 ttl 的值.
    # 先取出 table 中 txn.combine_key 的速率, 如果最近窗口速率等于0说明txn.combine_key是第一次出现. 然后再去完成 txn.combine_key 值的累计.
    http-request sc-inc-gpc1(0) if is_post { var(txn._combine_key),table_gpc0_rate eq 0 }  # 对 group:aggregate_key 这个复合键如果是第一次出现, 那么在gpc1中对group的进行自增(UV的概念).
    http-request sc-inc-gpc0(0) if is_post ## 在 gpc0 中对 group 的进行累计(PV的概念)
    http-request sc-inc-gpc0(1) if is_post ## 对 track-sc1 中的 group:aggregate_key 在 gpc0 中进行计数(PV) 

    http-request set-var(txn.gpv)  var(txn._group),table_gpc0_rate
    http-request set-var(txn.uv)  var(txn._group),table_gpc1_rate
    http-request set-var(txn.pv) var(txn._combine_key),table_gpc0_rate

    http-request return status 200 content-type application/json lf-string '{"gpv":%[var(txn.gpv)],"pv":%[var(txn.pv)],"uv":%[var(txn.uv)],"gidle":%[var(txn.gidle)],"vidle":%[var(txn.vidle)],"group":"%[var(txn.group)]","v":"%[var(txn.v)]","ttl":%[var(txn.gttl)],"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]","timestamp":"%[date]","ip": "%[src]","port": %cp}' hdr Access-Control-Allow-Origin "*"


backend group_distinct_1h
    stick-table type binary len 16 size 1g expire 1h store gpc0,gpc0_rate(1h),gpc1,gpc1_rate(1h)   
    acl is_post method POST
    
    # 在 sc-inc-gpc0 之前获取 ttl(expire) 和 idle 信息.
    # 定义变量
    http-request set-var(txn.group) url_param(group)  # group   --> group 
    # http-request set-var(txn.gttl)  str()
    http-request set-var(txn.v) url_param(distinct) unless { url_param(v) -m found } # distinct --> aggregate_key
    http-request set-var(txn.v) url_param(v) if { url_param(v) -m found }
    # http-request set-var-fmt(txn.combine_key) "%[var(txn.group)]%[var(txn.v)]"
    http-request set-var(txn.combine_key) var(txn.group),concat(':',txn.v,)  # 避免 v 为空值或者不传入分组待观测值时导致 combine_key 和 key 一样,进而重复计数.  # 避免 v 为空值或者不传入分组待观测值时导致 combine_key 和 key 一样,进而重复计数.
    # str(1),digest(md5),hex  #  str(),concat(<ip=,sess.ip,>),concat(<dn=,sess.dn,>)
    http-request set-var(txn._combine_key) var(txn.combine_key),digest(md5)
    http-request set-var(txn._group) var(txn.group),digest(md5)
    http-request set-var(txn._v) var(txn.v),digest(md5)

    http-request set-var(txn.gttl)  var(txn._group),table_expire
    http-request set-var(txn.gttl)  int(0) unless { var(txn.gttl) -m found }   ## 如果前面没有设置 txn.gttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.gidle) var(txn._group),table_idle
    http-request set-var(txn.gidle) int(0) unless { var(txn.gidle) -m found }  ## 如果前面没有设置 txn.gttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.vidle) var(txn._combine_key),table_idle
    http-request set-var(txn.vidle) int(0) unless { var(txn.vidle) -m found }
    # 在 sc-inc-gpc0 之后, table key 的 ttl 和 idle 时间会重置.
    #  track-scX 和 sc-inc-gpc0(X) 里面的 X 是 sc0, sc1, sc2 中的一个. 

    http-request track-sc0 var(txn._group)  if is_post  # track-scx 会刷新 idle 和 ttl 的值.
    http-request track-sc1 var(txn._combine_key)  if is_post  # track-scx 会刷新 idle 和 ttl 的值.
    # 先取出 table 中 txn.combine_key 的速率, 如果最近窗口速率等于0说明txn.combine_key是第一次出现. 然后再去完成 txn.combine_key 值的累计.
    http-request sc-inc-gpc1(0) if is_post { var(txn._combine_key),table_gpc0_rate eq 0 }  # 对 group:aggregate_key 这个复合键如果是第一次出现, 那么在gpc1中对group的进行自增(UV的概念).
    http-request sc-inc-gpc0(0) if is_post ## 在 gpc0 中对 group 的进行累计(PV的概念)
    http-request sc-inc-gpc0(1) if is_post ## 对 track-sc1 中的 group:aggregate_key 在 gpc0 中进行计数(PV) 

    http-request set-var(txn.gpv)  var(txn._group),table_gpc0_rate
    http-request set-var(txn.uv)  var(txn._group),table_gpc1_rate
    http-request set-var(txn.pv) var(txn._combine_key),table_gpc0_rate

    http-request return status 200 content-type application/json lf-string '{"gpv":%[var(txn.gpv)],"pv":%[var(txn.pv)],"uv":%[var(txn.uv)],"gidle":%[var(txn.gidle)],"vidle":%[var(txn.vidle)],"group":"%[var(txn.group)]","v":"%[var(txn.v)]","ttl":%[var(txn.gttl)],"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]","timestamp":"%[date]","ip": "%[src]","port": %cp}' hdr Access-Control-Allow-Origin "*"


backend group_distinct_1d
    stick-table type binary len 16 size 1g expire 1d store gpc0,gpc0_rate(1d),gpc1,gpc1_rate(1d)
    acl is_post method POST
    
    # 在 sc-inc-gpc0 之前获取 ttl(expire) 和 idle 信息.
    # 定义变量
    http-request set-var(txn.group) url_param(group)  # group   --> group 
    http-request set-var(txn.v) url_param(distinct) unless { url_param(v) -m found } # distinct --> aggregate_key
    http-request set-var(txn.v) url_param(v) if { url_param(v) -m found }  # distinct --> aggregate_key
    # http-request set-var-fmt(txn.combine_key) "%[var(txn.group)]%[var(txn.v)]"
    http-request set-var(txn.combine_key) var(txn.group),concat(':',txn.v,)  # 避免 v 为空值或者不传入分组待观测值时导致 combine_key 和 key 一样,进而重复计数.
    # str(1),digest(md5),hex  #  str(),concat(<ip=,sess.ip,>),concat(<dn=,sess.dn,>)
    http-request set-var(txn._combine_key) var(txn.combine_key),digest(md5)
    http-request set-var(txn._group) var(txn.group),digest(md5)
    http-request set-var(txn._v) var(txn.v),digest(md5)

    http-request set-var(txn.gttl)  var(txn._group),table_expire
    http-request set-var(txn.gttl)  int(0) unless { var(txn.gttl) -m found }   ## 如果前面没有设置 txn.gttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.gidle) var(txn._group),table_idle
    http-request set-var(txn.gidle) int(0) unless { var(txn.gidle) -m found }  ## 如果前面没有设置 txn.gttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.vidle) var(txn._combine_key),table_idle
    http-request set-var(txn.vidle) int(0) unless { var(txn.vidle) -m found }
    # 在 sc-inc-gpc0 之后, table key 的 ttl 和 idle 时间会重置.
    #  track-scX 和 sc-inc-gpc0(X) 里面的 X 是 sc0, sc1, sc2 中的一个. 

    http-request track-sc0 var(txn._group)  if is_post  # track-scx 会刷新 idle 和 ttl 的值.
    http-request track-sc1 var(txn._combine_key)  if is_post  # track-scx 会刷新 idle 和 ttl 的值.
    # 先取出 table 中 txn.combine_key 的速率, 如果最近窗口速率等于0说明txn.combine_key是第一次出现. 然后再去完成 txn.combine_key 值的累计.
    http-request sc-inc-gpc1(0) if is_post { var(txn._combine_key),table_gpc0_rate eq 0 }  # 对 group:aggregate_key 这个复合键如果是第一次出现, 那么在gpc1中对group的进行自增(UV的概念).
    http-request sc-inc-gpc0(0) if is_post ## 在 gpc0 中对 group 的进行累计(PV的概念)
    http-request sc-inc-gpc0(1) if is_post ## 对 track-sc1 中的 group:aggregate_key 在 gpc0 中进行计数(PV) 

    http-request set-var(txn.gpv)  var(txn._group),table_gpc0_rate
    http-request set-var(txn.uv)  var(txn._group),table_gpc1_rate
    http-request set-var(txn.pv) var(txn._combine_key),table_gpc0_rate

    http-request return status 200 content-type application/json lf-string '{"gpv":%[var(txn.gpv)],"pv":%[var(txn.pv)],"uv":%[var(txn.uv)],"gidle":%[var(txn.gidle)],"vidle":%[var(txn.vidle)],"group":"%[var(txn.group)]","v":"%[var(txn.v)]","ttl":%[var(txn.gttl)],"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]","timestamp":"%[date]","ip": "%[src]","port": %cp}' hdr Access-Control-Allow-Origin "*"


backend group_distinct_7d
    stick-table type binary len 16 size 1g expire 7d store gpc0,gpc0_rate(7d),gpc1,gpc1_rate(7d)
    acl is_post method POST
    
    # 在 sc-inc-gpc0 之前获取 ttl(expire) 和 idle 信息.
    # 定义变量
    http-request set-var(txn.group) url_param(group)  # group   --> group 
    http-request set-var(txn.v) url_param(distinct) unless { url_param(v) -m found } # distinct --> aggregate_key
    http-request set-var(txn.v) url_param(v) if { url_param(v) -m found }  # distinct --> aggregate_key
    # http-request set-var-fmt(txn.combine_key) "%[var(txn.group)]%[var(txn.v)]"
    http-request set-var(txn.combine_key) var(txn.group),concat(':',txn.v,)  # 避免 v 为空值或者不传入分组待观测值时导致 combine_key 和 key 一样,进而重复计数.
    # str(1),digest(md5),hex  #  str(),concat(<ip=,sess.ip,>),concat(<dn=,sess.dn,>)
    http-request set-var(txn._combine_key) var(txn.combine_key),digest(md5)
    http-request set-var(txn._group) var(txn.group),digest(md5)
    http-request set-var(txn._v) var(txn.v),digest(md5)

    http-request set-var(txn.gttl)  var(txn._group),table_expire
    http-request set-var(txn.gttl)  int(0) unless { var(txn.gttl) -m found }   ## 如果前面没有设置 txn.gttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.gidle) var(txn._group),table_idle
    http-request set-var(txn.gidle) int(0) unless { var(txn.gidle) -m found }  ## 如果前面没有设置 txn.gttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.vidle) var(txn._combine_key),table_idle
    http-request set-var(txn.vidle) int(0) unless { var(txn.vidle) -m found }
    # 在 sc-inc-gpc0 之后, table key 的 ttl 和 idle 时间会重置.
    #  track-scX 和 sc-inc-gpc0(X) 里面的 X 是 sc0, sc1, sc2 中的一个. 

    http-request track-sc0 var(txn._group)  if is_post  # track-scx 会刷新 idle 和 ttl 的值.
    http-request track-sc1 var(txn._combine_key)  if is_post  # track-scx 会刷新 idle 和 ttl 的值.
    # 先取出 table 中 txn.combine_key 的速率, 如果最近窗口速率等于0说明txn.combine_key是第一次出现. 然后再去完成 txn.combine_key 值的累计.
    http-request sc-inc-gpc1(0) if is_post { var(txn._combine_key),table_gpc0_rate eq 0 }  # 对 group:aggregate_key 这个复合键如果是第一次出现, 那么在gpc1中对group的进行自增(UV的概念).
    http-request sc-inc-gpc0(0) if is_post ## 在 gpc0 中对 group 的进行累计(PV的概念)
    http-request sc-inc-gpc0(1) if is_post ## 对 track-sc1 中的 group:aggregate_key 在 gpc0 中进行计数(PV) 

    http-request set-var(txn.gpv)  var(txn._group),table_gpc0_rate
    http-request set-var(txn.uv)  var(txn._group),table_gpc1_rate
    http-request set-var(txn.pv) var(txn._combine_key),table_gpc0_rate

    http-request return status 200 content-type application/json lf-string '{"gpv":%[var(txn.gpv)],"pv":%[var(txn.pv)],"uv":%[var(txn.uv)],"gidle":%[var(txn.gidle)],"vidle":%[var(txn.vidle)],"group":"%[var(txn.group)]","v":"%[var(txn.v)]","ttl":%[var(txn.gttl)],"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]","timestamp":"%[date]","ip": "%[src]","port": %cp}' hdr Access-Control-Allow-Origin "*"


roadmap

现在是使用 haproxy 来进行计数, 目前用的都是 inc, 如果以后 sum 的场景, 这个就需要 add 操作.
sc-add-gpc
sc-inc-gpc

sc-add-gpc

backend rolling_sum10s
    stick-table type binary len 16 size 1g expire 20s store gpc(1),gpc_rate(1,20s)   # ,gpc1,gpc1_rate(10s)
    acl is_post method POST

    # 在 sc-inc-gpc0 之前获取 ttl(expire) 和 idle 信息.

    http-request set-var(txn.v) url_param(v)
    http-request set-var(txn.num) url_param(num)
    http-request set-var(txn._v) var(txn.v),digest(md5)
    http-request set-var(txn.keyttl) var(txn._v),table_expire
    http-request set-var(txn.keyttl) int(0) unless { var(txn.keyttl) -m found }   ## 如果前面没有设置 txn.keyttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    http-request set-var(txn.keyidle) var(txn._v),table_idle
    http-request set-var(txn.keyidle) int(0) unless { var(txn.keyidle) -m found }  ## 如果前面没有设置 txn.keyttl table 中没有这个记录, 是第一次出现, 这里进行初始化.
    # stick-table  type string  size 1m  expire 30s  store gpc0,gpc0_rate(10s),gpc1,gpc1_rate(10s)
    # 在 sc-inc-gpc0 之后, table key 的 ttl 和 idle 时间会重置.
    #  track-scX 和 sc-inc-gpc0(X) 里面的 X 是 sc0, sc1, sc2 中的一个. 
    http-request track-sc0 var(txn._v) #if is_post  # track-sc0 会刷新 idle 和 ttl 的值.
    #http-request sc-inc-gpc0(0) if is_post  # sc-inc-xxx 对键对应的值进行累加
    http-request sc-add-gpc(0,0) int(10)  # if is_post  # var(txn.num)
    http-request set-var(txn.counter) var(txn._v),table_gpc(0,)    # table_gpc0
    http-request set-var(txn.counter) int(0) unless { var(txn.counter) -m found }
    http-request set-var(txn.rate) var(txn._v),table_gpc_rate(0,)
    http-request set-var(txn.rate) int(0) unless { var(txn.rate) -m found }
    http-request return status 200 content-type application/json lf-string '{"counter":%[var(txn.rate)],"v":"%[var(txn.v)]","ttl":%[var(txn.keyttl)],"idle":%[var(txn.keyidle)],"ip": "%[src]","port": %cp,"date":"%[date,utime(%Y-%m-%dT%H:%M:%S%z)]", "timestamp":"%[date]"}' hdr Access-Control-Allow-Origin "*"

curl -XPOST "http://10.84.71.214:88/rolling_sum10s?v=abcdef&num=10"