网站出现429 Too Many Requests怎么办?限流与访问频率排查

用户反馈"页面突然打不开了,提示 429",或者你自己的监控发现接口大面积返回 429 Too Many Requests。这个状态码的意思是:你在单位时间内请求得太频繁,被服务器或中间网关限流了。429 本身不是故障,它是限流机制在正常工作——问题往往在于"限制得太严,误伤了正常用户",或者"有人真在刷接口,需要区分对待"。

要处理 429,得先搞清楚是谁在限流:Nginx 自己的 limit_req/limit_conn、云 WAF、API 网关,还是应用代码里的限流器。不同层的触发原因和调整方式完全不同。这篇文章讲清楚 429 的触发机制、Nginx 限流配置怎么写、云 WAF 和 API 网关怎么排查、Retry-After 头的作用,以及怎么设置才不至于误伤正常访问。

一、429 是什么,什么时候会触发

429 由 RFC 6585 定义,表示"请求过多",通常伴随一个 Retry-After 响应头,告诉客户端"过多少秒后再试"。触发 429 的典型场景:

  • 接口防刷:限制单个 IP 或单个用户在一段时间内的请求次数。
  • 防 CC 攻击:源站或 WAF 对高频请求做清洗。
  • 保护后端:后端处理能力有限,用限流把超过能力的请求挡在门外。
  • 第三方 API 配额:调用外部接口超出了配额,对方返回 429。

区分点是"限制的粒度":按 IP、按用户、按接口、还是按 API Key。定位 429 要先知道被限的是哪个维度,才能判断是否合理。

一个关键的排查思路:看触发 429 的请求有什么共同点。是不是都来自同一个 IP、同一个 UA、同一个接口?正常用户分布的请求被限,说明阈值太低;集中在少数来源,说明可能是攻击或爬虫。

二、用访问日志确认是不是限流

先从日志确认 429 的范围和来源。

# 统计 429 请求的数量与路径
sudo awk '$9 == 429 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head

# 看 429 都是哪些 IP 触发的(判断是否集中在少数来源)
sudo awk '$9 == 429 {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head

# 看这些请求的 UA,区分浏览器和脚本
sudo awk '$9 == 429' /var/log/nginx/access.log | tail -20

如果 429 集中在少数 IP 且 UA 是脚本特征,多半是爬虫或攻击被正常限流挡下;如果 429 分布在大量不同 IP、UA 是正常浏览器,多半是阈值设置过严,误伤了真实用户。Nginx 的限流日志在 error.log 里会有 limiting requests 或 limiting connections 的关键字:

sudo grep -i -E 'limiting (requests|connections)' /var/log/nginx/error.log | tail -20

三、Nginx 的 limit_req 与 limit_conn

Nginx 有两套限流指令:limit_req 按请求速率限流,limit_conn 按并发连接数限流。

limit_req 的核心是先定义一个共享内存区(zone),记录每个 key 的请求速率,再在 location 里引用。

# http 段:定义限流区
# 语法:limit_req_zone $key zone=名称:大小 rate=速率;
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=10r/s;

server {
    location /api/ {
        # 引用限流区,burst 允许突发的额外请求数,nodelay 表示突发请求立即处理不排队
        limit_req zone=req_per_ip burst=20 nodelay;
        limit_req_status 429;          # 被限时返回的状态码,默认 503
        proxy_pass http://backend;
    }
}

几个参数要理解透:

参数 含义 建议
$binary_remote_addr 以客户端 IP 为限流 key(二进制形式更省内存) 按 IP 限流常用
zone=名称:大小 共享内存区,10m 大约能存 16 万个 IP 状态 视 IP 数量调整
rate=10r/s 平均速率,每秒 10 个请求 按接口能力设
burst=20 允许瞬时突发额外 20 个请求排队 给正常用户留缓冲
nodelay 突发请求立即处理,不按速率排队 体验更好,但更耗后端
limit_req_status 触发限流时返回的状态码 设 429 更规范

这里有个常见误区:不加 burst,只要超过 rate 就立刻 429,正常用户打开一个页面并发几个请求就可能被误伤。加上合理的 burst 和 nodelay,能容忍正常突发,只在真正高频时才拦。

如果按用户维度限流(比如按登录用户的 ID),key 可以换成变量:

# 示例:优先按用户标识限流,访问受限时以 cookie 或 header 里的用户标识为 key
limit_req_zone $http_x_user_id zone=req_per_user:10m rate=20r/s;

limit_conn 限制单个 key 的并发连接数,防止一个来源占满连接:

limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;

server {
    location /download/ {
        limit_conn conn_per_ip 10;     # 单 IP 最多 10 个并发连接
        limit_conn_status 429;
    }
}

配置完成后测试并重载:

sudo nginx -t && sudo systemctl reload nginx

四、云 WAF、API 网关与应用层限流

如果 Nginx 日志里没有 limiting 关键字,但用户确实收到 429,那限流可能发生在更上层或更深层。

云 WAF / CDN:很多云厂商的 WAF 提供 CC 防护和频率限制,触发后返回 429 或自定义拦截页。这类限流在服务器本地日志里看不到,必须到云控制台查看"防护日志""拦截事件"。常见触发条件包括:单 IP 高频访问、命中恶意特征、地域限制。

API 网关:如果请求经过 API 网关(如 Nginx 之外的网关服务、Kong、企业自研网关),网关层通常有独立的限流策略,按 API Key、路由、消费者维度配置,返回 429 时也会带 Retry-After。排查要去网关的管理界面看限流命中记录。

应用层限流:后端代码里也可能有限流器(比如令牌桶、漏桶、滑动窗口算法)。这类限流在应用日志里记录,报错往往伴随业务上下文。例如:

# 应用日志里搜索限流相关关键字
sudo grep -iE 'rate limit|too many|throttl' /var/log/app/app.log | tail

定位顺序建议:先看是不是 Nginx,再看是不是 WAF/网关,最后看应用层。前提是能拿到各层的日志或控制台访问权限。

五、Retry-After 头与友好处理

规范的 429 响应应该带上 Retry-After,告诉客户端多久后可以重试。这对 API 客户端和爬虫很重要——没有它,客户端可能立刻重试,反而加重限流压力。

# Nginx 触发限流时可以附加 Retry-After
location /api/ {
    limit_req zone=req_per_ip burst=20 nodelay;
    limit_req_status 429;
    add_header Retry-After 5 always;
}

对外提供服务时,还要考虑用户体验:直接返回裸 429 会让用户困惑,可以配一个友好的错误页,说明"访问过于频繁,请稍后再试"。

# 自定义 429 页面(可选)
error_page 429 /429.html;
location = /429.html {
    root /var/www/errors;
    internal;
}

对于搜索引擎爬虫,如果被 429 挡住,会降低抓取频率,长期可能影响收录。所以对爬虫应尽量放行或有独立的宽松策略,别让防刷规则误伤(这一点和第 067 篇呼应)。

六、怎么设置才不误伤正常用户

限流是"防坏人"和"别挡好人"之间的平衡。几个实用原则:

  • 阈值要有数据支撑。先统计正常用户的峰值请求速率(看访问日志的 P95/P99),把阈值设在峰值之上留出余量,而不是拍脑袋定个很低的值。
  • 善用 burst。给正常突发留缓冲,避免页面并发加载被误伤。
  • 区分维度和粒度。静态资源、接口、上传等可以有不同策略;登录用户和匿名 IP 分开限。
  • 白名单。公司出口 IP、监控系统、搜索引擎爬虫、合作方接口加白名单,避免误伤。
  • 可观测。对 429 数量做监控和告警,一旦异常升高立刻能发现是攻击还是配置问题。
# 白名单示例:监控和搜索引擎爬虫不限流(通过 map 生成限流 key)
map $http_user_agent $limit_key {
    default                 $binary_remote_addr;
    ~*Googlebot             "";      # 空 key 表示不限流
    ~*bingbot               "";
    ""                      $binary_remote_addr;
}
limit_req_zone $limit_key zone=req_dynamic:10m rate=10r/s;

# 公司出口 IP 白名单
geo $limit_ip {
    default        1;
    203.0.113.0/24 0;    # 0 表示不限流
}

风险提示:白名单里的 IP 段要核对准确,把不受限的 IP 段写得过宽会被滥用;限流阈值调低或临时加严都可能误伤真实用户,改动前记录原值、低峰期操作并持续观察 429 比例。

用数据反推合理阈值

阈值不该靠猜。做法是先统计正常流量的实际速率,再把阈值定在正常峰值之上。可以从访问日志按秒级或分钟级聚合每个 IP 的请求数,找出正常用户的最高值,再留出两到三倍余量。

# 统计单个 IP 每分钟的请求峰值分布(辅助定阈值)
sudo awk '{print $1, substr($4,2,17)}' /var/log/nginx/access.log \
  | sort | uniq -c | sort -rn | head -20

如果发现正常用户峰值大约每分钟 30 次,那么 rate=10r/s(约每分钟 600 次)配 burst=20 就相当宽松,不会误伤;反过来,如果阈值定成 rate=1r/s 又没给 burst,正常的页面加载和接口并发就会被打成 429。合理的做法是"正常用户感觉不到限流,异常高频立刻被拦"。同时要把阈值和告警挂钩:如果 429 数量突然飙升,先判断是攻击还是配置问题,再决定是调高阈值还是加强封禁。

常见问题

Q:429 和 503 有什么区别?
503 是"服务暂时不可用"(过载或维护),429 更强调"你请求太多了"。Nginx 限流默认返回 503,建议用 limit_req_status 429 改成更准确的 429。

Q:Nginx 日志里没有 limiting 关键字,但用户收到 429?
说明限流不在 Nginx。查云 WAF/CDN 的拦截日志、API 网关的限流记录,或应用层限流器的日志。

Q:怎么区分是被攻击还是阈值太低?
看 429 的来源分布。集中在少数 IP/UA 是攻击或被刷;分散在大量正常 UA 是阈值过严,需要提高 rate 或加大 burst。

Q:加了 Retry-After 有什么用?
告诉客户端多久后重试,避免客户端立刻重试导致雪崩。对 API 客户端和爬虫尤其重要。

Q:限流会不会影响搜索引擎收录?
会。爬虫被 429 挡住会降低抓取频率。建议对正规爬虫放行或有独立宽松策略,并监控爬虫拿到的 429 数量。

总结

429 表示请求频率超限,是限流机制在工作,处理重点是分清"阈值过严误伤"还是"有人真在刷"。

排查从访问日志入手,看 429 集中在哪些 IP、路径、UA,再看 Nginx error.log 有没有 limiting 关键字。

Nginx 用 limit_req(按速率)和 limit_conn(按并发)限流,limit_req_zone 定义共享区,务必配合理的 burst 避免误伤,并把状态码设成 429。

如果 Nginx 没限流,往上看云 WAF/CDN 和 API 网关,往里看应用层限流器,逐层定位。

配置要用真实数据支撑阈值、给白名单放行监控和爬虫、带 Retry-After 头、对 429 做监控告警,改动前备份并在低峰期调整。