限流

OxPHP 内置了按 IP 的限流器,因此无需运行任何外部依赖或基础设施。启用后,它会按客户端 IP 跟踪请求数量,并在客户端超过配置的阈值时返回 429 Too Many Requests 响应。

工作原理

限流器使用以客户端 IP 地址为键的固定窗口计数器。每个 IP 拥有各自独立的计数器和窗口。

  1. 当请求到达时,OxPHP 会在其内部跟踪器中查找该客户端 IP。
  2. 如果不存在对应条目,或当前窗口已过期,就会开启一个新窗口,计数器归零。
  3. 每个请求都会使计数器递增。
  4. 如果计数器超过 RATE_LIMIT,服务器会立即返回带有限流响应头的 429 响应。请求会在路由或 PHP 执行之前被拒绝。

被限流的请求仍会出现在访问日志和指标中。

配置

变量 默认值 说明
RATE_LIMIT 0 每个 IP 在窗口内允许的最大请求数。0 表示完全禁用限流,且零开销
RATE_WINDOW_SECONDS 60 限流窗口的时长,单位为秒
bash
# 每个 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
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_LIMITRATE_WINDOW_SECONDS

检查被限流的请求数量:

bash
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 示例

compose.yaml
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 时会清理已过期的条目,移除所有窗口已过期的条目。

另请参阅

  • 指标 — 通过 Prometheus 监控被限流的请求数量
  • 请求 ID — 在日志中关联被限流的请求
  • 访问日志 — 429 响应会出现在访问日志中
  • 配置参考 — 环境变量的完整列表