4.2 限流与访问控制:挡住深夜的异常流量


4.2 限流与访问控制:挡住深夜的异常流量

本节摘要:limit_req 按请求速率漏桶限流,limit_conn 按并发连接数限流,配合 key 的选择与状态码区分,构成入口层的第一道防线。限流的核心不是指令而是"以什么为键、给谁留余量"。

深夜告警

凌晨两点,网关 QPS 从 800 涨到 12000,全部打向同一个商品详情接口。日志里 UA 五花八门,但 IP 高度集中在某几个 /24 网段——脚本抢购。应用层限流来不及生效(线程池先满了),只能靠 Nginx 在门口解决。

漏桶限流:limit_req

http { # 定义速率:10r/s 即每 100ms 一个令牌,burst 是突发桶深 limit_req_zone $binary_remote_addr zone=perip:20m rate=10r/s; limit_req_zone $http_x_appid zone=perapp:10m rate=200r/s; server { location /api/ { limit_req zone=perip burst=20 nodelay; limit_req_status 429; # 默认503,改成429更语义化 proxy_pass http://app_backend; } location /search/ { limit_req zone=perapp burst=100 nodelay; # 内部服务用应用ID限 proxy_pass http://app_backend; } } }

三个关键理解:

  • burst 是缓冲队列,nodelay 决定排队还是拒绝。不加 nodelay 时突发请求按速率匀速放行(请求变慢);加 nodelay 则突发立即通过、超出桶深直接 429(请求失败)。抢购场景要 nodelay,宁拒不等。
  • 键的选择决定限谁的流。per-IP 挡外部脚本,但出口 NAT 后一片用户共享 IP 会误伤;对可信内部调用用应用 ID 头做键更准。
  • zone 大小按 IP 数估:每条状态约 64 字节,20m 约存 30 万 IP,超出后最旧的被淘汰。

并发连接限流:limit_conn

速率之外还有维度——单 IP 同时挂多少条连接:

limit_conn_zone $binary_remote_addr zone=connperip:10m; limit_conn_zone $server_name zone=connperserver:10m; server { limit_conn connperip 20; # 单 IP 最多 20 并发连接 limit_conn connperserver 50000; # 整机兜底上限 limit_conn_status 429; }

大文件下载、长轮询这类"连接久占"的业务,速率限流看不出异常,limit_conn 才是对症的。

图:漏桶算法的工作方式

图:漏桶算法的工作方式

访问控制:黑白与地理

location /admin/ { allow 10.0.0.0/8; # 内网 allow 203.0.113.5; # 运维出口 deny all; proxy_pass http://admin_backend; }

规则自上而下首条命中生效,deny all 收尾。管理后台、健康检查端点是必须套 allow/deny 的地方——"没人知道 URL"从来不是安全策略。

处置那次深夜事件

对脚本抢购的最终处置是组合拳:per-IP 10r/s + burst 20 挡外层;对触发 429 超 100 次的网段在防火墙加临时黑名单(用 awk 从日志提取 IP 后批量封);接口侧再按用户 ID 加一层应用限流。三层各自独立生效,Nginx 层保住了应用不死,应用层保住了业务规则公平。

验证限流真的在工作

限流配置最危险的状态是"以为配了但没生效"。上线后用压测工具验证一轮,观察 429 的出现节奏是否符合参数设定:

# 100 个请求快速发出,per-ip 10r/s burst=20 nodelay for i in $(seq 1 100); do curl -s -o /dev/null -w "%{http_code}\n" https://shop.example.com/api/items done | sort | uniq -c # 期望:200 约 21 个(burst 20 + 当前令牌 1),429 约 79 个

数字对不上时按序排查:limit_req 写在了哪个层级(server 与 location 都写时取最近者);zone 名字是否与定义一致(拼错不报错,规则不生效);前面是否还有 CDN 把真实 IP 转成了少数几个回源 IP——这种情况下 per-IP 限的是 CDN 节点,误伤全体用户,必须改用真实客户端 IP 头做键并限制该头只信任 CDN 来源。

限流键的进阶:从 IP 到业务身份

per-IP 是起点,成熟方案要落到业务身份。电商场景的三级键设计:匿名用户按 IP 限 10r/s;登录用户按用户 ID 头限 30r/s(该头由内网网关注入,外不可伪造);开放平台按 appid 限合同额度。用 map 组合出统一键:

# 内网网关注入的 X-User-Id 优先,否则退回 IP map $http_x_user_id $limit_key { default $http_x_user_id; "" $binary_remote_addr; } limit_req_zone $limit_key zone=byuser:20m rate=30r/s;

键的信任链是这套设计的命门:用户 ID 头必须在入口剥离客户端同名头、由可信层重新注入,否则限流键本身就能被伪造,整套防线作废。

限流规则的治理收尾:每条规则上线时登记"归属人与下线条件"。限流是典型的"加容易减难"——没人敢删一条三年前加的规则,因为没人知道它挡着什么。半年一次的规则盘点里,逐条验证"这条还在挡东西吗、挡的是预期的对象吗",把僵尸规则清掉。限流体系与缓存体系一样,不治理就会从防线变成不可理解的历史沉积,最终在某个流量形态变化后突然误伤。

本节要点回顾

  • 限流三问:限谁(键)、多严(rate 与 burst)、拒绝成什么样(状态码);
  • nodelay 决定突发是排队还是被拒,抢购类场景宁拒不排;
  • limit_conn 管连接久占,与速率限流互补;
  • 管理面必须 allow/deny,地理封禁见下节第三方模块。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U