跳到主要内容

limit-conn

limit-conn 插件限制 HTTP 并发请求、WebSocket 连接以及 Stream 路由上的 TCP 连接。超过配置阈值的流量可以被延迟或拒绝,从而控制资源使用并防止过载。

本地限速与基于 Redis 的限速​

对于 HTTP 路由,limit-conn 插件支持两种限速模式:

  • 本地限速:每个网关实例独立实施限速。每个实例维护自己的计数器,因此当流量分散到多个实例时,实际限额约为“限额 × 实例数”。未设置 policy 或将其设置为 local 时,这是默认模式。
  • 基于 Redis 的限速:所有网关实例通过 Redis 共享限额,因此配置的限额会应用到所有网关实例。

Stream 插件 Schema 未定义 policy,因此本文档中的 Stream 配置使用本地计数器。有关 Stream 参数和示例,请参阅限制 Stream 路由上的 TCP 连接。

示例​

以下场景演示如何为 HTTP、WebSocket 和 Stream 路由配置本地及基于 Redis 的连接限制。

基于远程地址进行速率限制​

此示例使用 remote_addr 作为限速键,因此每个客户端地址都有独立的连接数和突发阈值。

创建一个按客户端地址限制连接数的路由:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "limit-conn-route",
"uri": "/get",
"plugins": {
"limit-conn": {
"conn": 2,
"burst": 1,
"default_conn_delay": 0.1,
"key_type": "var",
"key": "remote_addr",
"policy": "local",
"rejected_code": 429
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

❶ conn:允许 2 个并发请求。

❷ burst:允许 1 个额外的并发请求。

❸ default_conn_delay:对于并发数介于 conn 和 conn + burst 之间的请求,允许 0.1 秒的处理延迟。

❹ key_type:设置为 var,将 key 解释为变量。

❺ key:根据请求的 remote_addr 计算限速计数。

❻ policy:使用内存中的本地计数器。

❼ rejected_code:将拒绝状态码设置为 429。

向该路由发送五个并发请求:

seq 1 5 | xargs -n1 -P5 bash -c 'curl -s -o /dev/null -w "Response: %{http_code}\n" "http://127.0.0.1:9080/get"'

你应该会看到类似于以下的响应,其中多余的请求被拒绝:

Response: 200
Response: 200
Response: 200
Response: 429
Response: 429

基于远程地址和消费者名称进行速率限制​

此示例在限速键中组合 remote_addr 和 consumer_name,使每个消费者在同一客户端地址上拥有独立配额。

创建消费者 john:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "john"
}'

为该消费者创建 key-auth 凭证:

curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'

创建第二个消费者 jane:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jane"
}'

为该消费者创建 key-auth 凭证:

curl "http://127.0.0.1:9180/apisix/admin/consumers/jane/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jane-key-auth",
"plugins": {
"key-auth": {
"key": "jane-key"
}
}
}'

创建一个启用了 key-auth 和 limit-conn 插件的路由:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "limit-conn-route",
"uri": "/get",
"plugins": {
"key-auth": {},
"limit-conn": {
"conn": 2,
"burst": 1,
"default_conn_delay": 0.1,
"rejected_code": 429,
"policy": "local",
"key_type": "var_combination",
"key": "$remote_addr $consumer_name"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

❶ key-auth:在路由上启用密钥认证。

❷ key_type:设置为 var_combination,将 key 解释为变量组合。

❸ key:设置为 $remote_addr $consumer_name 以根据远程地址和消费者应用限速配额。

作为消费者 john 发送五个并发请求:

seq 1 5 | xargs -n1 -P5 bash -c 'curl -s -o /dev/null -w "Response: %{http_code}\n" "http://127.0.0.1:9080/get" -H "apikey: john-key"'

你应该会看到类似于以下的响应,其中多余的请求被拒绝:

Response: 200
Response: 200
Response: 200
Response: 429
Response: 429

立即作为消费者 jane 发送五个并发请求:

seq 1 5 | xargs -n1 -P5 bash -c 'curl -s -o /dev/null -w "Response: %{http_code}\n" "http://127.0.0.1:9080/get" -H "apikey: jane-key"'

你应该也会看到类似于以下的响应,其中多余的请求被拒绝:

Response: 200
Response: 200
Response: 200
Response: 429
Response: 429

两个消费者得到相同的结果,说明每个消费者都有独立配额。

限制 WebSocket 连接速率​

此示例限制每个客户端地址的并发 WebSocket 连接数,并使用固定版本的回显服务器,使上游环境可在 Docker 和 Kubernetes 中复现。

启动一个示例上游 WebSocket 服务器:

将 GATEWAY_CONTAINER 设置为正在运行的 APISIX 或 API7 网关容器。创建专用网络并将网关连接到该网络:

export GATEWAY_CONTAINER=replace-with-gateway-container-name

docker network create gateway-websocket-net
docker network connect gateway-websocket-net "$GATEWAY_CONTAINER"

在共享网络中启动回显服务器:

docker run -d \
--name websocket-server \
--network gateway-websocket-net \
jmalloc/echo-server:v0.3.7

设置 Admin API 和 ADC 示例使用的上游主机名:

export WEBSOCKET_UPSTREAM_HOST=websocket-server

服务器在 /.ws 提供端点,并回显收到的每条消息。

创建一条到 WebSocket 服务器端点的路由,并为该路由启用 WebSocket:

curl "http://127.0.0.1:9180/apisix/admin/routes/ws-limit-conn" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d @- <<EOF
{
"uri": "/.ws",
"plugins": {
"limit-conn": {
"conn": 2,
"burst": 1,
"default_conn_delay": 0.1,
"key_type": "var",
"key": "remote_addr",
"rejected_code": 429,
"policy": "local"
}
},
"enable_websocket": true,
"upstream": {
"type": "roundrobin",
"nodes": {
"$WEBSOCKET_UPSTREAM_HOST:8080": 1
}
}
}
EOF

如果尚未安装 WebSocket 客户端,请安装 websocat 等客户端。通过路由打开连接:

websocat "ws://127.0.0.1:9080/.ws"

发送 hello,服务器应回显该消息:

Request served by 1cd244052136
hello
hello

保持第一个连接处于打开状态,再打开三个终端会话并在每个会话中运行相同命令:

websocat "ws://127.0.0.1:9080/.ws"

第四个连接应以 429 Too Many Requests 失败,因为两个连接处于活动状态,另一个连接已占用突发容量。

限制 Stream 路由上的 TCP 连接​

此示例限制 APISIX Stream 路由上来自每个客户端地址的 TCP 并发连接。它允许每个客户端有一个活动连接,并在该活动连接结束前关闭其他连接。

创建 Stream 路由前,请配置 APISIX 以监听端口 9100 上的 TCP 流量。

在网关配置文件中添加或更新以下部分:

config.yaml
apisix:
proxy_mode: http&stream
stream_proxy:
tcp:
- 9100

对于主机安装,更新配置后重新加载 APISIX:

apisix reload

对于 APISIX 快速入门容器,请在创建或重新部署容器时将端口 9100 从容器发布到主机,例如添加 -p 9100:9100。然后在容器中重新加载 APISIX:

docker exec apisix-quickstart apisix reload

有关公开 Stream 监听器的更多信息,请参阅代理传输层(L4)流量。

创建一个 Stream 路由,允许来自每个客户端地址的一个并发连接,并将 TCP 流量代理到一个支持 HTTP 的测试服务:

curl "http://127.0.0.1:9180/apisix/admin/stream_routes/limit-conn-tcp" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"server_port": 9100,
"plugins": {
"limit-conn": {
"conn": 1,
"burst": 0,
"default_conn_delay": 0.1,
"key_type": "var",
"key": "remote_addr"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

❶ conn:允许每个客户端地址有一个并发 TCP 连接。

❷ burst:将突发容量设为 0,使超出限制的连接立即被拒绝,而不是延迟处理。

❸ default_conn_delay:将必填的基础延迟设为 0.1 秒。由于 burst 为 0,此示例不会延迟超额连接。

❹ key_type:将配置的 key 解释为变量。

❺ key:使用 remote_addr 分别统计每个客户端地址的连接数。

打开与 Stream 路由的 TCP 连接,并保持该会话打开:

nc 127.0.0.1 9100

在另一个终端中,尝试从同一客户端地址打开第二个连接:

curl -i --max-time 5 \
-H "Host: httpbin.org" \
"http://127.0.0.1:9100/get"

第二个连接会被拒绝,终端显示:

curl: (52) Empty reply from server

根据客户端和操作系统的不同,也可能显示 Connection reset by peer。

关闭 nc 会话,然后再次运行请求:

curl -i --max-time 5 \
-H "Host: httpbin.org" \
"http://127.0.0.1:9100/get"

由于连接槽位再次可用,连接现在会成功:

HTTP/1.1 200 OK
Content-Type: application/json
...
{
"url": "http://httpbin.org/get"
}

API7 网关使用 Stream 服务和网关组范围的 Admin API 请求。有关其 limit-conn Stream 路由配置流程,请参阅配置 TCP/UDP 代理。

使用 Redis 服务器在网关节点间共享配额​

此示例将连接计数器存储在一个 Redis 服务器中,使加载同一路由资源的网关节点共同实施同一限额。

启动 Redis​

在网关所在环境中启动 Redis。

将 GATEWAY_CONTAINER 设置为正在运行的 APISIX 或 API7 网关容器。创建专用网络并将网关连接到该网络:

export GATEWAY_CONTAINER=replace-with-gateway-container-name

docker network create gateway-redis-net
docker network connect gateway-redis-net "$GATEWAY_CONTAINER"

对用于验证共享计数器的每个其他网关容器重复执行网络连接命令:

docker network connect gateway-redis-net replace-with-additional-gateway-container

在共享网络中启动 Redis:

docker run -d \
--name redis-standalone \
--network gateway-redis-net \
redis:8.10.2-alpine \
redis-server --requirepass redis-password

验证需要认证的连接:

docker exec -e REDISCLI_AUTH=redis-password redis-standalone redis-cli ping

你应该会收到 PONG。

设置 Admin API 和 ADC 示例使用的 Redis 主机名:

export REDIS_HOST=redis-standalone

这些测试资源使用教程密码和内部服务地址。用于生产环境时,请限制网络访问、以 Secret 管理凭证、启用持久化,并在支持的情况下配置 TLS。

创建路由​

在网关集群或网关组中创建路由。每个节点都必须加载同一路由资源,并且能够访问 Redis。该路由将请求发送到 httpbin 延迟两秒的端点,使连接在验证期间发生重叠。

curl "http://127.0.0.1:9180/apisix/admin/routes/limit-conn-redis" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d @- <<EOF
{
"uri": "/limit-conn/redis",
"plugins": {
"proxy-rewrite": {
"uri": "/delay/2"
},
"limit-conn": {
"conn": 1,
"burst": 1,
"default_conn_delay": 0.1,
"rejected_code": 429,
"key_type": "var",
"key": "remote_addr",
"policy": "redis",
"redis_host": "$REDIS_HOST",
"redis_password": "redis-password"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

❶ policy:设置为 redis,将连接计数器存储在 Redis 中。

❷ redis_host:设置为网关可以访问的 Redis 主机名。

❸ redis_password:设置为 Redis 配置的密码。

向该路由发送五个并发请求:

seq 1 5 | xargs -n1 -P5 bash -c 'curl -s -o /dev/null -w "Response: %{http_code}\n" "http://127.0.0.1:9080/limit-conn/redis"'

你应该会看到类似于以下的响应,其中多余的请求被拒绝:

Response: 200
Response: 200
Response: 429
Response: 429
Response: 429

响应的确切顺序可能不同,但一次最多只能接受两个请求:一个在 conn 范围内,另一个在 burst 范围内。通过同一网关集群或网关组中的不同节点发送请求,以验证 Redis 在所有节点间共同实施限额,而不是每个节点各自实施一次。

使用 Redis Cluster 在网关节点间共享配额​

此示例将连接计数器存储在 Redis Cluster 中,使多个网关节点共享经过分片和复制的计数器存储。

启动 Redis Cluster​

将 GATEWAY_CONTAINER 设置为正在运行的 APISIX 或 API7 网关容器。创建专用网络并将网关连接到该网络:

export GATEWAY_CONTAINER=replace-with-gateway-container-name

docker network create gateway-redis-cluster-net
docker network connect gateway-redis-cluster-net "$GATEWAY_CONTAINER"

对用于验证共享计数器的每个其他网关容器重复执行网络连接命令:

docker network connect gateway-redis-cluster-net replace-with-additional-gateway-container

创建以下 Docker Compose 文件,其中包含三个主节点和三个副本:

docker-compose.yml
x-redis-node: &redis-node
image: redis:8.10.2-alpine
command:
- redis-server
- --cluster-enabled
- "yes"
- --cluster-config-file
- nodes.conf
- --cluster-node-timeout
- "5000"
- --appendonly
- "no"
- --requirepass
- redis-cluster-password
- --masterauth
- redis-cluster-password
healthcheck:
test: ["CMD", "redis-cli", "-a", "redis-cluster-password", "ping"]
interval: 2s
timeout: 2s
retries: 30
networks:
- gateway-redis-cluster-net

services:
redis-cluster-1:
<<: *redis-node
container_name: redis-cluster-1
redis-cluster-2:
<<: *redis-node
container_name: redis-cluster-2
redis-cluster-3:
<<: *redis-node
container_name: redis-cluster-3
redis-cluster-4:
<<: *redis-node
container_name: redis-cluster-4
redis-cluster-5:
<<: *redis-node
container_name: redis-cluster-5
redis-cluster-6:
<<: *redis-node
container_name: redis-cluster-6

networks:
gateway-redis-cluster-net:
external: true

启动节点并等待其健康检查通过:

docker compose up -d --wait

初始化集群,为每个主节点配置一个副本:

docker exec -e REDISCLI_AUTH=redis-cluster-password redis-cluster-1 \
redis-cli --cluster create \
redis-cluster-1:6379 \
redis-cluster-2:6379 \
redis-cluster-3:6379 \
redis-cluster-4:6379 \
redis-cluster-5:6379 \
redis-cluster-6:6379 \
--cluster-replicas 1 \
--cluster-yes

输出应以 [OK] All 16384 slots covered. 结尾。

等待几秒钟,让节点交换集群状态,然后验证所有哈希槽均可用:

docker exec -e REDISCLI_AUTH=redis-cluster-password redis-cluster-1 \
redis-cli cluster info

输出应包含 cluster_state:ok 和 cluster_slots_assigned:16384。

设置 Admin API 和 ADC 示例使用的节点主机名:

export REDIS_CLUSTER_NODE_1=redis-cluster-1
export REDIS_CLUSTER_NODE_2=redis-cluster-2
export REDIS_CLUSTER_NODE_3=redis-cluster-3
export REDIS_CLUSTER_NODE_4=redis-cluster-4
export REDIS_CLUSTER_NODE_5=redis-cluster-5
export REDIS_CLUSTER_NODE_6=redis-cluster-6

这些测试资源仅用于评估。用于生产环境时,请遵循 Redis 关于持久化、认证、TLS、故障恢复和拓扑规模的指导。

创建路由​

在网关集群或网关组中创建路由。每个节点都必须加载同一路由资源,并且能够访问 Redis Cluster。该路由将请求发送到 httpbin 延迟两秒的端点,使连接在验证期间发生重叠。

curl "http://127.0.0.1:9180/apisix/admin/routes/limit-conn-redis-cluster" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d @- <<EOF
{
"uri": "/limit-conn/redis-cluster",
"plugins": {
"proxy-rewrite": {
"uri": "/delay/2"
},
"limit-conn": {
"conn": 1,
"burst": 1,
"default_conn_delay": 0.1,
"rejected_code": 429,
"key_type": "var",
"key": "remote_addr",
"policy": "redis-cluster",
"redis_cluster_nodes": [
"$REDIS_CLUSTER_NODE_1:6379",
"$REDIS_CLUSTER_NODE_2:6379",
"$REDIS_CLUSTER_NODE_3:6379",
"$REDIS_CLUSTER_NODE_4:6379",
"$REDIS_CLUSTER_NODE_5:6379",
"$REDIS_CLUSTER_NODE_6:6379"
],
"redis_password": "redis-cluster-password",
"redis_cluster_name": "limit-conn-docs-redis-cluster"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

❶ policy:设置为 redis-cluster,将连接计数器存储在 Redis Cluster 中。

❷ redis_cluster_nodes:设置为网关可以解析和访问的节点地址。客户端会通过这些种子节点发现集群中的其他节点。

❸ redis_password:设置为 Redis Cluster 配置的密码。

❹ redis_cluster_name:设置为在网关中标识该 Redis Cluster 的名称。

向该路由发送五个并发请求:

seq 1 5 | xargs -n1 -P5 bash -c 'curl -s -o /dev/null -w "Response: %{http_code}\n" "http://127.0.0.1:9080/limit-conn/redis-cluster"'

你应该会看到类似于以下的响应,其中多余的请求被拒绝:

Response: 200
Response: 200
Response: 429
Response: 429
Response: 429

响应的确切顺序可能不同,但一次最多只能接受两个请求。通过同一网关集群或网关组中的不同节点发送请求,以验证所有节点共享基于集群的计数器。

基于规则的速率限制​

limit-conn 可以根据请求属性应用不同的限速规则。此功能自 API7 企业版 3.8.17 和 APISIX 3.16.0 起提供。以下示例使用 HTTP 请求头的值表示调用者的访问层级。

规则按顺序应用。如果配置的键不存在,则跳过对应规则。

提示

规则还可以使用其他内置变量,针对不同请求属性实施限速。

创建一个路由,为每个订阅应用一个限额,并对试用用户应用更严格的限额。该示例从请求头中读取订阅标识符和试用标识符:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "limit-conn-rules-route",
"uri": "/get",
"plugins": {
"limit-conn": {
"rejected_code": 429,
"default_conn_delay": 0.1,
"policy": "local",
"rules": [
{
"key": "${http_x_subscription_id}",
"conn": "${http_x_custom_conn ?? 5}",
"burst": 1
},
{
"key": "${http_x_trial_id}",
"conn": 1,
"burst": 1
}
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

❶ 使用 X-Subscription-ID 请求头的值作为速率限制键。

❷ 根据 X-Custom-Conn 请求头动态设置请求并发连接数。如果未提供该请求头,则应用默认并发连接数 5。

❸ 使用 X-Trial-ID 请求头的值作为速率限制键。

要验证速率限制,使用相同的订阅 ID 发送 7 个并发请求到该路由:

seq 1 7 | xargs -n1 -P7 bash -c 'curl -s -o /dev/null -w "Response: %{http_code}\n" "http://127.0.0.1:9080/get" -H "X-Subscription-ID: sub-123456789"'

你应该会看到以下响应,显示当未提供 X-Custom-Conn 头时,应用了 5 个并发连接限制和 1 个突发请求:

Response: 429
Response: 200
Response: 200
Response: 200
Response: 200
Response: 200
Response: 200

使用相同的订阅 ID 发送 5 个并发请求到该路由,并将 X-Custom-Conn 头设置为 1:

seq 1 5 | xargs -n1 -P5 bash -c 'curl -s -o /dev/null -w "Response: %{http_code}\n" "http://127.0.0.1:9080/get" -H "X-Subscription-ID: sub-123456789" -H "X-Custom-Conn: 1"'

你应该会看到以下响应,显示应用了 1 个并发连接限制和 1 个突发请求:

Response: 429
Response: 429
Response: 429
Response: 200
Response: 200

最后,使用试用 ID 头生成 5 个请求到该路由:

seq 1 5 | xargs -n1 -P5 bash -c 'curl -s -o /dev/null -w "Response: %{http_code}\n" "http://127.0.0.1:9080/get" -H "X-Trial-ID: trial-123456789"'

你应该会看到以下响应,显示应用了 1 个并发连接限制和 1 个突发请求:

Response: 429
Response: 429
Response: 429
Response: 200
Response: 200