Nginx限流实战:如何优雅地防御CC攻击和突发流量

admin 2026-06-30 09:18:07 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细讲解Nginx限流实战,重点介绍使用limitreqzone、limitconnzone和limit_req三个核心指令防御CC攻击和突发流量。文章涵盖漏桶算法原理、burst/nodelay参数区别、key选择策略,并提供按IP限速、多级接口限流等可直接落地的配置示例和压测方法,帮助运维人员快速构建生产级限流防护体系。 综合评分: 85 文章分类: WEB安全,安全工具,实战经验,解决方案,安全运营


cover_image

Nginx 限流实战:如何优雅地防御 CC 攻击和突发流量

点击关注 👉 点击关注 👉

马哥Linux运维

2026年6月28日 13:00 广东

在小说阅读器读本章

去阅读

Nginx 限流实战:如何优雅地防御 CC 攻击和突发流量

一、问题背景

Nginx 是绝大多数互联网公司的接入层首选,但它默认没有任何限流能力。一旦面对 CC 攻击、突发流量、爬虫狂扫,CPU 飙升、连接数打满、带宽耗尽几乎是必然的。

实际生产中常见的”被打挂”场景大致是这样:

  • 凌晨 2 点告警群突然@全员:某个 URI 的 QPS 突增 100 倍
  • 业务方做了个限时活动,10 万用户同时点按钮
  • 黑产用脚本扫登录接口、撞验证码接口
  • 竞争对手爬虫把首页和列表页全量抓一遍
  • 某个 SDK 版本有 bug,客户端疯狂重试

这些场景下,Nginx 如果没有限流,最直接的后果就是:CPU 100% → worker 卡死 → upstream 503 → 全站雪崩。靠防火墙挡 IP 是事后补救,靠 WAF 是另一条产品线,而 Nginx 自带的限流模块在大多数中小规模场景下就够了。

Nginx 限流的核心能力来自三个指令:limit_req_zonelimit_conn_zonelimit_req。再配合 geo 模块做黑白名单、map 做请求识别、OpenResty 做复杂逻辑,就能搭建一套相对完整的接入层限流体系。

这一篇不讲 RFC 文档的抽象概念,直接站在运维视角,把限流配置、压测、监控、回滚讲清楚。

写作目标:

  • 让初中级运维能够独立配置一套生产级限流
  • 让读者在被 CC 攻击时 10 分钟内上线防护
  • 让读者能够根据业务场景调优限流参数
  • 给出可直接落地的配置、压测命令、监控告警

二、适用场景

  • 接入层 CC 攻击防护
  • 突发流量削峰(限时活动、秒杀、抢购)
  • 爬虫与恶意请求拦截
  • 单 IP / 单用户 / 单接口限速
  • 接口级精细化限流(登录、注册、下单、支付)
  • API 网关层限流
  • 后端微服务限流
  • CDN 回源限流
  • 灰度发布期间限流
  • 客户端 SDK 异常重试防护
  • 数据库写操作限流
  • 短信、邮件发送限流
  • 验证码、Token 颁发限流
  • 文件下载带宽限流
  • 移动端弱网限流
  • 跨机房流量整形

三、核心知识点

3.1 Nginx 限流三剑客

3.1.1 limit_req_zone

请求速率限流,按”在单位时间窗口内允许的请求数”控制。

语法:

nginx

limit_req_zone $key zone=name:size rate=rate;
  • $key

    :限流的 key,常用 $binary_remote_addr(按 IP)、$server_name(按域名)、自定义变量

  • zone

    :共享内存区域,name:size 格式

  • rate

    :限流速率,如 10r/s(每秒 10 个请求)、60r/m(每分钟 60 个)

底层算法:leaky bucket(漏桶),令牌按固定速率填充,请求消耗令牌,没有令牌就拒绝或排队。

3.1.2 limit_req

在具体的 location 或 server 中应用限流规则。

语法:

nginx

limit_req zone=name [burst=number] [nodelay | delay=number];
  • zone

    :对应 limit_req_zone 的名称

  • burst

    :允许的突发请求数,超过 rate 的请求会放到队列

  • nodelay

    :超过 rate 的请求立即返回,不排队

  • delay

    :超过 burst 的请求延迟处理

注意:burst 不是”突发流量总容量”,而是”排队容量”。

3.1.3 limit_conn_zone

连接数限流,控制单个 key 的并发连接数。

语法:

nginx

limit_conn_zone $key zone=name:size;
nginx

limit_conn zone_name number;
  • 控制同时活跃的连接数
  • 适合限制长连接、慢请求

3.1.4 三个状态码指令

nginx

limit_req_status 429;
limit_conn_status 429;
  • 默认 503,可以改成 429
  • 客户端能识别 429 后做退避

3.1.5 日志级别

nginx

limit_req_log_level warn;
limit_conn_log_level warn;
  • 控制限流事件的日志输出级别
  • 默认 error,生产环境建议 warn

3.2 漏桶算法

Nginx 的 limit_req 实现是漏桶算法

令牌按 rate 速率匀速填充到桶里
桶容量 = burst
请求到达时消耗一个令牌
桶空则请求被拒绝(或排队)

举例:

  • rate=10r/s burst=20 nodelay
  • 桶里平时有 0 个令牌
  • 突发 20 个请求全部通过
  • 之后每个新请求要等 100ms 才有令牌
  • 第 21 个以后的请求立即被拒绝

与令牌桶的区别:

  • 漏桶:输出速率恒定(r),适合削峰
  • 令牌桶:允许一定突发,适合允许抖动的场景

3.3 burst / nodelay / delay 的区别

| 参数 | 行为 | 适用场景 | | — | — | — | | 无 burst | 严格按 rate,超出立即拒绝 | 严格限流 | | burst=N | 允许 N 个请求排队等待 | 允许抖动 | | burst=N nodelay | N 个以内立即通过,之外立即拒绝 | 允许突增但不排队 | | burst=N delay=M | 排 M 个以内立即通过,剩余排队 | 折中 |

举例说明:

nginx

# 配置 1:严格限流
limit_req zone=api_limit burst=0;
# 行为:超过 1r/s 立即 503

# 配置 2:允许 20 个突发
limit_req zone=api_limit burst=20;
# 行为:超过 1r/s 的请求先排队,排队满 20 个就 503

# 配置 3:允许 20 个突发,但不排队
limit_req zone=api_limit burst=20 nodelay;
# 行为:超过 1r/s 的请求,20 个以内立即通过,超过 20 个立即 503

# 配置 4:折中
limit_req zone=api_limit burst=20 delay=10;
# 行为:超过 1r/s 的请求,前 10 个立即通过,10-20 之间排队,20 之外 503

3.4 key 的选择

$binary_remote_addr 是最常用的 key(按 IP 限流),但实际场景可能更复杂:

| 场景 | key | 备注 | | — | — | — | | 按 IP | $binary_remote_addr | 最常用,节省内存 | | 按 IP + URL | binary_remote_addr$request_uri | 防止单 IP 扫多个 URI | | 按用户 | $http_authorization 中的用户 ID | 登录后用 | | 按域名 | $server_name | 防止某个域名被打 | | 按接口 | $request_uri | 防止单 URI 被刷 | | 按 IP+UA | 自定义组合 | 防止代理 IP 轮换 |

注意:

  • 共享内存区域大小与 key 数量相关
  • 1MB 共享内存大约能存 16K 个 IP
  • key 数量超出 zone 大小会按 LRU 淘汰

3.5 常见误区

误区一:限流配了就有用。

错。如果 rate 太大、burst 太大,限流根本起不到作用。要按真实业务量压测后调整。

误区二:限流不丢请求。

错。超过 rate 的请求必然被拒绝(除非 burst 排队)。要做好客户端的 429 重试和退避。

误区三:限流一次到位。

错。限流参数要按业务增长持续调整,突发流量是动态的。

误区四:所有接口都限流。

错。读多写少、缓存命中率高的接口,限流优先级低;写多、消耗资源的接口,限流优先级高。

误区五:限流能挡住所有攻击。

错。分布式攻击(不同 IP)需要配合 WAF、验证码、IP 信誉库。

四、整体排查与实施思路

4.1 限流实施的五个层次

1. 客户端限流(SDK 重试退避、按钮防抖)
2. 接入层限流(Nginx / CDN / WAF)
3. 应用层限流(Sentinel / Guava RateLimiter / 自研)
4. 中间件限流(Redis 令牌桶 / MQ 限流)
5. 数据库限流(连接池 / 慢 SQL 拦截)

每一层都要做。

4.2 限流参数的设计步骤

  1. 业务基线评估

    :通过监控拿到正常时期的 QPS

  2. 峰值评估

    :按业务量 + 3-5 倍冗余估算峰值

  3. 突发评估

    :按 1.5-2 倍峰值估算突发

  4. 分接口评估

    :登录、下单、查询接口分开评估

  5. 分用户评估

    :VIP 和普通用户分开评估

4.3 限流的实施顺序

  1. 先评估

    :用监控、日志拿到真实数据

  2. 再压测

    :用 wrk / ab / vegeta 压测

  3. 后配置

    :按压测结果配 limit_req

  4. 要灰度

    :先在部分节点上线

  5. 再观察

    :看 429 比例、客户端影响

  6. 最后全量

    :扩大到所有节点

4.4 限流异常的排查步骤

1. 429 比例突增 → 检查 limit_req 配置、burst 设置
2. 误伤正常用户 → 检查 key 选择、rate 是否过严
3. 上游 QPS 没降 → 检查 limit_req 是否真的应用
4. 客户端重试风暴 → 客户端要做指数退避
5. 内存溢出 → 检查 zone 大小是否足够

五、实战步骤

5.1 基础限流配置:按 IP 限速

第一步:在 http 块中定义限流 zone。

nginx

# /etc/nginx/nginx.conf
http {
    # 按 IP 限流:每秒 10 个请求
    limit_req_zone$binary_remote_addr zone=ip_limit:10m rate=10r/s;

    # 限制连接数:每个 IP 最多 100 个连接
    limit_conn_zone$binary_remote_addr zone=conn_limit:10m;

    # 返回 429 而不是 503
    limit_req_status429;
    limit_conn_status429;

    # 限流日志级别
    limit_req_log_levelwarn;
    limit_conn_log_levelwarn;

    server {
        listen80;
        server_name api.example.com;

        location / {
            # 应用限流:允许 20 个突发,不排队
            limit_req zone=ip_limit burst=20 nodelay;
            limit_conn conn_limit 100;

            proxy_pass http://backend;
        }
    }
}

第二步:测试配置并 reload。

bash

nginx -t
nginx -s reload

第三步:压测验证。

bash

# 用 ab 压测 60 秒 100 并发
ab -n 6000 -c 100 'http://api.example.com/api/test'

# 看响应码分布
# 大部分应该是 200,少量 429

5.2 多级限流:按接口分别限流

nginx

http {
    # 登录接口:每 IP 每秒 1 个
    limit_req_zone$binary_remote_addr zone=login_limit:10m rate=1r/s;

    # 注册接口:每 IP 每秒 1 个
    limit_req_zone$binary_remote_addr zone=register_limit:10m rate=1r/s;

    # 查询接口:每 IP 每秒 50 个
    limit_req_zone$binary_remote_addr zone=query_limit:10m rate=50r/s;

    # 下单接口:每 IP 每秒 5 个
    limit_req_zone$binary_remote_addr zone=order_limit:10m rate=5r/s;

    # 全局兜底:每 IP 每秒 100 个
    limit_req_zone$binary_remote_addr zone=global_limit:10m rate=100r/s;

    server {
        listen80;
        server_name api.example.com;

        # 登录接口
        location /api/login {
            limit_req zone=login_limit burst=5 nodelay;
            proxy_pass http://backend;
        }

        # 注册接口
        location /api/register {
            limit_req zone=register_limit burst=3 nodelay;
            proxy_pass http://backend;
        }

        # 查询接口
        location /api/query {
            limit_req zone=query_limit burst=100 nodelay;
            proxy_pass http://backend;
        }

        # 下单接口
        location /api/order {
            limit_req zone=order_limit burst=10 nodelay;
            proxy_pass http://backend;
        }

        # 默认:全局限流
        location / {
            limit_req zone=global_limit burst=200 nodelay;
            proxy_pass http://backend;
        }
    }
}

5.3 多维度限流:按 IP + URI 限流

nginx

http {
    # 按 IP + URI 限流:每个 IP 对每个 URI 每秒 5 个
    limit_req_zone $binary_remote_addr$request_uri zone=ip_uri_limit:20m rate=5r/s;

    server {
        location / {
            limit_req zone=ip_uri_limit burst=10 nodelay;
            proxy_pass http://backend;
        }
    }
}

注意:

  • zone

    占用内存 = 16K × key 数量

  • IP+URI 组合会让 key 数量激增,zone 要相应调大

  • 20MB 共享内存大约能存 320K 个 IP+URI 组合

5.4 限流 + 白名单(IP 不限流)

nginx

http {
    # 定义白名单:内网和办公网不限流
    geo$limit_ip {
        default1;  # 默认需要限流
        10.0.0.0/8 0;        # 内网不限流
        172.16.0.0/12 0;     # 办公网不限流
        192.168.0.0/16 0;    # 办公网不限流
    }

    # key 用空字符串表示不限流
    limit_req_zone$limit_ip zone=ip_limit:10m rate=10r/s;

    map$limit_ip$limit_key {
        default$binary_remote_addr;
        0 "";  # 白名单 key 为空
    }

    server {
        location / {
            limit_req zone=ip_limit burst=20 nodelay;
            proxy_pass http://backend;
        }
    }
}

注意:上面的写法有个小坑,limit_req_zone 的 key 用 $limit_ip,值为 0 时相当于没消耗桶。但更稳的写法是用 map 转 key:

nginx

http {
    geo$is_internal {
        default0;
        10.0.0.0/8 1;
        172.16.0.0/12 1;
        192.168.0.0/16 1;
    }

    map$is_internal$limit_key {
        default$binary_remote_addr;
        1 "";  # 白名单
    }

    limit_req_zone$limit_key zone=ip_limit:10m rate=10r/s;

    server {
        location / {
            limit_req zone=ip_limit burst=20 nodelay;
            proxy_pass http://backend;
        }
    }
}

5.5 限流 + 黑名单(特定 IP 拒绝)

nginx

http {
    geo$is_black {
        default0;
        1.2.3.4 1;        # 单个 IP
        5.6.0.0/16 1;     # IP 段
    }

    server {
        location / {
            if ($is_black) {
                return444;  # 静默断开
            }

            proxy_pass http://backend;
        }
    }
}

5.6 CC 攻击防护:UA 识别 + 限流

nginx

http {
    # 识别常见攻击工具
    map$http_user_agent$is_bot {
        default0;
        ~*sqlmap1;
        ~*nmap1;
        ~*masscan1;
        ~*scrapy1;
        ~*httpclient1;
        ~*java1;
        ~*python-requests1;
    }

    # 没有 UA 字符串的也可能是攻击
    map$http_user_agent$is_no_ua {
        default0;
        "" 1;
    }

    server {
        location / {
            if ($is_bot) {
                return444;
            }

            if ($is_no_ua) {
                return444;
            }

            proxy_pass http://backend;
        }
    }
}

注意:拦截 UA 可能误伤,部分合法客户端会不带 UA。

5.7 突发流量削峰:分阶段限流

nginx

http {
    # 平时:每秒 1000 个
    limit_req_zone$binary_remote_addr zone=normal_limit:10m rate=1000r/s;

    # 限流:限制为平时一半
    limit_req_zone$binary_remote_addr zone=peak_limit:10m rate=500r/s;

    server {
        location / {
            # 默认用 normal
            set$zone_name normal_limit;
            set$zone_burst2000;

            # 每天 9-10 点限流更严
            if ($time_hour = "09") {
                set$zone_name peak_limit;
                set$zone_burst1000;
            }

            limit_req zone=$zone_name burst=$zone_burst nodelay;
            proxy_pass http://backend;
        }
    }
}

注意:if 在 location 中有”邪恶 if”的坑,能用 map 就用 map:

nginx

http {
    # 限流 zone 名称根据时间动态
    map$time_hour$limit_zone {
        default normal_limit;
        "09" peak_limit;
        "10" peak_limit;
        "20" peak_limit;
    }

    limit_req_zone$binary_remote_addr zone=normal_limit:10m rate=1000r/s;
    limit_req_zone$binary_remote_addr zone=peak_limit:10m rate=500r/s;

    server {
        location / {
            limit_req zone=$limit_zone burst=2000 nodelay;
            proxy_pass http://backend;
        }
    }
}

5.8 连接数限流:防慢速攻击

nginx

http {
    limit_conn_zone$binary_remote_addr zone=conn_limit:10m;
    limit_conn_zone$server_name zone=server_conn_limit:10m;

    server {
        location / {
            # 单 IP 最多 10 个连接
            limit_conn conn_limit 10;
            # 单域名最多 1000 个连接
            limit_conn server_conn_limit 1000;

            proxy_pass http://backend;
        }
    }
}

5.9 限流 + 限速(带宽)

Nginx 自带 limit_rate

nginx

server {
    location /download/ {
        # 单连接每秒最多 100KB
        limit_rate 100k;
        # 超过 1MB 之后开始限速
        limit_rate_after 1m;

        root /var/www;
    }
}

5.10 限流日志与监控

nginx

http {
    log_format limit_log '$remote_addr - $remote_user [$time_local] '
                         '"$request" $status $body_bytes_sent '
                         'limit_req_status=$limit_req_status '
                         'limit_conn_status=$limit_conn_status '
                         'rt=$request_time '
                         'uaddr="$upstream_addr" '
                         'ustatus="$upstream_status"';

    access_log /var/log/nginx/limit.log limit_log;

    server {
        location / {
            limit_req zone=ip_limit burst=20 nodelay;
            limit_conn conn_limit 100;

            proxy_pass http://backend;
        }
    }
}

注意:$limit_req_status 是 Nginx 1.17.7+ 引入的变量,编译时需注意版本。

六、常用命令

6.1 Nginx 限流配置相关

bash

# 测试配置
nginx -t

# 平滑 reload
nginx -s reload

# 看配置
nginx -T | grep -E 'limit_req|limit_conn'

# 看错误日志
tail -F /var/log/nginx/error.log | grep -i limit

# 看 access log 中 429
awk '$9 == 429' /var/log/nginx/access.log | wc -l

# 看 429 Top IP
awk '$9 == 429' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

# 看 429 Top URI
awk '$9 == 429' /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20

6.2 压测工具

6.2.1 ab(Apache Bench)

bash

# 安装
apt install apache2-utils
# 或
yum install httpd-tools

# 基本压测:1000 个请求,10 并发
ab -n 1000 -c 10 'http://api.example.com/test'

# 持续压测:60 秒
ab -t 60 -c 100 'http://api.example.com/test'

# 看详细输出
ab -n 1000 -c 10 -v 2 'http://api.example.com/test' 2>&1 | head -50

# 输出示例:
# Concurrency Level:      10
# Time taken for tests:   5.123 seconds
# Complete requests:      1000
# Failed requests:        0
# Total transferred:      123000 bytes
# HTML transferred:       12000 bytes
# Requests per second:    195.20 [#/sec] (mean)
# Time per request:       51.234 [ms] (mean)
# Time per request:       5.123 [ms] (mean, across all concurrent requests)
# Transfer rate:          23.45 [Kbytes/sec] received
# Connection Times (ms)
#               min  mean[+/-sd] median   max
# Connect:        0    1   0.5      1       3
# Processing:    20   50  10.2     48      95
# Waiting:        5   15   8.7     14      35
# Total:         20   51  10.3     49      96
# Percentage of the requests served within a certain time (ms)
#   50%     49
#   66%     55
#   75%     60
#   80%     65
#   90%     75
#   95%     85
#   98%     95
#   99%     99
#  100%     96 (longest request)

6.2.2 wrk

bash

# 安装
apt install wrk
# 或编译安装
git clone https://github.com/wg/wrk.git
cd wrk && make

# 基本压测:4 线程 100 连接 30 秒
wrk -t 4 -c 100 -d 30s 'http://api.example.com/test'

# 看延迟分布
wrk -t 4 -c 100 -d 30s --latency 'http://api.example.com/test'

# 用 Lua 脚本自定义请求
wrk -t 4 -c 100 -d 30s -s post.lua 'http://api.example.com/api/post'

# post.lua
wrk.method = "POST"
wrk.body = '{"key":"value"}'
wrk.headers["Content-Type"] = "application/json"

6.2.3 vegeta

bash

# 安装
apt install vegeta
# 或
go install github.com/tsenart/vegeta@latest

# 创建 targets.txt
echo"GET http://api.example.com/test" > targets.txt

# 压测:每秒 1000 个请求,持续 30 秒
vegeta attack -duration=30s -rate=1000 -targets=targets.txt | vegeta report

# 输出 HTML 报告
vegeta attack -duration=30s -rate=1000 -targets=targets.txt | vegeta report -type=hist[0,50ms,100ms,200ms,500ms,1s,2s,5s]

# 输出文本
vegeta attack -duration=30s -rate=1000 -targets=targets.txt | vegeta report -type=text

6.2.4 hey

bash

# 安装
go install github.com/rakyll/hey@latest

# 压测
hey -n 1000 -c 10 'http://api.example.com/test'

6.3 IP 频次统计

bash

# 统计最近 1 万条日志中 Top 20 IP
awk '{print $1}' /var/log/nginx/access.log | tail -10000 | sort | uniq -c | sort -rn | head -20

# 统计最近 1 小时 Top 20 IP
awk -v cutoff="$(date -d '1 hour ago' '+%d/%b/%Y:%H')" '$4 >= "["cutoff' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

# 找 UA 为空的 IP(可能是攻击)
awk '$14 == "-" || $14 ~ /^- $|^-$/' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

# 找 1 秒内请求超过 100 次的 IP
awk '{print $1, $4}' /var/log/nginx/access.log | \
  awk '{print $1, substr($2, 14, 8)}' | \
  sort | uniq -c | sort -rn | awk '$1 > 100' | head

6.4 防火墙联动(iptables / nftables)

把高频攻击 IP 加入防火墙:

bash

# 收集攻击 IP
ATTACK_IPS=$(awk '{print $1}' /var/log/nginx/access.log | tail -10000 | sort | uniq -c | sort -rn | head -20 | awk '$1 > 1000 {print $2}')

# 加入 iptables 黑名单
for ip in $ATTACK_IPS; do
    iptables -I INPUT -s $ip -j DROP
done

# 保存
iptables-save > /etc/iptables/rules.v4

# 解除黑名单
iptables -D INPUT -s <ip> -j DROP
bash

# nftables 版本
nft add rule inet filter input ip saddr { 1.2.3.4, 5.6.7.8 } drop

注意:

  • iptables 规则太多会影响性能
  • 大量 IP 用 ipset 更高效
bash

# 用 ipset
ipset create blacklist&nbsp;hash:net
ipset add blacklist 1.2.3.4
ipset add blacklist 5.6.0.0/16

# iptables 引用
iptables -I INPUT -m&nbsp;set&nbsp;--match-set blacklist src -j DROP

6.5 临时封禁 IP(CC 应急)

bash

#!/bin/bash
# block_attack_ips.sh
# 用途:扫描最近高频 IP,加入 iptables 黑名单
# 用法:./block_attack_ips.sh /var/log/nginx/access.log 1000

LOG="${1:-/var/log/nginx/access.log}"
THRESHOLD="${2:-1000}"
TIME_RANGE_MIN="${3:-5}"# 最近 5 分钟

# 1. 拿到时间阈值
CUTOFF=$(date&nbsp;-d&nbsp;"$TIME_RANGE_MIN&nbsp;minutes ago"'+%d/%b/%Y:%H:%M')

# 2. 拿到攻击 IP
ATTACK_IPS=$(awk -v cutoff="$CUTOFF"'$4 >= "["cutoff'"$LOG"&nbsp;| \
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;awk&nbsp;'{print $1}'&nbsp;|&nbsp;sort&nbsp;|&nbsp;uniq&nbsp;-c |&nbsp;sort&nbsp;-rn | \
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;awk -v t="$THRESHOLD"'$1 > t {print $2}')

# 3. 加入黑名单
for&nbsp;ip&nbsp;in$ATTACK_IPS;&nbsp;do
&nbsp; &nbsp;&nbsp;if&nbsp;! iptables -C INPUT -s&nbsp;"$ip"&nbsp;-j DROP 2>/dev/null;&nbsp;then
&nbsp; &nbsp; &nbsp; &nbsp; iptables -I INPUT -s&nbsp;"$ip"&nbsp;-j DROP
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;echo"$(date): Blocked&nbsp;$ip"
&nbsp; &nbsp;&nbsp;fi
done

# 4. 保存
iptables-save > /etc/iptables/rules.v4

注意:脚本必须按实际环境调整,特别是 iptables 命令。

七、配置示例

7.1 完整生产限流配置

nginx

# /etc/nginx/nginx.conf
user&nbsp;nginx;
worker_processes&nbsp;auto;
worker_rlimit_nofile65535;
error_log&nbsp;/var/log/nginx/error.log&nbsp;warn;
pid&nbsp;/var/run/nginx.pid;

events&nbsp;{
&nbsp; &nbsp;&nbsp;worker_connections65535;
&nbsp; &nbsp;&nbsp;multi_accepton;
&nbsp; &nbsp;&nbsp;useepoll;
}

http&nbsp;{
&nbsp; &nbsp;&nbsp;include&nbsp; &nbsp; &nbsp; &nbsp;mime.types;
&nbsp; &nbsp;&nbsp;default_type&nbsp; application/octet-stream;

&nbsp; &nbsp;&nbsp;# 自定义日志格式
&nbsp; &nbsp;&nbsp;log_format&nbsp;main_ext&nbsp;'$remote_addr&nbsp;-&nbsp;$remote_user&nbsp;[$time_local] '
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'"$request"&nbsp;$status&nbsp;$body_bytes_sent&nbsp;'
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'"$http_referer" "$http_user_agent" '
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'rt=$request_time&nbsp;uct="$upstream_connect_time" '
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'urt="$upstream_response_time" '
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'uaddr="$upstream_addr" '
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'ustatus="$upstream_status" '
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'limit_req=$limit_req_status&nbsp;'
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'limit_conn=$limit_conn_status';

&nbsp; &nbsp;&nbsp;access_log&nbsp;/var/log/nginx/access.log main_ext;

&nbsp; &nbsp;&nbsp;sendfileon;
&nbsp; &nbsp;&nbsp;tcp_nopushon;
&nbsp; &nbsp;&nbsp;tcp_nodelayon;
&nbsp; &nbsp;&nbsp;keepalive_timeout65;

&nbsp; &nbsp;&nbsp;client_max_body_size20m;
&nbsp; &nbsp;&nbsp;client_body_buffer_size128k;
&nbsp; &nbsp;&nbsp;client_header_buffer_size4k;
&nbsp; &nbsp;&nbsp;large_client_header_buffers416k;

&nbsp; &nbsp;&nbsp;gzipon;
&nbsp; &nbsp;&nbsp;gzip_min_length1k;
&nbsp; &nbsp;&nbsp;gzip_buffers416k;
&nbsp; &nbsp;&nbsp;gzip_comp_level6;
&nbsp; &nbsp;&nbsp;gzip_types&nbsp;text/plain text/css application/json application/javascript text/xml application/xml;

&nbsp; &nbsp;&nbsp;# ================== 限流配置开始 ==================

&nbsp; &nbsp;&nbsp;# 1. IP 黑名单
&nbsp; &nbsp;&nbsp;geo$is_black&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;default0;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 1.2.3.4 1;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 5.6.0.0/16 1;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;# 2. IP 白名单
&nbsp; &nbsp;&nbsp;geo$is_internal&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;default0;
&nbsp; &nbsp; &nbsp; &nbsp; 10.0.0.0/8 1;
&nbsp; &nbsp; &nbsp; &nbsp; 172.16.0.0/12 1;
&nbsp; &nbsp; &nbsp; &nbsp; 192.168.0.0/16 1;
&nbsp; &nbsp; &nbsp; &nbsp; 127.0.0.1 1;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;# 3. 白名单转换 key
&nbsp; &nbsp;&nbsp;map$is_internal$limit_key&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;default$binary_remote_addr;
&nbsp; &nbsp; &nbsp; &nbsp; 1 "";
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;# 4. UA 识别
&nbsp; &nbsp;&nbsp;map$http_user_agent$is_bad_ua&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;default0;
&nbsp; &nbsp; &nbsp; &nbsp; ~*sqlmap1;
&nbsp; &nbsp; &nbsp; &nbsp; ~*nmap1;
&nbsp; &nbsp; &nbsp; &nbsp; ~*masscan1;
&nbsp; &nbsp; &nbsp; &nbsp; ~*scrapy1;
&nbsp; &nbsp; &nbsp; &nbsp; ~*httpclient1;
&nbsp; &nbsp; &nbsp; &nbsp; ~*java1;
&nbsp; &nbsp; &nbsp; &nbsp; ~*python-requests1;
&nbsp; &nbsp; &nbsp; &nbsp; ~*go-http-client1;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;# 5. 限流 zone 定义
&nbsp; &nbsp;&nbsp;# 全局限流:每 IP 每秒 100 个
&nbsp; &nbsp;&nbsp;limit_req_zone$limit_key&nbsp;zone=global_limit:10m&nbsp;rate=100r/s;

&nbsp; &nbsp;&nbsp;# API 限流:每 IP 每秒 50 个
&nbsp; &nbsp;&nbsp;limit_req_zone$limit_key&nbsp;zone=api_limit:10m&nbsp;rate=50r/s;

&nbsp; &nbsp;&nbsp;# 登录限流:每 IP 每秒 1 个
&nbsp; &nbsp;&nbsp;limit_req_zone$limit_key&nbsp;zone=login_limit:5m&nbsp;rate=1r/s;

&nbsp; &nbsp;&nbsp;# 静态资源限流:每 IP 每秒 200 个
&nbsp; &nbsp;&nbsp;limit_req_zone$limit_key&nbsp;zone=static_limit:10m&nbsp;rate=200r/s;

&nbsp; &nbsp;&nbsp;# 6. 连接数限流
&nbsp; &nbsp;&nbsp;limit_conn_zone$limit_key&nbsp;zone=conn_limit:10m;
&nbsp; &nbsp;&nbsp;limit_conn_zone$server_name&nbsp;zone=server_conn_limit:10m;

&nbsp; &nbsp;&nbsp;# 7. 返回 429 而不是 503
&nbsp; &nbsp;&nbsp;limit_req_status429;
&nbsp; &nbsp;&nbsp;limit_conn_status429;
&nbsp; &nbsp;&nbsp;limit_req_log_levelwarn;
&nbsp; &nbsp;&nbsp;limit_conn_log_levelwarn;

&nbsp; &nbsp;&nbsp;# ================== 限流配置结束 ==================

&nbsp; &nbsp;&nbsp;upstream&nbsp;backend {
&nbsp; &nbsp; &nbsp; &nbsp; least_conn;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;server10.0.0.11:8080&nbsp;max_fails=3&nbsp;fail_timeout=30s;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;server10.0.0.12:8080&nbsp;max_fails=3&nbsp;fail_timeout=30s;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;server10.0.0.13:8080&nbsp;max_fails=3&nbsp;fail_timeout=30s;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;keepalive32;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;server&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;listen80;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;server_name&nbsp;api.example.com;

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 健康检查
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;location&nbsp;= /health {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;access_logoff;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return200"ok\n";
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 黑名单
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;($is_black) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return444;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 异常 UA
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;($is_bad_ua) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return444;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 登录接口
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;location&nbsp;/api/login {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;limit_req&nbsp;zone=login_limit burst=5&nbsp;nodelay;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;limit_conn&nbsp;conn_limit&nbsp;10;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_pass&nbsp;http://backend;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_set_header&nbsp;X-Real-IP&nbsp;$remote_addr;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_set_header&nbsp;X-Forwarded-For&nbsp;$proxy_add_x_forwarded_for;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 静态资源
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;location&nbsp;/static/ {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;limit_req&nbsp;zone=static_limit burst=400&nbsp;nodelay;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;alias&nbsp;/var/www/static/;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;expires30d;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;access_logoff;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 默认 API
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;location&nbsp;/ {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;limit_req&nbsp;zone=api_limit burst=100&nbsp;nodelay;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;limit_conn&nbsp;conn_limit&nbsp;50;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_pass&nbsp;http://backend;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_set_header&nbsp;Host&nbsp;$host;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_set_header&nbsp;X-Real-IP&nbsp;$remote_addr;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_set_header&nbsp;X-Forwarded-For&nbsp;$proxy_add_x_forwarded_for;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_set_header&nbsp;X-Forwarded-Proto&nbsp;$scheme;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_http_version1.1;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_set_header&nbsp;Connection&nbsp;"";
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_connect_timeout5s;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_read_timeout60s;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_send_timeout60s;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

7.2 OpenResty + Redis 复杂限流

用 OpenResty + Redis 实现分布式限流(多机共享):

nginx

# /usr/local/openresty/nginx/conf/nginx.conf
http&nbsp;{
&nbsp; &nbsp;&nbsp;lua_shared_dict&nbsp;my_limit&nbsp;10m;

&nbsp; &nbsp;&nbsp;init_worker_by_lua_block&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;local&nbsp;redis = require&nbsp;"resty.redis"
&nbsp; &nbsp; &nbsp; &nbsp; local ok, err = redis.connect("10.0.0.1",&nbsp;6379)
&nbsp; &nbsp; &nbsp; &nbsp; if not ok then
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ngx.log(ngx.ERR,&nbsp;"redis connect error: ", err)
&nbsp; &nbsp; &nbsp; &nbsp; end
&nbsp; &nbsp; }

&nbsp; &nbsp; access_by_lua_block {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;local&nbsp;key =&nbsp;"rate_limit:"&nbsp;.. ngx.var.remote_addr
&nbsp; &nbsp; &nbsp; &nbsp; local now = ngx.now() *&nbsp;1000
&nbsp; &nbsp; &nbsp; &nbsp; local window =&nbsp;60000&nbsp; --&nbsp;1&nbsp;分钟
&nbsp; &nbsp; &nbsp; &nbsp; local limit =&nbsp;100&nbsp; &nbsp; &nbsp;--&nbsp;100&nbsp;个请求

&nbsp; &nbsp; &nbsp; &nbsp; local redis = require&nbsp;"resty.redis"
&nbsp; &nbsp; &nbsp; &nbsp; local ok, err = redis.connect("10.0.0.1",&nbsp;6379)
&nbsp; &nbsp; &nbsp; &nbsp; if not ok then
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; -- Redis 不可用时放行
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return
&nbsp; &nbsp; &nbsp; &nbsp; end

&nbsp; &nbsp; &nbsp; &nbsp; local res, err = redis:eval([[
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; local key = KEYS[1]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; local now = tonumber(ARGV[1])
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; local window = tonumber(ARGV[2])
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; local limit = tonumber(ARGV[3])
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; redis.call('ZADD', key, now, now)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; redis.call('ZREMRANGEBYSCORE', key,&nbsp;0, now - window)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; local count = redis.call('ZCARD', key)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; redis.call('PEXPIRE', key, window)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if count > limit then
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; end
&nbsp; &nbsp; &nbsp; &nbsp; ]],&nbsp;1, key, now, window, limit)

&nbsp; &nbsp; &nbsp; &nbsp; if not res or res ==&nbsp;0&nbsp;then
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ngx.status =&nbsp;429
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ngx.say("rate limit exceeded")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return ngx.exit(429)
&nbsp; &nbsp; &nbsp; &nbsp; end
&nbsp; &nbsp; }

&nbsp; &nbsp; server {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;listen80;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;server_name&nbsp;api.example.com;

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;location&nbsp;/ {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_pass&nbsp;http://backend;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

注意:OpenResty 连接 Redis 必须配置连接池,避免频繁建连。

7.3 Tengine 主动健康检查

Tengine 自带健康检查模块:

nginx

# /etc/nginx/nginx.conf
http&nbsp;{
&nbsp; &nbsp;&nbsp;upstream&nbsp;backend {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;server10.0.0.11:8080;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;server10.0.0.12:8080;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;server10.0.0.13:8080;

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;check&nbsp;interval=3000&nbsp;rise=2&nbsp;fall=5&nbsp;timeout=2000&nbsp;type=http default_down=true;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;check_http_send"GET /health HTTP/1.0\r\n\r\n";
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;check_http_expect_alive&nbsp;http_2xx http_3xx;
&nbsp; &nbsp; }
}

7.4 限流 fallback 页面

nginx

http&nbsp;{
&nbsp; &nbsp;&nbsp;limit_req_zone$binary_remote_addr&nbsp;zone=api_limit:10m&nbsp;rate=10r/s;

&nbsp; &nbsp;&nbsp;server&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;listen80;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;server_name&nbsp;api.example.com;

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 限流错误页
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;error_page429&nbsp;/errors/429.html;

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;location&nbsp;= /errors/429.html {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; internal;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;root&nbsp;/var/www/errors;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;location&nbsp;/ {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;limit_req&nbsp;zone=api_limit burst=20&nbsp;nodelay;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_pass&nbsp;http://backend;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

429.html 内容示例:

html

<!DOCTYPE&nbsp;html>
<html>
<head>
&nbsp; &nbsp;&nbsp;<title>Too Many Requests</title>
&nbsp; &nbsp;&nbsp;<style>
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;body&nbsp;{&nbsp;font-family: Arial, sans-serif;&nbsp;text-align: center;&nbsp;padding:&nbsp;50px; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;h1&nbsp;{&nbsp;color:&nbsp;#d9534f; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;p&nbsp;{&nbsp;color:&nbsp;#666; }
&nbsp; &nbsp;&nbsp;</style>
</head>
<body>
&nbsp; &nbsp;&nbsp;<h1>429 Too Many Requests</h1>
&nbsp; &nbsp;&nbsp;<p>请求过于频繁,请稍后重试。</p>
&nbsp; &nbsp;&nbsp;<p>Request rate limit exceeded. Please try again later.</p>
</body>
</html>

7.5 限流 + JSON 返回

nginx

http&nbsp;{
&nbsp; &nbsp;&nbsp;limit_req_zone$binary_remote_addr&nbsp;zone=api_limit:10m&nbsp;rate=10r/s;

&nbsp; &nbsp;&nbsp;server&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;listen80;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;server_name&nbsp;api.example.com;

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;location&nbsp;/ {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;limit_req&nbsp;zone=api_limit burst=20&nbsp;nodelay;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;proxy_pass&nbsp;http://backend;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 限流时返回 JSON
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;error_page429&nbsp;=&nbsp;@rate_limit;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;location@rate_limit&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;default_type&nbsp;application/json;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return429'{"code":429,"message":"rate limit exceeded","retry_after":60}';
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

八、日志与指标观察方法

8.1 关键日志

8.1.1 Nginx 限流日志

bash

# 限流事件(Nginx 默认 error log)
tail&nbsp;-F /var/log/nginx/error.log | grep -i&nbsp;'limiting requests'

# 输出示例:
# 2024/01/01 10:30:01 [warn] 1234#0: *5678 limiting requests, excess: 20.000 by zone "api_limit", client: 1.2.3.4, server: api.example.com, request: "GET /api/data HTTP/1.1"

8.1.2 自定义日志

nginx

log_format&nbsp;limit_log&nbsp;'$remote_addr&nbsp;- [$time_local] '
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'$request&nbsp;$status&nbsp;'
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'limit_req=$limit_req_status&nbsp;'
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'limit_conn=$limit_conn_status&nbsp;'
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'rt=$request_time';

注意:$limit_req_status 是 Nginx 1.17.7+ 才有的变量。

8.2 关键指标

8.2.1 限流相关指标

  • 429 比例
  • 各 URI 的 429 比例
  • 各 IP 的 429 次数
  • 限流错误日志条数
  • limit_req zone 内存使用

8.2.2 Prometheus 抓取

yaml

# /etc/prometheus/prometheus.yml
scrape_configs:
&nbsp;&nbsp;-&nbsp;job_name:&nbsp;'nginx'
&nbsp; &nbsp;&nbsp;static_configs:
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;targets:&nbsp;['nginx-exporter:9113']

8.2.3 关键 PromQL

promql

# 429 比例
sum(rate(nginx_http_requests_total{status="429"}[5m]))
/ sum(rate(nginx_http_requests_total[5m]))

# 各 URI 的 429 率
sum by (uri) (rate(nginx_http_requests_total{status="429"}[5m]))

# 各 IP 的 429 次数
sum by (client_ip) (rate(nginx_http_requests_total{status="429"}[5m]))

8.2.4 告警规则

yaml

groups:
-name:nginx_limit
rules:
-alert:Nginx429Spike
&nbsp; &nbsp;&nbsp;expr:rate(nginx_http_requests_total{status="429"}[1m])>100
&nbsp; &nbsp;&nbsp;for:1m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:warning
&nbsp; &nbsp;&nbsp;annotations:
&nbsp; &nbsp; &nbsp;&nbsp;summary:"429 错误数飙升,可能是限流触发"

-alert:Nginx429Ratio
&nbsp; &nbsp;&nbsp;expr:|
&nbsp; &nbsp; &nbsp; sum(rate(nginx_http_requests_total{status="429"}[5m]))
&nbsp; &nbsp; &nbsp; /
&nbsp; &nbsp; &nbsp; sum(rate(nginx_http_requests_total[5m]))
&nbsp; &nbsp; &nbsp; > 0.05
&nbsp; &nbsp;&nbsp;for:2m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:critical
&nbsp; &nbsp;&nbsp;annotations:
&nbsp; &nbsp; &nbsp;&nbsp;summary:"429 比例超过 5%,可能限流过严"

-alert:NginxConnectionHigh
&nbsp; &nbsp;&nbsp;expr:nginx_connections_active>50000
&nbsp; &nbsp;&nbsp;for:1m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:warning
&nbsp; &nbsp;&nbsp;annotations:
&nbsp; &nbsp; &nbsp;&nbsp;summary:"Nginx 活跃连接数过高"

注意:阈值必须按业务基线调整。

九、排查路径

9.1 故障排查案例 1:429 飙升

现象:

  • 告警:429 错误数飙升
  • 业务反馈:用户登录失败

初步判断:

  • 限流配置过严
  • 业务量突增
  • 客户端有重试风暴

命令检查:

bash

# 1. 看 429 比例
awk&nbsp;'$9 == 429'&nbsp;/var/log/nginx/access.log |&nbsp;wc&nbsp;-l

# 2. 看 429 Top IP
awk&nbsp;'$9 == 429'&nbsp;/var/log/nginx/access.log | awk&nbsp;'{print $1}'&nbsp;|&nbsp;sort&nbsp;|&nbsp;uniq&nbsp;-c |&nbsp;sort&nbsp;-rn |&nbsp;head&nbsp;-20

# 3. 看 429 Top URI
awk&nbsp;'$9 == 429'&nbsp;/var/log/nginx/access.log | awk&nbsp;'{print $7}'&nbsp;|&nbsp;sort&nbsp;|&nbsp;uniq&nbsp;-c |&nbsp;sort&nbsp;-rn |&nbsp;head&nbsp;-20

# 4. 看 429 时间分布
awk&nbsp;'$9 == 429'&nbsp;/var/log/nginx/access.log | awk&nbsp;'{print $4}'&nbsp;|&nbsp;cut&nbsp;-c14-21 |&nbsp;uniq&nbsp;-c

# 5. 看错误日志
tail&nbsp;-F /var/log/nginx/error.log | grep -i&nbsp;limit

关键指标:

  • 429 Top IP:集中在某几个 IP
  • 429 Top URI:集中在某个接口
  • 错误日志:limiting requests by zone

根因定位:

  • 单 IP 高频 → 客户端有重试循环
  • 单 URI 高频 → 接口被刷

修复方案:

  • 短期:调整 limit_req 的 rate
  • 中期:客户端重试退避
  • 长期:分布式限流

验证:

  • 429 比例下降
  • 业务反馈恢复
  • 客户端日志确认

回滚:

  • 临时注释 limit_req
  • reload 回去

9.2 故障排查案例 2:被 CC 攻击

现象:

  • 告警:Nginx 带宽打满
  • 告警:upstream QPS 突增
  • 业务反馈:服务慢

初步判断:

  • CC 攻击
  • 爬虫狂扫

命令检查:

bash

# 1. 看 Top IP
awk&nbsp;'{print $1}'&nbsp;/var/log/nginx/access.log |&nbsp;sort&nbsp;|&nbsp;uniq&nbsp;-c |&nbsp;sort&nbsp;-rn |&nbsp;head&nbsp;-20

# 2. 看 Top UA
awk&nbsp;'{print $14}'&nbsp;/var/log/nginx/access.log |&nbsp;sort&nbsp;|&nbsp;uniq&nbsp;-c |&nbsp;sort&nbsp;-rn |&nbsp;head&nbsp;-20

# 3. 看请求时间分布
awk&nbsp;'{print $4}'&nbsp;/var/log/nginx/access.log |&nbsp;cut&nbsp;-c14-21 |&nbsp;uniq&nbsp;-c

# 4. 看异常请求
awk&nbsp;'$14 ~ /python|java|sqlmap|scrapy/'&nbsp;/var/log/nginx/access.log |&nbsp;head

关键指标:

  • Top IP 集中度高
  • 异常 UA 占比高
  • 请求时间分布不均

根因定位:

  • 大量同 IP 或同 IP 段的高频请求
  • 没有 UA 或 UA 是常见攻击工具

修复方案:

nginx

# 1. 启用 UA 拦截
map$http_user_agent$is_bad_ua&nbsp;{
&nbsp; &nbsp;&nbsp;default0;
&nbsp; &nbsp; ~*sqlmap1;
&nbsp; &nbsp; ~*nmap1;
&nbsp; &nbsp; ~*scrapy1;
&nbsp; &nbsp; ~*python-requests1;
&nbsp; &nbsp; ~*go-http-client1;
}

server&nbsp;{
&nbsp; &nbsp;&nbsp;if&nbsp;($is_bad_ua) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return444;
&nbsp; &nbsp; }
}

# 2. 加 iptables 黑名单
iptables&nbsp;-I INPUT -s&nbsp;1.2.3.4&nbsp;-j DROP

# 3. 启用严格限流
limit_req zone=ip_limit burst=5&nbsp;nodelay;

验证:

  • 攻击 IP 减少
  • 5xx 下降
  • 带宽恢复

回滚:

  • 调整限流 rate
  • 解除部分 IP 黑名单

9.3 故障排查案例 3:突发流量

现象:

  • 告警:Nginx 5xx 飙升
  • 业务方:限时活动开始

初步判断:

  • 突发流量超过平时容量
  • 上游服务响应慢

命令检查:

bash

# 1. 看 QPS 变化
awk&nbsp;'{print $4}'&nbsp;/var/log/nginx/access.log |&nbsp;cut&nbsp;-c14-21 |&nbsp;uniq&nbsp;-c

# 2. 看 5xx 比例
awk&nbsp;'$9 ~ /^5/'&nbsp;/var/log/nginx/access.log |&nbsp;wc&nbsp;-l

# 3. 看 upstream 响应时间
awk&nbsp;'{print $urt}'&nbsp;/var/log/nginx/access.log |&nbsp;sort&nbsp;-rn |&nbsp;head&nbsp;-20

关键指标:

  • QPS 突增
  • upstream 响应时间变长
  • 5xx 比例升高

根因定位:

  • 突发流量超过平时 10 倍
  • 限流没启用或 burst 太大

修复方案:

  • 立即启用严格限流
  • 扩容上游
  • 客户端降级
nginx

# 立即启用
limit_req&nbsp;zone=api_limit burst=10&nbsp;nodelay;

验证:

  • 5xx 下降
  • 业务恢复

回滚:

  • 活动结束后调宽限流
  • 灰度恢复

十、风险提醒

10.1 限流配置的风险

  • rate 太小

    :误伤正常用户

  • rate 太大

    :起不到限流作用

  • burst 太大

    :突发流量全部放行

  • burst 太小

    :正常请求被拒绝

  • zone 太小

    :内存溢出或 key 淘汰

  • key 选错

    :限流维度不对

10.2 限流变更的风险

  • 修改 limit_req 前要压测
  • 修改后要灰度
  • 出现误伤要快速回滚
  • 配合客户端重试退避

10.3 限流与上游联动

  • 限流不能让上游变快
  • 限流挡住的请求要让客户端感知
  • 限流挡住的请求要记日志
  • 限流挡住的请求要有降级页面

10.4 限流与监控

  • 429 比例要监控
  • 限流错误日志要监控
  • 上游 QPS 要监控
  • 客户端重试比例要监控

10.5 限流与安全

  • 限流不能替代 WAF
  • 限流不能替代 IP 黑名单
  • 限流不能替代验证码
  • 限流不能替代 HTTPS

十一、验证方式

11.1 配置验证

bash

# 1. 测试配置语法
nginx -t

# 2. 模拟压测
wrk -t 4 -c 100 -d 30s&nbsp;'http://api.example.com/test'

# 3. 看 429 比例
echo&nbsp;"Total:&nbsp;$(wc -l < /var/log/nginx/access.log)"
echo&nbsp;"429:&nbsp;$(awk '$9 == 429' /var/log/nginx/access.log | wc -l)"

11.2 灰度验证

bash

# 1. 在单节点启用
# 修改配置
nginx -t && nginx -s reload

# 2. 持续压测
wrk -t 4 -c 100 -d 60s&nbsp;'http://<single_node_ip>/test'

# 3. 看响应码
# 应该有 200 + 部分 429

# 4. 持续 5 分钟后扩展到全量

11.3 灰度上线流程

bash

# 1. 准备配置
cat&nbsp;> /etc/nginx/conf.d/limit_v2.conf <<'EOF'
limit_req_zone&nbsp;$binary_remote_addr&nbsp;zone=api_v2:10m rate=20r/s;
EOF

# 2. 在测试节点加载
# 修改配置 -> nginx -t -> nginx -s reload

# 3. 持续观察
tail&nbsp;-F /var/log/nginx/access.log | grep&nbsp;'status="429'

# 4. 5 分钟后扩展
# 通过配置中心 / ansible 推送到所有节点

# 5. 全量后持续观察

十二、回滚方案

12.1 配置回滚

bash

# 1. 修改前备份
cp&nbsp;/etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date&nbsp;+%Y%m%d_%H%M%S)

# 2. 修改后测试
nginx -t

# 3. 平滑 reload
nginx -s reload

# 4. 出问题立即回滚
cp&nbsp;/etc/nginx/nginx.conf.bak.<timestamp> /etc/nginx/nginx.conf
nginx -t && nginx -s reload

12.2 关闭限流

bash

# 临时关闭(注释 limit_req)
sed -i&nbsp;'s/^ &nbsp; &nbsp;limit_req/# &/'&nbsp;/etc/nginx/nginx.conf
nginx -t && nginx -s reload

# 紧急关闭(直接去掉 zone)
# 但要去掉所有引用

12.3 调整限流参数

bash

# 把 rate 调宽(限流放宽)
# 改前
limit_req_zone&nbsp;$binary_remote_addr&nbsp;zone=api_limit:10m rate=10r/s;
# 改后
limit_req_zone&nbsp;$binary_remote_addr&nbsp;zone=api_limit:10m rate=100r/s;

nginx -t && nginx -s reload

12.4 紧急情况

如果限流配置错误导致大面积 429:

bash

# 1. 立即恢复
cp&nbsp;/etc/nginx/nginx.conf.bak.<timestamp> /etc/nginx/nginx.conf
nginx -t && nginx -s reload

# 2. 确认恢复
curl -i&nbsp;'http://api.example.com/test'

# 3. 通知业务方

十三、生产环境注意事项

13.1 限流参数调优

  • 拿监控数据,按业务量设置
  • 拿压测数据,验证限流效果
  • 拿真实数据,调整 burst
  • 持续观察,按业务增长调整

13.2 限流与客户端配合

  • 客户端要做好 429 退避
  • 客户端要做好超时控制
  • 客户端要有限流提示
  • 客户端要有降级方案

13.3 限流与上游配合

  • 上游要有降级方案
  • 上游要有缓存
  • 上游要有限流(应用层)
  • 上游要有熔断

13.4 限流与监控

  • 429 比例监控
  • 限流错误日志监控
  • 上游 QPS 监控
  • 客户端重试监控

13.5 限流与灰度

  • 单节点验证
  • 部分流量验证
  • 全量上线
  • 持续观察

13.6 限流与安全

  • WAF 配合
  • IP 黑名单
  • 验证码
  • HTTPS

13.7 限流与高可用

  • 多机房限流(每机房独立)
  • 多集群限流(每集群独立)
  • 全局限流(用 OpenResty + Redis)
  • 动态限流(按业务量自动调整)

13.8 限流与容量

  • 容量评估
  • 弹性扩容
  • 应急预案
  • 故障演练

十四、总结

Nginx 限流是接入层必备的能力。这一篇把核心讲清楚了:

基础概念

  • limit_req_zone、limit_req、limit_conn_zone、limit_conn
  • burst、nodelay、delay 的差别
  • 漏桶算法
  • key 的选择

实战配置

  • 按 IP 限流
  • 按 URI 限流
  • 多级限流
  • 白名单、黑名单
  • CC 攻击防护
  • 突发流量削峰
  • OpenResty + Redis 复杂限流

生产实践

  • 压测验证
  • 灰度上线
  • 监控告警
  • 回滚方案

关键参数

  • rate:按真实业务量 + 冗余
  • burst:按允许的突发 + 排队容忍
  • zone:按 key 数量 × 16KB

多层防护

  • Nginx 接入层
  • 应用层(Sentinel / Guava)
  • 中间件层(Redis 令牌桶)
  • 数据库层(连接池)

面试要点

  • 限流算法(漏桶 vs 令牌桶)
  • 限流配置(limit_req + burst + nodelay)
  • 限流参数(rate、burst、key)
  • 限流场景(CC、突发、爬虫)
  • 限流与降级、熔断的关系

最终验收点:

  • 拿到一个被 CC 攻击的告警,能否 10 分钟内上线防护
  • 拿到一个 429 飙升的告警,能否定位是配置问题还是客户端问题
  • 拿到一个限流参数调优需求,能否按业务量算出合理的 rate 和 burst

如果能做到这三点,这篇文章的价值就达到了。

文末福利

今天给大家分享一份超级牛掰的Linux学习笔记,足足有1456页!是一位Linux运维大佬整理分享的,分享是获得大佬同意的,大家有需要的尽管收藏起来!

笔记介绍

这份笔记非常全面且详细,从Linux基础到shell脚本,再到防火墙、数据库、日志服务管理、Nginx、高可用集群、Redis、虚拟化、Docker等等,与其说Linux学习笔记,不如说是涵盖了运维各个核心知识。

并且图文并茂,代码清晰,每一章下面都有更具体详细的内容,十分适合Linux运维学习参考!

笔记展示

笔记下载

扫描下方二维码,回复暗号“1456页Linux笔记“,即可100%免费领取成功


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:马哥Linux运维 点击关注 👉 点击关注 👉《Nginx 限流实战:如何优雅地防御 CC 攻击和突发流量》

评论:0   参与:  0