Files
OpenFlare/docs/superpowers/specs/2026-07-20-site-limit-req-design.md
T
ryan 80c47f6ff3 feat(rate-limit): 站点级请求频率限制支持继承与自定义
在站点详情流量限制中配置 limit_req_per_ip;渲染按 effective rate 生成多 limit_req_zone,并以站点+IP 隔离计数。
2026-07-20 15:02:38 +08:00

6.0 KiB
Raw Blame History

站点级访问频率限制设计

日期:2026-07-20
状态:已评审待实现
方案:站点详情 Limits 暴露 limit_req_per_ip;渲染时按 effective rate 生成多 limit_req_zone,并用站点键隔离 IP 计数

背景

全局默认已有:

  • openresty_default_limit_conn_per_server
  • openresty_default_limit_conn_per_ip
  • openresty_default_limit_rate
  • openresty_default_limit_req_per_ip

站点级并发/带宽已在「反代站点详情 → 流量限制」中配置,语义为:空/0 继承、-1 关闭、自定义覆盖。

请求频率(limit_req)后端字段与 merge 已存在,但:

  1. 前端站点详情未暴露 limit_req_per_ip
  2. 渲染侧仅在全局默认非空时输出单一 limit_req_zone ... rate=全局值,站点自定义 rate 无法真正独立生效(nginx 的 rate 写在 zone 上,不能仅靠 location 覆盖)

目标

  1. 在仅站点详情「流量限制」区块配置单 IP 请求频率。
  2. 语义与现有三项一致:空/0 继承全局;-1 关闭;合法 Nr/s / Nr/m 为站点自定义。
  3. 站点自定义 rate 真正按该 rate 生效(A 站 5r/s、B 站 10r/s 互不影响)。
  4. 同 IP 在不同站点的频率配额按站点隔离。
  5. 修改后仍需发布配置版本;Agent 使用与 Server 同源的 render 路径。

非目标

  • 在「安全性 → 限流」页增加按站点列表编辑
  • 新建站点表单中的频率字段
  • 按路径 / URI 差异化频率限制
  • 改变 limit_conn_* / limit_rate 的现有 zone 与合并模型
  • 业务 Zone(顶级域 + 二级域名资源)模型变更

语义

站点字段 limit_req_per_ip(字符串)

值 含义
空 / "0" 继承全局 openresty_default_limit_req_per_ip
"-1" 本站显式关闭频率限制
^\d+r/[sm]$(大小写不敏感,存小写) 本站自定义 rate

全局默认

值 含义
空 / "0" 默认关闭;继承方亦不输出 limit_req
合法 rate 未配置站点的 effective rate

合并(与现有 mergeLimitRate 一致)

if route == -1:          effective = off
else if route is set:    effective = route   // 合法 rate
else:                    effective = global  // route 空/0
// global 空/0 → off

渲染

问题

nginx limit_req_zone 的 rate= 在 zone 声明时固定;多个站点若 effective rate 不同,必须使用不同 zone。

步骤

  1. 在 RenderRouteConfig / main 配置生成前,对全部 route 计算 effective LimitReqPerIP。
  2. 收集非空 effective rate 的去重集合,在 http {}(renderOpenRestyLimitZoneBlock 扩展,需能访问 routes 或 precomputed rates)输出:
# 变量键:站点名 + IP,保证跨站点计数隔离
# 实现可用 map 或在 server 内 set 后引用;zone key 采用组合键
limit_req_zone $openflare_req_key zone=openflare_req_<rate_token>:10m rate=<rate>;

rate_token 由 rate 规范化生成(如 10r/s → 10rs,100r/m → 100rm),仅作 zone 名片段,合法 nginx zone 名。

  1. 每个业务 server 在 access 相关位置之前设置:
set $openflare_req_key "$openflare_waf_site$binary_remote_addr";

(与现有 set $openflare_waf_site "..." 同源 site_name;若某 server 无 waf site 变量则用同一 displayName/site_name。)

  1. renderRouteLimitBlock 在 effective rate 非空时输出:
limit_req zone=openflare_req_<rate_token> burst=<calculateBurst> nodelay;
limit_req_status 429;
  1. 无任何 effective rate 时:不输出任何 limit_req_zone / limit_req(避免引用不存在的 zone)。

  2. 应用范围与现有 limit 块一致:HTTP/HTTPS 反代 location /、Pages 相关 location;不含 HTTP→HTTPS 重定向-only server。

与旧行为差异

项 旧 新
zone 数量 全局最多 1 个 按不同 effective rate 多个
zone key $binary_remote_addr $openflare_req_key(站点+IP)
站点自定义 rate 无法真正独立 引用对应 rate 的 zone

快照 JSON 仍保留站点原始值(含空/-1),不把 merge 结果写回 route。

数据与 API

  • 列 of_proxy_routes.limit_req_per_ip 已存在;无新迁移(若环境已跑过既有迁移)。
  • API Input / View 已有字段;normalize / 校验已存在。
  • 前端类型与详情表单补齐即可。

前端

仅改站点详情 limits-section.tsx:

  • 增加「单 IP 请求频率」输入
  • 校验:空、0、-1、或 ^\d+r/[sm]$i
  • 规范化:trim + lower;0 → ""
  • ProxyRouteItem / ProxyRouteMutationPayload 增加 limit_req_per_ip
  • buildPayloadFromRoute 带上该字段,避免其它区块保存时丢失

文案:与并发/带宽一致(空或 0 继承;-1 关闭;例如 10r/s、100r/m 自定义)。

Agent / 发布

  • 配置保存后须发布配置版本
  • Agent 本地 RenderJSON;必须部署含本设计 render 的 Agent,否则 source 有字段但 conf 无指令
  • 若 Agent 已记录同 version/checksum,升级二进制后需触发重新 apply(重启或强制重同步)

测试

  • mergeRouteLimitConfig:继承 / 覆盖 / -1(已有则补 rate 断言)
  • 多站点不同 effective rate:main conf 含多个 limit_req_zone,各 location 引用正确 zone 名
  • 全关闭:无 limit_req 相关指令
  • 仅全局有值:一个 zone + 未自定义站点引用该 zone
  • 前端类型与表单校验(手工或既有模式)

验收

  1. 全局 10r/s,站点空 → 该站 location 有 limit_req,zone rate=10r/s
  2. 站点改 5r/s 并发布 → 该站引用 5r/s zone
  3. 站点 -1 → 该站无 limit_req
  4. 两站不同 rate,同 IP 压测互不抢同一配额

实现边界

层 工作量
渲染 pkg/render/openresty 多 zone + 站点键 + location 引用
前端详情 Limits + types + payload 补字段
后端 API/DB 已具备,仅回归
文档/changelog 用户可见变更记中文 changelog