一、突发流量和恶意抓取的真实困境

你有没有碰到过这种情况:自家的电商平台刚上了一款限量特价商品,瞬间就涌来上万用户抢,服务器直接卡成PPT;或者半夜里,有程序一直在爬取你的商品数据、用户评论,不仅占带宽,还可能把真实用户的请求挤掉?这俩问题,一个是突发的正常流量(但太猛扛不住),一个是持续的恶意请求(偷数据还捣乱),单靠某一种办法解决不了,得靠Nginx的两个核心限流模块配合。

1.1 两个问题的本质区别

先搞懂俩问题的本质,才能选对工具:

  • 突发流量:是正常用户的合法请求,只是短时间内太多,比如秒杀、大促、热点事件带来的访问,我们的目标是“扛住”,不能让真实用户打不开页面,还要尽量多服务正常请求。
  • 恶意抓取:是非法的、无意义的请求,比如爬虫爬取私密数据、刷评论、刷库存,我们的目标是“拦截”,直接把这类请求挡在外面,别占资源。

二、Nginx两个限流模块的基础认知

Nginx自带两个专门的限流模块,一个管“请求的频率”,一个管“连接的数量”,刚好对应上面的两个问题。

2.1 limit_req:管请求频率(解决突发流量)

limit_req的核心是“漏桶算法”——你可以把它想象成一个底部有固定出水速度的水桶,不管你往桶里倒多少水(多少请求),水只能按固定速度流出去(处理请求)。如果桶倒满了,多余的水(请求)就会溢出(被拒绝)。 举个生活化的例子:小区快递柜,每个柜子每10秒只能取一件快递(固定处理速度),快递员一次送100件(突发请求),柜子只能按10秒一件的速度处理,剩下的快递员只能先放快递站(队列),如果快递站也满了,就只能退回(拒绝)。

2.2 limit_conn:管连接数量(解决恶意抓取)

limit_conn的核心是“固定连接上限”——你可以把它想象成餐厅的座位,每个客人占一个座位(一个连接),如果座位满了,新来的客人就不能进去(拒绝)。恶意抓取的爬虫,往往会同时开很多连接,占用所有座位,导致真实用户没位置坐,limit_conn就是专门卡这个的。

三、两个模块的协同逻辑

单独用limit_req,可能会出现:恶意爬虫开100个连接,每个连接按固定频率发请求,总请求量还是很大;单独用limit_conn,可能会出现:突发的正常流量(比如秒杀),每个用户占一个连接,总连接数超过上限,真实用户被拒绝。所以必须两个配合:

  1. 先用limit_conn卡连接数:每个IP最多开几个连接,防止爬虫占满所有连接。
  2. 再用limit_req卡每个连接的请求频率:每个连接每几秒只能发一个请求,防止单个连接疯狂发请求。
  3. 给limit_req加队列:如果突发的正常请求太多,先放进队列里排队处理,不让真实用户直接被拒绝。

四、实战配置示例

所有示例统一使用Nginx 1.20版本(主流稳定版),配置文件放在Nginx的conf.d目录下,命名为limit_config.conf。

4.1 基础配置(先定义规则)

首先在Nginx的http块里定义限流规则,http块是全局配置,所有虚拟主机都能用到:

# 定义limit_req的漏桶规则:每个IP($binary_remote_addr)每秒钟最多发1个请求
limit_req_zone $binary_remote_addr zone=req_zone:10m rate=1r/s;

# 定义limit_conn的连接规则:每个IP($binary_remote_addr)最多开2个连接
limit_conn_zone $binary_remote_addr zone=conn_zone:10m;

配置说明:

  • $binary_remote_addr:代表客户端的IP地址,用二进制存储,比字符串省内存,适合大量IP的场景。
  • zone=req_zone:10m:给这个限流规则起个名字叫req_zone,分配10MB的内存(大概能存16万个IP的规则)。
  • rate=1r/s:漏桶的出水速度,每秒钟最多处理1个请求。
  • zone=conn_zone:10m:给连接限流规则起个名字叫conn_zone,分配10MB的内存。

4.2 应用到具体站点(协同生效)

然后在具体的server块(比如电商平台的域名)里应用这两个规则,这里以电商的商品详情页为例,我们要保护这个页面,既防爬虫,又扛住突发流量:

server {
    listen 80;
    server_name example.com; # 你的电商域名

    # 保护商品详情页:/product/开头的路径,比如/product/123
    location /product/ {
        # 先应用连接限流:每个IP最多开2个连接
        limit_conn conn_zone 2;
        # 再应用请求频率限流:使用req_zone规则,burst=5,nodelay
        limit_req zone=req_zone burst=5 nodelay;

        # 正常的反向代理配置,把请求转发到后端的电商服务
        proxy_pass http://backend_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # 其他页面的配置(比如首页、登录页)可以不加限流,或者加不同的规则
    location / {
        proxy_pass http://backend_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

配置说明:

  • limit_conn conn_zone 2:每个IP最多开2个连接,比如爬虫想同时开10个连接爬商品数据,最多只能开2个,剩下的直接被拒绝。
  • limit_req zone=req_zone burst=5 nodelay:
    • burst=5:队列的大小,也就是漏桶的容量,突发的请求可以先放进队列里排队,最多能排5个。
    • nodelay:如果队列里有请求,直接按漏桶的速度处理,不让队列里的请求等待太久,保证真实用户的体验。

4.3 进阶配置:区分正常用户和恶意用户

如果想更精细,比如对已经登录的用户(用Cookie识别)放松限制,对未登录的用户(可能是爬虫)严格限制,可以这么配置:

server {
    listen 80;
    server_name example.com;

    location /product/ {
        # 定义一个变量$is_logged_in,判断用户是否登录(根据Cookie里的user_id)
        set $is_logged_in 0;
        if ($http_cookie ~* "user_id=(\d+)") {
            set $is_logged_in 1;
        }

        # 先应用连接限流:未登录的用户最多开1个连接,已登录的最多开3个
        limit_conn conn_zone $is_logged_in;
        limit_conn_status 403; # 连接超过上限时,返回403状态码

        # 再应用请求频率限流:未登录的用户每2秒发1个请求,已登录的每0.5秒发1个请求
        limit_req zone=req_zone burst=5 nodelay;
        limit_req_status 429; # 请求频率超过上限时,返回429状态码

        proxy_pass http://backend_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

配置说明:

  • $is_logged_in:根据Cookie里的user_id判断用户是否登录,未登录时为0,已登录时为1。
  • limit_conn conn_zone $is_logged_in:未登录的用户($is_logged_in=0)最多开0个连接?不对,这里要改一下,正确的写法是:
# 修正后的连接限流配置
limit_conn conn_zone 1; # 未登录的最多1个
if ($is_logged_in) {
    limit_conn conn_zone 3; # 已登录的最多3个
}

这样更合理,已登录的真实用户可以多开几个连接,未登录的爬虫只能开1个。

五、应用场景、优缺点和注意事项

5.1 应用场景

  1. 电商秒杀、大促:用limit_req的队列保证真实用户能排队,用limit_conn防止爬虫占满连接。
  2. 内容平台的热点事件:比如某条视频爆了,用限流保证大部分用户能正常访问。
  3. 防止恶意爬虫:比如爬取商品数据、用户评论、接口数据,用limit_conn和limit_req配合,把爬虫的请求量降到最低。
  4. 接口防刷:比如短信验证码接口、登录接口,用限流防止恶意刷接口。

5.2 技术优缺点

优点

  1. 协同互补:limit_req管请求频率,limit_conn管连接数量,覆盖了突发流量和恶意抓取两种场景。
  2. 性能高:Nginx的限流是在内存里处理的,比后端服务的限流快很多,不会增加后端的负担。
  3. 配置灵活:可以根据IP、Cookie、请求头、URL等条件,给不同的用户设置不同的限流规则。
  4. 可控性强:可以设置队列大小、返回状态码、日志记录等,方便监控和调整。

缺点

  1. 无法识别动态IP:如果爬虫用代理IP(每个请求换一个IP),limit_conn和limit_req都会失效,因为每个IP的连接数和请求频率都不会超过上限。
  2. 可能误杀正常用户:如果真实用户的网络波动,导致连接数或请求频率超过上限,可能会被限流,影响体验。
  3. 配置复杂:如果规则太多,配置文件会变得很复杂,不容易维护。

5.3 注意事项

  1. 合理设置参数:rate(请求频率)、burst(队列大小)、limit_conn(连接数)要根据实际情况调整,比如秒杀时可以把burst设大一点,让更多用户能排队;防爬虫时可以把rate设小一点,limit_conn设小一点。
  2. 不要过度限流:如果限流太严,会导致很多真实用户被拒绝,影响业务。
  3. 监控限流状态:可以通过Nginx的日志(error.log)查看被限流的请求,比如:
# 在error.log里开启限流日志
error_log /var/log/nginx/error.log notice;

日志里会出现类似“limiting requests, excess: 5.000 by zone "req_zone"”的信息,说明有请求被限流了。 4. 结合其他防护手段:比如CDN、WAF(Web应用防火墙)、验证码、接口签名等,和Nginx的限流配合,效果更好。

六、文章总结

Nginx的limit_req和limit_conn两个限流模块,一个管请求频率,一个管连接数量,通过协同工作,既能扛住突发的正常流量(比如秒杀、大促),又能拦截恶意的抓取请求(比如爬虫、刷接口)。在实际使用时,要根据业务场景合理设置参数,结合队列和漏桶算法,保证可用性的同时,拦截恶意请求。同时,要注意监控限流状态,结合其他防护手段,才能构建更完善的防护体系。