限流
OxPHP 内置了按 IP 的限流器,因此无需运行任何外部依赖或基础设施。启用后,它会按客户端 IP 跟踪请求数量,并在客户端超过配置的阈值时返回 429 Too Many Requests 响应。
工作原理
限流器使用以客户端 IP 地址为键的固定窗口计数器。每个 IP 拥有各自独立的计数器和窗口。
- 当请求到达时,OxPHP 会在其内部跟踪器中查找该客户端 IP。
- 如果不存在对应条目,或当前窗口已过期,就会开启一个新窗口,计数器归零。
- 每个请求都会使计数器递增。
- 如果计数器超过
RATE_LIMIT,服务器会立即返回带有限流响应头的429响应。请求会在路由或 PHP 执行之前被拒绝。
被限流的请求仍会出现在访问日志和指标中。
配置
| 变量 | 默认值 | 说明 |
|---|---|---|
RATE_LIMIT |
0 |
每个 IP 在窗口内允许的最大请求数。0 表示完全禁用限流,且零开销 |
RATE_WINDOW_SECONDS |
60 |
限流窗口的时长,单位为秒 |
# 每个 IP 在 60 秒窗口内允许 100 个请求
RATE_LIMIT=100
RATE_WINDOW_SECONDS=60响应头
被拒绝的请求会返回 429 Too Many Requests 响应,并带有以下响应头:
| 响应头 | 说明 |
|---|---|
Retry-After |
距离当前窗口重置的秒数 |
x-ratelimit-limit |
每个窗口允许的最大请求数 |
x-ratelimit-remaining |
当前窗口内剩余的请求数(被限流时为 0) |
x-ratelimit-reset |
距离当前窗口重置的秒数 |
x-request-id |
请求 ID,用于将此响应与访问日志关联 |
429 响应示例:
HTTP/1.1 429 Too Many Requests
Retry-After: 45
x-ratelimit-limit: 100
x-ratelimit-remaining: 0
x-ratelimit-reset: 45
x-request-id: 67e2a1f412341a2b0042
429 Too Many Requests故障排查
合法用户被限流了
你的阈值对于真实的流量模式来说可能过低。检查指标中 429 响应的比率,并相应地调整 RATE_LIMIT 或 RATE_WINDOW_SECONDS。
检查被限流的请求数量:
curl http://localhost:9090/metrics | grep rate_limited解决方法: 提高 RATE_LIMIT 或延长 RATE_WINDOW_SECONDS,给客户端更多余量。
企业 NAT 后的用户共用同一个 IP 计数器
OxPHP 按源 IP 进行限流。所有位于共享 NAT 或代理之后的用户会共用一个计数器。如果这带来了问题,可以考虑禁用 OxPHP 内置的限流器(RATE_LIMIT=0),转而在更高层级(例如你的负载均衡器或 API 网关)实施限流,那里你可以访问到用户标识符。
设置 TRUSTED_PROXIES,以确保限流使用真实的客户端 IP,而不是代理的 IP。参见可信代理。
限流在多个实例之间不生效
OxPHP 的限流器基于内存且按实例独立。如果你在负载均衡器之后运行多个 OxPHP 实例,每个实例都会跟踪各自独立的计数器。一个客户端可以向每个实例发送 RATE_LIMIT 个请求而不触发 429。若要在多个实例之间进行协调限流,请在负载均衡器或 API 网关层级使用外部限流器。
在 IP 轮换攻击下内存增长
OxPHP 最多跟踪 100,000 个唯一 IP 地址。当达到此上限时,会先清除已过期的条目,再添加新条目。如果你观察到攻击者快速轮换 IP 导致内存增长,自动清理机制会将影响限制在有限的内存范围内。
Docker 示例
services:
app:
image: ghcr.io/oxphp/oxphp:0.10.0
ports:
- "8080:80"
environment:
RATE_LIMIT: "100"
RATE_WINDOW_SECONDS: "60"
volumes:
- ./app:/var/www/html:ro最佳实践
- 从保守值开始。 先设置一个较低的限值(例如每分钟 60 个请求),再根据观察到的流量模式逐步提高。放宽限制比从服务器过载中恢复要容易得多。
- 多实例部署使用共享限流器。 OxPHP 的限流器按实例独立。若要在多个实例之间进行协调限流,请在负载均衡器或 API 网关层级实施限流。
- 监控 429 响应比率。 在指标中跟踪被限流请求的占比,以便发现配置错误的阈值或意外的流量激增。
注意事项
- 固定窗口算法。 限流器使用固定窗口计数器,而非滑动窗口。客户端可以在两个窗口的交界处以突发方式发送最多
2x配置限值的请求。 - 仅支持按 IP。 限流以源 IP 地址为键。不支持诸如 API key 或用户 ID 之类的自定义键。
- 内存中的状态。 限流计数器不会在多个 OxPHP 实例之间共享。
- 自动清理。 当跟踪器超过 100,000 个 IP 时会清理已过期的条目,移除所有窗口已过期的条目。