> For the complete documentation index, see [llms.txt](https://www.axisnow.io/zh/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://www.axisnow.io/zh/docs/edge/policies/rate-limiting/how-it-works.md).

# 工作原理

速率控制的计数与超限判定方式

## 一次请求经历什么 <a href="#request-flow" id="request-flow"></a>

请求进入边缘后，速率控制插件按下面的顺序处理：

1. **逐条评估规则**：按优先级从前往后，条件不匹配的规则跳过，匹配的规则进入下一步。
2. **生成计数 Key**：由这条规则和您选定的维度的当前取值拼成一个唯一键。例如按客户端 IP 地址（`ip.src`）加请求 URI 路径（`http.request.uri.path`）计数时，来自 `203.0.113.7`、访问 `/api/login` 的请求落在“这条规则 × 这个 IP × 这个路径”的计数器上。
3. **查限制状态并计数**：这个 Key 已在限制期内，本请求执行限速动作，不计数；否则计数加一，请求继续。计数加到阈值的那一刻，这个 Key 进入 N 秒的限制期。
4. **继续评估下一条规则**：命中多条规则时，每条各自计数、各自判定。**观察**动作不中断评估；**阻断**、**IP 封禁**和**挑战**（含持有有效凭证直接放行的请求）会结束这个请求在速率控制里的评估。已验证的 Bot 触发 IP 封禁时被放行，评估继续。

速率控制在缓存之前判定，命中缓存的请求同样被计数、同样会被限制。

下文把请求匹配了规则条件、被纳入计数叫“命中”，把这个 Key 的计数达到阈值、触发了动作叫“超限”。

## 计数 Key <a href="#counting-key" id="counting-key"></a>

计数 Key 决定“按谁来数”。它由这条规则以及您在配置里选定的每个维度及其在本次请求中的取值拼成。几个推论：

* **每条规则独立计数**：同一个客户端命中两条规则，是两个计数器。修改规则的阈值或条件不会清空已有计数；修改计数维度等于换了一套新的计数器。
* **维度的配置顺序无关**：拼接前会按固定顺序排列，`IP + 路径` 与 `路径 + IP` 是同一个 Key。
* **维度取不到值时按空值参与**：例如按请求头 `authorization` 计数，没有带这个头的请求全部落进同一个“空值”计数器；如果匿名流量很大，这个计数器会先触发。按身份计数的规则通常要配合条件，只对带身份的请求生效，或者另配一条按 IP 的规则兜底。**会话标识**是例外，没有该 Cookie 时回退为按客户端 IP 计数。
* **取值区分大小写**：不做归一化（请求头名称除外，它在 HTTP 层本就不区分大小写）。
* 所有维度都为空时，自动回退到按请求 Host 加客户端 IP 计数。

以下面这条规则为例：

* **计数 Key**：客户端 IP 地址 + 请求头 `x-api-key`
* **限速阈值**：10 秒内不超过 2 请求
* **限速动作**：阻断

下图是它处理五个请求的过程。请求按计数 Key 分拣到各自的计数器里，同一条规则下每个计数器的阈值相同，水位各自涨：

<figure><img src="https://3062891322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKDgGNU6JXG7cqYCk2aCz%2Fuploads%2Fgit-blob-3a834b951bd9d44e7c67e491c9bfacfdfa78e320%2Frate-limiting-counting-key.png?alt=media" alt="请求按计数 Key 落进各自的计数器；Key A 的水位涨到阈值 2 后进入 10 秒限制期，它的下一个请求被阻断，而 Key B、Key C 不受影响"><figcaption></figcaption></figure>

可选维度的完整列表与各自的参数见[配置项 › 计数 Key](/zh/docs/edge/policies/rate-limiting/configuration.md#counting-key)。

## 阈值与窗口 <a href="#threshold-and-window" id="threshold-and-window"></a>

阈值写成「N 秒内不超过 M 请求」。计数器采用滑动窗口算法：时间按 N 秒切成连续的窗口，判断频率时不是只看当前窗口，而是把上一个窗口的计数按它在“最近 N 秒”里还占的比例折算进来，再加上当前窗口的计数。例如窗口 60 秒，当前窗口刚过了 15 秒，那么上一窗口的计数按 75% 计入。这种估算方式只需为每个 Key 保存两个计数，不用记录每个请求的时间戳，是业界边缘限速的常用做法（Cloudflare 在 [How we built rate limiting capable of scaling to millions of domains](https://blog.cloudflare.com/counting-things-a-lot-of-different-things/) 里有详细推导）。

这样做是为了避免固定窗口的边界漏洞：如果只按整窗口计数，攻击者在上一窗口的最后几秒发 M 个、下一窗口的最初几秒再发 M 个，短短几秒内放进了 2M 个请求却从未触发。滑动窗口下，任意连续 N 秒内的请求量都大致受 M 约束。

判定规则：

* 第 1 到第 M 个请求放行；第 M 个请求把计数加到阈值，这个 Key 从这一刻起进入 N 秒的限制期（不是到窗口边界）。
* 限制期内该 Key 的每个请求——也就是第 M + 1 个起——都执行限速动作，且不再计数。
* 限制期结束后恢复计数。因为上一窗口的计数仍以一定权重参与估算，持续高频的来源在解除后很快会再次触发；低于阈值的来源则不受影响。
* 阈值设为 0 与设为 1 效果相同：放行第一个请求后即进入限制期。

## 响应计数条件 <a href="#response-counting" id="response-counting"></a>

默认情况下，匹配条件的请求在请求阶段计数，不管它最终返回什么。开启响应计数条件后，计数推迟到响应头就绪时，只有响应满足条件（例如状态码为 404 Not Found，或带某个响应头）的请求才累加；在触发之前，不满足条件的请求既不计数也不受影响。

这让您可以按“行为结果”而不是“请求数量”限速：撞库产生的大量 401、扫描器产生的大量 404，正常用户几乎不会密集产生。第 M 个满足条件的响应产生时计数达到阈值，这个 Key 进入限制期，从它的下一个请求起在请求阶段就执行限速动作，不论那个请求会返回什么。

以下面这条规则为例：

* **条件**：请求 URI 路径 等于 `/api/login`
* **计数 Key**：客户端 IP 地址
* **响应计数条件**：响应状态码 在以下各项之中 `401`
* **限速阈值**：60 秒内不超过 2 请求
* **限速动作**：阻断

同一个 IP 连续四个请求的处理过程：

<table data-search="false"><thead><tr><th width="90">请求</th><th width="200">请求阶段</th><th width="120">源站响应</th><th width="200">计数（只数 401）</th><th>结果</th></tr></thead><tbody><tr><td>请求 1</td><td>评估规则，放行到源站</td><td>401</td><td>+1 → 1</td><td>放行</td></tr><tr><td>请求 2</td><td>评估规则，放行到源站</td><td>200</td><td>不满足条件，仍为 1</td><td>放行</td></tr><tr><td>请求 3</td><td>评估规则，放行到源站</td><td>401</td><td>+1 → 2，达到阈值</td><td>放行；该 IP 进入 60 秒限制期</td></tr><tr><td>请求 4</td><td>限制期内，执行限速动作</td><td>未到源站</td><td>不计数</td><td>阻断</td></tr></tbody></table>

命中缓存的响应同样参与响应计数。

## 计数的作用域 <a href="#scope" id="scope"></a>

计数器和限制状态按边缘节点各自保存，节点之间不同步。坦白说，这是一个工程取舍：要让分布在不同地域的节点对同一个 Key 共享一个精确计数，就得在请求路径上引入一个全局或分区的计数服务，随之而来的是每个请求多一次远程往返、计数服务本身的可用性和一致性问题、跨地域复制的延迟，以及它一旦故障对限速乃至整个请求链路的影响。边缘限速的目标是在节点本地、以微秒级的开销挡住绝大多数滥用，为此我们选择节点内精确、节点间独立，而不是为了全局精确把限速变成一个有状态的分布式系统。

实际影响：一个来源的流量如果分散到了 K 个边缘节点，它在触发限制前总共能发出的请求约是阈值的 K 倍。如果您的流量分布在多个边缘节点，阈值要比按总流量估的值更低；对已确认的攻击来源用 IP 封禁，它一旦在某个节点触发，就在该节点持续生效一整段封禁时长。IP 封禁同样按节点生效，封禁记录里的**生效边缘**一列显示的就是触发封禁的节点。

计数器不可用时请求照常放行，速率控制是失败放行（fail-open）的：它的故障不会变成您业务的故障。

## 四种动作 <a href="#actions" id="actions"></a>

<table data-search="false"><thead><tr><th width="100">动作</th><th width="150">作用对象</th><th>行为</th><th>适合</th></tr></thead><tbody><tr><td>观察</td><td>—</td><td>不干预请求，只记录与统计。超限的请求会出现在「观察请求」指标和日志检索里</td><td>新规则上线前的灰度验证；评估阈值的误伤面</td></tr><tr><td>阻断</td><td>触发的计数 Key</td><td>阈值以内的请求照常放行；限制期内该 Key 的请求被拒绝，返回您设定的状态码（默认 429 Too Many Requests）和页面，或不返回页面内容。同一 IP 的其它 Key（其它路径、其它规则）不受影响</td><td>API 客户端：它们能根据状态码正确退避</td></tr><tr><td>IP 封禁</td><td>客户端 IP</td><td>封禁时长内该 IP 到这个节点的全部连接都被丢弃，不区分域名和端口。触发的那个请求连接被直接关闭，没有响应</td><td>已确认的攻击来源：开销最低，但误伤面也最大</td></tr><tr><td>挑战</td><td>触发的计数 Key</td><td>向浏览器返回人机验证页（HTTP 403 Forbidden）；通过后下发凭证，有效期内这条规则不再限制该客户端。只对浏览器页面导航请求（在地址栏打开或点击链接产生的请求，不包括页面脚本发出的接口调用）有意义</td><td>面向真人的页面流量：真人验证后继续访问，脚本被挡住</td></tr></tbody></table>

阻断和 IP 封禁的区别值得单独看一眼。

**阻断**只拒绝超出阈值的那部分请求：计数按 Key 分别进行，哪个 Key 在窗口内超了，哪个 Key 在接下来的限制期内被拒，阈值以内的流量照常放行，起到的是削峰的作用。它适合 API 客户端——超出的请求收到您设定的状态码（如 429 Too Many Requests）后退避，其余请求不受影响。

<figure><img src="https://3062891322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKDgGNU6JXG7cqYCk2aCz%2Fuploads%2Fgit-blob-ff73abda4cd1d5420092e403ab3abe39ad5f214a%2Frate-limiting-action-block.png?alt=media" alt="阻断：阈值以内的请求照常放行，只有超出阈值的那部分请求被拒绝" width="600"><figcaption></figcaption></figure>

**IP 封禁**一旦触发，就在封禁时长内拒绝该 IP 的全部请求，不看路径、不看是否还超阈值，直到封禁到期。它适合已确认的攻击来源：开销最低，但误伤面也最大，共享出口后面的正常用户会一起被封。

<figure><img src="https://3062891322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKDgGNU6JXG7cqYCk2aCz%2Fuploads%2Fgit-blob-791b2dd2829e3765f7fed3288da822f034ebaa6f%2Frate-limiting-action-ban.png?alt=media" alt="IP 封禁：计数达到阈值即触发，封禁时长内该 IP 的全部请求都被拒绝，到期后恢复" width="600"><figcaption></figcaption></figure>

### 响应头 <a href="#response-headers" id="response-headers"></a>

超限后产生的响应会带三个响应头。观察模式下请求照常由源站响应，响应头同样会加上：

<table data-search="false"><thead><tr><th width="220">响应头</th><th>含义</th></tr></thead><tbody><tr><td><code>X-RateLimit: true</code></td><td>这个请求已超限（观察模式下仍由源站正常响应）</td></tr><tr><td><code>X-RateLimit-Limit</code></td><td>触发的阈值（请求数）</td></tr><tr><td><code>X-RateLimit-Reset</code></td><td>限制期的长度（秒），等于规则的时间窗口 N。它是固定值，不是剩余秒数；客户端把它当作最长退避时间即可</td></tr></tbody></table>

目前不返回 [`Retry-After`](https://www.rfc-editor.org/rfc/rfc9110#section-10.2.3) 头。

### 阻断的响应 <a href="#block-response" id="block-response"></a>

阻断的响应状态码可在 400–599 之间设定，默认 [429 Too Many Requests](https://www.rfc-editor.org/rfc/rfc6585#section-4)；响应体可以用内置页面、自己编辑或上传的页面（支持 `${{remote_addr}}`、`${{http_host}}`、`${{request_id}}` 等模板变量），也可以选择**断开连接**，不返回任何页面内容。详见[配置项 › 阻断](/zh/docs/edge/policies/rate-limiting/configuration.md#action-block)。

### IP 封禁的细节 <a href="#ip-ban-details" id="ip-ban-details"></a>

* 封禁期内该 IP 的请求不进入速率控制，不产生计数和日志，也不消耗边缘的资源。
* 封禁时长 1 秒到 1 年，到期自动解除。目前没有手动解封的入口；禁用或删除规则可以阻止再次封禁，但已生效的封禁会持续到期满。
* **已验证的 Bot 不会被封禁**：搜索引擎、SEO 工具、监控服务、Webhook 等经过验证的爬虫来源触发 IP 封禁时，请求被放行并继续评估后面的规则，访问日志里带 Bot 信息，不计入 IP 封禁次数。阻断动作没有这个豁免。
* 封禁记录可在洞察页查看，包括触发规则、生效边缘、剩余时长，见[洞察与日志 › IP 封禁记录](/zh/docs/edge/policies/rate-limiting/insight.md#ban-records)。

### 挑战的细节 <a href="#challenge-details" id="challenge-details"></a>

* 两种类型：**AxisNow 挑战**在访客浏览器里自动完成一次轻量计算，无需任何外部服务；**第三方挑战**接入您自己的 Cloudflare Turnstile 或 Google reCAPTCHA（v3 可设分数阈值）。
* 通过后浏览器获得一个凭证 Cookie，与这条规则、客户端 IP 和 TLS 指纹绑定；有效期内该客户端对这条规则免于限制，换 IP 或换到另一条规则需要重新验证。有效期可在 30 分钟到 24 小时间选择（默认 24 小时），AxisNow 挑战可自定义 60 秒到 7 天。
* **自动封禁**：同一客户端 IP 在窗口内被下发挑战的次数达到上限（默认 60 秒内 100 次）即封禁该 IP（默认 5 分钟）。它防的是反复触发挑战而不完成、或者轰炸验证接口的客户端。
* 挑战与[通用访问控制里的挑战](/zh/docs/edge/policies/access-control/how-it-works.md#challenge-details)是同一套机制，但凭证相互独立：通过了访问控制的挑战，不等于通过了速率控制的挑战。

## 只覆盖 HTTP <a href="#http-only" id="http-only"></a>

速率控制作用于 HTTP 和 HTTPS 端点。TCP 转发端点不经过本插件。

## 下一步 <a href="#next-steps" id="next-steps"></a>

* [配置项](/zh/docs/edge/policies/rate-limiting/configuration.md)：每个字段的含义与取值范围。
* [防护场景](/zh/docs/edge/policies/rate-limiting/use-cases.md)：按攻击面整理的参数组合。
* [洞察与日志](/zh/docs/edge/policies/rate-limiting/insight.md)：命中、超限、封禁与挑战数据怎么读。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://www.axisnow.io/zh/docs/edge/policies/rate-limiting/how-it-works.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
